线程安全是不得不考虑的问题,那么java是如何实现线程安全的呢?
1.互斥同步
互斥同步(mutual exclusion & synchronization).同步是指并发访问数据时,保证共享数据同一时刻只被一个线程使用,互斥是手段, 同步是目的.
在java中,最基本的互斥同步手段就是synchronized关键字,synchronized在编译后,会在同步块前后分别形成monitorenter和monitorexit两个字节码指令,这两个指令需要一个reference类型的参数来指明要锁定和解锁的对象.通俗来讲,synchronized后面,如果写代码时指定了对象,那么就是这个对象的引用;如果没有写,那么如果synchronized修饰的是静态方法,那就是当前类的Class对象,如果是实例方法,那么就是当前的对象实例的引用.
当执行monitorenter指令时,如果当前对象还没有被锁定,或者当前线程已经拥有了这个对象的锁,那么把锁的计数器+1;当执行monitorexit指令时,就-1,当计数器的数值为0时,对象锁就被释放.如果获取对象锁失败,则当前线程被置于上图的Entry Set中,即需要阻塞等待,等待锁的owner释放锁之后,再去竞争锁.
对synchronized来说,对于同一线程,锁是可以重入的,即不会出现自己把自己锁死的情况.而在当前线程释放锁之前,会阻塞后面的线程进入.
因为java的线程会映射到操作系统的原生线程之上,所以当阻塞或者唤醒一个线程是需要耗费很多处理器的资源的(从用户态转换到核心态),对于比较简单的同步块,很可能切换消耗的时间会比代码执行的时间还长,所以synchronized是java中的一个重量级操作,不过随着java版本的更新,jvm本身会有一些优化,比如加入一段自旋等待过程,避免频繁地切换到核心态中.
另外,java.util.concurrent包下的ReentrantLock(重入锁)也可以用来实现同步,基本用法与synchronized相似,不过ReentrantLock增加了一些高级功能:等待可中断,可实现公平锁,以及锁可以绑定多个条件.
顾名思义,等待可中断是指等待线程可以放弃等待.
公平锁是指按照申请锁的时间来依次获得锁,ReentrantLock默认也是非公平的,可以用过new ReentrantLock(true)来使用公平锁.
绑定多个条件,待我再详细了解,哈哈...
总之,这两个相差不大,尤其java1.6之后,synchronized已经优化得差不多了,性能基本持平,所以还是synchronized用得比较多.
2.非阻塞同步
Blocking Synchronization是一种悲观的并发策略,不管三七二十一,一上来就先把锁加上,有可能发生并发的程序并不是每次都有多个线程去争夺资源的,所以,这里便出现了另外一种选择:基于冲突检测的乐观并发策略,即先进行操作,然后再看共享资源有没有被其他线程占用,如果没有,那么这次操作就成功了,如果有,那么采取其他补偿措施(如不断重试). 因为这种操作不需要把线程挂起,所以称作非阻塞同步(Non-Blocking Synchronization).
非阻塞同步能够实现的前提是冲突检测和数据操作这两个步骤具有原子性 .这依赖于"硬件指令集的发展",常用的这类指令有:blah blah blah....其中
比较并交换:CAS
加载链接/条件存储:LL/SC
是现代处理器常用的.
3.无同步方案
如果一个方法不涉及共享数据,那么他天生就是线程安全的.
网友评论