线程安全的三个方法

1、互斥同步(阻塞同步)

(1)synchronized关键字是常见的阻塞手段,synchronized编译之后会在代码块前后添加minitorenter和monitorexit指令,synchronized根据修饰的类或者对象,进行锁定,尝试获取锁,成功之后monitorenter会将锁计数器加一,monitorexit指令会将计数器减一,为0则释放锁。synchronized指令对同一个线程是可重入的。线程执行完之前,会阻塞后边的线程。

值得注意的是,Java的线程都是映射到操作系统(OS)上的,如果要阻塞或者唤醒一个线程都需要操作系统来帮忙,从用户态转换到核心态,很耗cpu时。对于简单的代码块,状态转换比代码更耗时。

(2)java.util.concurrent中的可重入锁一样可以说实现同步,功能与synchronized类似,一样具有具有可重入性、互斥性,区别是一个是API层面的锁,一个是原生语法层面的。相比synchronized,ReentrantLock添加了更多的功能,主要是三点:

一是等待可中断(持有锁的线程长时间不释放的时候,等待的线程可执行其它操作),

二是可实现公平锁(构造参数的boolean值代表是否公平锁)(可根据等待锁的时间顺序依次获取锁,syncharonized是非公平锁)

三是可指定解锁条件(ReentrantLock可绑定多个Condition,只需要lock.newCondition()即可)

两个锁对比,synchronized在多线程高并发的情况下,性能下降的非常严重,ReentranLock是最佳选择,synchronized有很多要优化的地方

注意:
1、通过代码块的锁对象,可以是任意的对象
2、必须保证多个线程使用的锁对象是同一个
3、锁对象的作用是把同步代码快锁住,只允许一个线程在同步代码块执行

2、非阻塞同步

互斥同步是属于悲观锁的并发策略,因为它总认为会出现并发问题,所以做同步措施。最新的硬件指令集提供了一个基于冲突检测的乐观锁并发策略:先进行操作,如果有没有冲突,就操作成功,如果有冲突,在进行补偿(常见的是重新操作,直到成功)。

这种乐观锁的同步策略需要将操作和冲突检测放在一个指令集里边。

常见的类似指令 :

(1)测试并设置(Test And Set)

(2)获取并增加(Fetch And Increment)

(3)交换(Swap)

(4)比较并交换(Compare And Swap)

(5)加载链接/条件存储(Load Linked/Store Conditional)

CAS指令需要三个参数(V、A、B)V是内存地址,A是旧值,B是新值,当且仅当V符合A的值时候,cpu会将新值B更新到地址V,这是连续的原子操作

JDK1.5之后,在Java的sun.misc.Unsafe提供CAS操作,如:CompareAndSwapInt()、compareAndSwapLong()等

CAS的漏洞:“ABA问题”,原值A,地址目前也是A,但是无法确认A是否是被修改过的A,还是原来的A

3、无同步方案

一些代码天生是线程安全的,因此不需要进行线程安全的操作。如:

(1)可重入代码:一些代码可以运行的时候,可以中断执行其它代码,在获取线程的执行权之后继续执行,不会有错误。

可重入代码有以下特征:不依赖存储在堆上的数据和公共资源、用到的数据是参数传入,不调用不可重入数据。

可如此判断:一个方法,它的结果是可以预测的,输入了相同的数据,既可以返回预测的数据。

(2)线程本地存储:如果一段代码必须与其它线程共享数据,我们就看看这数据能否限制在同一个线程内,如果可以的话,即可以无须同步。

这三点可以参照操作系统中的内容进行深入的学习

你可能感兴趣的:(安全,java,开发语言,操作系统)