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

发布时间:2020-06-05 10:02:18 作者:wx5c78c8b1dbb1b
来源:网络 阅读:612
  1. 线程不安全

线程安全

解决线程安全问题(一)

  1. Ad-hoc 线程封闭

栈封闭(常用)

/**
 * 获取user总数
 * @param userList
 * @return
 */
public int getTotalUser (List<User> userList) {
    List<User> 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<Connection> connectionThreadLocal
            = new ThreadLocal<Connection>(){
        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关键字.)


推荐阅读:
  1. 初学Java多线程的基本概念
  2. Java基础 (3) - String/StringBuilder/Buffer StringFor

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

编程语言 java ava

上一篇:PHP程序mysql报mysql has gone away错误的原因及解决办法

下一篇:Windows安装MySQL5.7教程及忘记root密码的解决方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》