当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的
一 线程安全的实现方法
1.1 互斥同步
互斥同步(Mutual Exclusion & Synchronization)是一种最常见也是最主要的并发正确性保障手段。同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条(或者是一些,当使用信号量的时候)线程使用.
在Java里面,最基本的互斥同步手段就是synchronized关键字,这是一种块结构(BlockStructured)的同步语法。synchronized关键字经过Javac编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。这两个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java源码中的synchronized明确指定了对象参数,那就以这个对象的引用作为reference;如果没有明确指定,那将根据synchronized修饰的方法类型(如实例方法或类方法),来决定是取代码所在的对象实例还是取类型对应的Class对象来作为线程要持有的锁。
synchronized的主要特征
1.可重入
2.没有办法被中断
3.非公平锁
锁的另一种常见的实现是Lock锁,
lock锁的主要特征
1.重入锁
2.等待可以被中断
3.公平锁
4.可以绑定多个条件
一个ReentrantLock对象可以同时绑定多个Condition对象。在synchronized中,锁对象的wait()跟它的notify()或者notifyAll()方法配合可以实现一个隐含的条件,如果要和多于一个的条件关联的时候,就不得不额外添加一个锁;而ReentrantLock则无须这样做,多次调用newCondition()方法即可。
lock锁和synchronized的选取
优先使用synchronized,因为synchronized不需要手动释放锁
1.2 非阻塞同步
得益于硬件指令集合的发展,比较并且设置可以在硬件上成为一个原子性操作,所以就有了CAS操作(比较并且交换),CAS需要三个值,第一个是设置的值A,第二个是期望的旧值B,第三个是变量的内存的地址V,如果B和内存中的值相同,就把内存中的值设置为A,否则就返回false
1.3 非阻塞的同步
每一个线程需要操作的数据都存储在线程本地,不和其他线程共享,例如ThreadLocal的实现
二 锁的优化
JDK5之后对synchronized进行了优化,并不是每一次都需要调用内核接口进入到内核态中
锁升级的过程
synchronized关键字是依赖对象的对象头实现的.一共有64位
2.1 偏向锁
上锁的过程
锁的状态是无锁态,在Java锁发现当前代码块没有被锁住的时候,在锁对象中记录下来当前的线程id信息,这个时候锁对象指向了拥有锁的线程id,并且开启偏向锁标记位置
解除锁过程
解锁锁就是锁升级的过程,当一个线程获取偏向锁失败的时候,在stop-the-word时候暂停线程,判断偏向锁指向的线程是否存活,如果存活,让其获取到轻量级锁,否则的话请求获取锁的线程获取到轻量级锁
偏向锁的好处
偏向锁针对的情景是假设在程序运行的生命周期中,这个锁只会被一个线程抢占了,不会有另一个线程使用
2.2 轻量级锁
上锁的过程
在关闭偏向锁的情况下或者获取偏向锁失败时候,获取轻量级,轻量级锁是在本次线程栈中拷贝一份锁对象的markword,然后请求对象头将对象指针指向请求的线程,如果成功,请求线程获取锁,否则进行自旋获取锁,如果获取不到升级到重量级锁
解锁过程
当获取不到轻量级锁的时候,这个线程修改锁对象的markdown中的指针指向到互斥量,并且将自己阻塞悬挂起来.当获取到轻量级的锁通过CAS机制将copy的markword数据写入到对象头中,如果成功就释放了锁,失败的坏证明已经升级为重量级锁,需要唤醒对应的线程
轻量级锁的用处
使用的场景是线程交互进入到代码块中