028-86922220

建站动态

根据您的个性需求进行定制 先人一步 抢占小程序红利时代

Java多线程安全(一)不共享与不可变

  1. 线程不安全

    成都创新互联致力于成都网站设计、网站制作,成都网站设计,集团网站建设等服务标准化,推过标准化降低中小企业的建站的成本,并持续提升建站的定制化服务水平进行质量交付,让企业网站从市场竞争中脱颖而出。 选择成都创新互联,就选择了安全、稳定、美观的网站建设服务!

线程安全

解决线程安全问题(一)

  1. Ad-hoc 线程封闭

栈封闭(常用)

/**
 * 获取user总数
 * @param userList
 * @return
 */
public int getTotalUser (List userList) {
    List userLists = null;
    int  totalUser = 0;
    userLists = userList;
    for (User user : userList) {
        totalUser ++;
    }
    return totalUser;
}

该方法userLists是一个局部变量,存在于每个线程的栈中,是每一个线程私有的,别的线程获取不到,只要不把这个对象的发布出去,也就是返回,这样这个userLists 闭在了这个线程栈中,就是线程安全的.而对于totalUser 这个基本类型来说,发布出去也没有关系,因为由于任何线程都无法获取对基本类型的引用,因此Java语言

的这种机制就确保了基本类型的局部变量始终封闭在线程内,也是线程安全的.

ThreadLocal类

public class ConnectionUtils {

    private static ThreadLocal connectionThreadLocal
            = new ThreadLocal(){
        protected  Connection initialValue () {
            Connection connection = null;
            try {
                Class.forName("org.postgresql.Driver").newInstance();
                connection = DriverManager.getConnection
                ("jdbc:postgresql://localhost:5432/postgres",
                        "postgres", "test");
            } catch (Exception e) {
                e.printStackTrace();
            }
            return connection;
        }

    };

    public static Connection getConnection () {
        return connectionThreadLocal.get();
    }

    public static void main (String[] args) throws Exception {
        for (int i = 0; i < 2; i++) {
            Thread thread = new Thread(() -> {
                Connection connection = ConnectionUtils.getConnection();
                System.out.println(Thread.currentThread().getName() + 
                "--------" + connection.toString());
            }, "thread" + i);
            thread.start();
        }
    }
}

thread0--------org.postgresql.jdbc4.Jdbc4Connection@4fce58ae

thread1--------org.postgresql.jdbc4.Jdbc4Connection@257f7c5b

通过代码可以看见两个线程获取了各自的连接对象,都是绑定在当前线程上的,第一次获取是调用initialValue这个方法的返回值来设定值的,如果调用set方法也会和当前

线程绑定.ThreadLocal源码实现分析参考:敬请期待Smile

不可变的对象

  1. 对象创建以后其状态就不能修改.

  2. 对象的所有域都是final类型.

  3. 对象是正确创建的(在对象的创建期间,this引用没有逸出).

Final 域

  1. final 类型的域是不能修改的(但如果final引用的对象是可变的,那么这些被引用的对象是可以修改的).在Java内存模型中,final域能够确保初始化过程的安全性.即使对象是可变的,通过将对象的某些域声明为final类型,仍然可以简化对状态的判断.通过将域声明为final类型,也相当于告诉维护人员这些域是不会变化的.

  2. 某些时候不可变对象提供了一种弱类型的原子性,如下代码示例:

public class OneValueCache {

    private final BigInteger lastNumber;

    private final BigInteger[] lastFactors;

    public OneValueCache (BigInteger i , BigInteger[] fastFactors) {
        lastNumber = i;
        lastFactors = Arrays.copyOf(fastFactors,fastFactors.length);
    }

    public BigInteger[] getFactors (BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i)) {
            return null;
        } else {
            return Arrays.copyOf(lastFactors,lastFactors.length);
        }
    }

 
}

代码分析:OneValueCache 有两个final 域的变量,并在构造函数时初始化它们(没有提供其它初始化数据方案,因为要保证初始化后状态的不可变),在getFactors 方法里面没有返回原数组引用,如果这样那就不安全了因为lastFactors数组的域是不可变的,但是引用对应的内容是可以修改的,所以要是有copyOf方法,返回一个新数组(也可以使用clone方法).如果我们要修改lastNumber和lastFactors只有调用构造方法重新构造一个不可变对象,而构造对象需要这两个变量一起传入,要么成功要么失败,所以说不可变对象是一种弱类型的原子性.

对于访问和更新多个相关变量时出现的竞争问题,可以通过将这些变量全部保存在一个不可变对象中来消除.如果是一个可变对象,那么就必须使用锁来确保原子性.如果是一个不可变对象,那么当前获得了带对象的引用后,就不必担心另一个线程会修改对象的状态.如果要更新这些变量,那么只有重新建一个新的容器对象,但其他使用原有对象的线程仍然看到对象处于一致状态(其它线程看见的还是原来的对象,如果要保证可见性,可以使用volatile关键字.)


当前标题:Java多线程安全(一)不共享与不可变
文章起源:http://www.tsicrk.com/article/pjijgj.html

其他资讯

让你的专属顾问为你服务

2.9529s