线程安全之可见性问题

1、线程安全概念

  • 临界区:无法被多个线程同时访问的公共资源。
  • 竞态条件:可能发生在临界区内的特殊条件。
  • 共享资源:被多个线程读取或修改的资源。
  • 线程安全:
    • 如果一段代码是线程安全的,则不包含竞态条件。
    • 只有当多个线程更新共享资源时,才会发生竞态条件。
    • 栈封闭时,不会在线程间共享的变量,都是线程安全的。
    • 局部变量引用本身不共享,但是引用的对象存储在共享堆中。
    • 如果方法内创建的对象,只是在方法中传递,并且不对其他线程可用,也是线程安全的。
    • 判定规则:如果创建、使用和处理资源,永远不会逃脱单个线程的控制,该资源的使用是线程安全的。
  • 不可变对象
    • 创建不可变的共享对象,来保证对象在线程间共享时不会被修改,从而实现线程安全。
    • 如:不对外提供setter方法,构造方法中设置,final等。

 

2、Java中锁概念分类:

  • 自旋锁:为了不放弃CPU执行事件,循环的使用CAS技术对数据尝试进行更新,直至成功。
  • 悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。
  • 乐观锁:假定没有冲突,在修改数据时,如果发现数据和之前获取的不一致,则读取最新数据,修改后重试修改。
  • 独享锁/写锁(单写):给资源加上写锁,线程可以修改资源,其他线程不能再加锁(读锁或写锁)。
  • 共享锁/读锁(多读):给资源加上读锁,线程只能读取资源不能修改资源,其他资源也只能加读锁。
  • 可重入锁:线程拿到一把锁之后,可以自由进入同一把锁同步的其他代码。
  • 不可重入锁:线程拿到一把锁之后,不可以自由进入同一把锁同步的其他代码。
  • 公平锁:争抢锁的顺序,按照先来后到争抢,则为公平锁。
  • 非公平锁:争抢锁的顺序,不是按照先来后到争抢,则为非公平锁。

 

3、synchronized关键字简介

  • 属于最基本的线程通信机制
  • 基于对象监视器实现
  • Java中每个对象都与一个监视器相关联,一个线程可以锁定或解锁,一次只有一个线程可以锁定监视器。
  • 试图锁定该监视器的任何其他线程都会被阻塞,直到它们可以获得该监视器上的锁定为止。
  • 特性:可重入锁、独享锁、悲观锁
  • 锁范围:类锁、对象锁、锁消除、锁粗化、自旋锁、偏向锁、轻量级锁等
  • 根据JMM规定,可以保证可见性(读取最新主内存数据,结束后写入主内存)。

 

4、synchronized关键字使用

  • 修饰实例方法:作用于当前对象实例。
  • 修饰静态方法:给类加锁,静态成员是类成员,不属于任何一个对象实例,作用于当前类的所有对象实例。
  • 修饰代码块:指定加锁对象,进入同步代码块前要获得给定对象的锁。

尽量不要使用synchronized(String a),因为字符串常量池有缓存功能。

双重检测锁实现单例模式

public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 instance = new Singleton();这行代码分3步执行:

  1. 为instance分配内存空间;
  2. 初始化instance;
  3. 将instance指向分配的内存空间地址;

JVM具有指令重排特性,执行顺序可能变成1→3→2。

如果不加synchronized代码块,指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。如:线程T1执行了1和3,此时T2调用getInstance()后发现instance不为空,因此返回instance,但是此时instance还未被初始化。

 

5、synchronized关键字底层原理

属于JVM层面

5.1 synchronized同步语句块

使用javap命令查看字节码信息,可以发现,同步语句块的实现,使用的monitorenter和monitorexit指令,分别指向同步代码块的开始和结束位置。当执行monitorenter指令时,线程试图获取锁,即获取monitor/监视器的持有权。

5.2 synchronized修饰方法

使用javap命令查看字节码信息,可以发现,使用的是ACC_SYNCHRONIZED标识,表明该方法是一个同步方法,JVM执行相应的同步调用。

5.3 JDK1.6对synchronized关键字底层优化

JDK1.6对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术,来减少锁操作的开销。

锁主要四种状态:

  • 无锁状态
  • 偏向锁状态
  • 轻量级锁状态
  • 重量级锁状态

会随着竞争的激烈而逐渐升级,锁可以升级不可以降级。

详情可参考JavaGuide大神的文章,链接如下:

https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md

6.volatile关键字

可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。

JMM规定,happens-before同步原则:对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作。

volatile变量的访问控制符会加上ACC_VOLATILE,对volatile变量相关的指令不做重排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值