浅析synchronized和volatile

并发理论:
    1.数据共享性
                数据共享是线程安全的主要原因之一
    2.互斥性
                同时只允许一个访问者访问,通常允许多个线程同时对数据进行读操作,但同时只允许一个线程进行写操作
    3.原子性
                对数据的操作是一个独立的、不可分割的过程,保证原子性可以采用CAS或加锁(synchronized,lock)
    4.可见性
                每个线程都有一个自己的工作内存(相当于高速缓冲区,以提高性能),对于共享变量,线程每次读取的是工作内存中共享变量的副本,写入的时候也直接修改工作内存中副本中的值。然后在某个时间点再将工作内存与主内存中的值进行同步。可通过synchronized或volatile来保证可见性。
    5.有序性
                为了提高性能,处理器和编译器会对指令进行重排序。可以使用volatile来禁止重排序。

synchronized的作用主要有三个:
    1.确保线程互斥的访问同步代码
    2.保证共享变量的修改能够及时可见
    3.有效解决重排序问题

synchronized原理:
    通过反编译发现,其实底层实现是通过两条指令
                monitorenter:每个对象有一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行monitorenter时尝试获取monitor的所有权:
                1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

                2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

                3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

                monitorexit:执行monitorexit的线程必须是对应monitor的所有者。
                指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。
    synchronized的语义底层是通过一个monitor的对象完成。

因此:同一个实例的不同synchronized修饰的方法是互斥的
            如果是静态方法的synchronized修饰,则不同实例也是互斥的
            
synchronized是通过对象内部的一个叫做monitor来实现的,但是监视器锁本质是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,也是为什么synchronized效率低的原因。为了提高性能,引入了“轻量级锁”和“偏向锁”

偏向锁和轻量级锁默认是开启的,可以通过:-XX:-UseBiasedLocking来禁用偏向锁。

轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

引入 偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

适应性自旋 :
        当线程在获取轻量级锁的过程中执行CAS操作失败时,是要通过自旋来获取重量级锁的。问题在于,自旋是需要消耗CPU的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费CPU资源。解决这个问题最简单的办法就是指定自旋的次数,例如让其循环10次,如果还没获取到锁就进入阻塞状态。但是JDK采用了更聪明的方式——适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。

锁粗化:

        就是将多次连接在一起的加锁、解锁合并为一次,将多个连续的锁扩展成一个范围更大的锁。 

     public void append(){
         stringBuffer.append("a");
         stringBuffer.append("b");
         stringBuffer.append("c");
     }
        每次调用stringBuffer.append()方法都需要加锁和解锁,如果虚拟机检测到有一系列的加锁和解锁操作,会将它们合并成一次范围更大的加锁和解锁操作。
锁消除:
        删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,可以认为这段代码是线程安全的,不必要加锁。 
    public void append(String str1, String str2) {
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
     }
虽然StringBuffer的append是一个同步方法,但这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去,是线程安全的,可以将锁消除。
        

wait方法:
    将当前运行的线程挂起,即让其进入阻塞状态。直到notify或notifyAll来唤醒线程。
    wait方法是一个本地方法,其底层是通过一个叫做监视器锁的对象来完成的。所以上面之所以会抛出异常,是因为在调用wait方式时没有获取到monitor对象的所有权,那如何获取monitor对象所有权?Java中只能通过Synchronized关键字来完成,修改上述代码,增加Synchronized关键字。wait方法的使用必须在同步的范围内,否则就会抛出IllegalMonitorStateException异常。
    
    既然wait方式是通过对象的monitor对象来实现的,所以只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了。notify和notifyAll的区别在于前者只能唤醒monitor上的一个线程,对其他线程没有影响,而notifyAll则唤醒所有的线程
    
    有两点注意:
     调用wait方法后,线程是会释放monitor对象的所有权的。
    一个通过wait方法阻塞的线程,必须同时满足以下两个条件才能被真正执行:
        线程需要被唤醒(notify/notifyAll)
        线程唤醒后需要竞争到锁(monitor)

线程的sleep方法与wait方法区别:
        sleep方法只是暂时让出cpu执行权,并不释放锁。而wait方法则需要释放锁。
线程的yield方法的作用是:
        yield:暂停当前线程,以便其它线程有机会执行。不过不能指定暂停的时间,并且不能保证当前线程立即停止,只是将Running状态转化成Runnable状态。
        join:父线程等待子线程执行完毕后再执行,即将异步执行的线程换为同步的线程。

wait等待其实是对象monitor,由于java中的每一个对象都有一个内置的monitor对象,自然所有的类都理应有wait/notify方法。

    volatile是提供的轻量级的解决可见性和有序性的方案。关于原子性,要强调:volatile修饰的变量单次读/写是可以保证原子性的,但并不能保证 i++这种操作的原子性。因为本质上:i++是读、写两次操作 
package com.paddx.test.concurrent;

public class Singleton {
    public static volatile Singleton singleton;

    /**
     * 构造函数私有,禁止外部实例化
     */
    private Singleton() {};

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
volatile可见性实现:
      (1)修改volatile变量时会强制将修改后的值刷新的主内存中。
      (2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。 



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值