sychronized原理

         每个Java程序员都知道synchroniezd关键字, 用在多线程并发时解决共享冲突的问题。 但它的实现原理是什么呢?  其实跟Java CAS原理类似, 就是计数0变1或1变0的过程。  

    

synchronized的应用场景:

1、用在实例方法, 其实就是对当前实例加锁;

2、用在代码块, 括号里就是加锁对象;

3、用在静态方法, 其实就是对类对象加锁;


字节码说明: 

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter

The objectref must be of type reference.

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:

If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.

If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

翻译过来就是:

每个Java对象都关联一个monitor对象, objectref是指向它的引用, 默认情况下引用计数等于0; 多线程并发时只能有一个线程(即拿到锁)将0变为1, 且拿到锁的线程可重入, 即将值变为2、3、4等等等; 其它线程判断值非0则等待, 在进入数为0时表示释放锁, 所有线程可以重新抢锁。


The objectref must be of type reference.

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
翻译过来就是:

只能是有锁的线程才能执行monitorexit, 每次执行entry数量减1, 当entry数量为0时表示释放锁。

PS:跟Java CompareAndSet原理是一样的


 使用生产者消费者模式解释synchronized的原理。

public class SyncTest {

    private List<String> mList;
    private final int SIZE = 10;

    public SyncTest() {
        mList = new ArrayList<>();
    }

    /**
     * 生产几个商品
     * @param count,商品个数
     */
    public void product(int count) {
        synchronized (this) {
            if (mList.size() + count >= SIZE) {
                System.out.println("生产者线程" + ", 空间已满等待消费。。。");
                try {
                    this.wait();
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }

            for (int i = 0; i < count; i++) {
                //mList.add("test");
                add("test");  //synchronized是可重入锁
            }

            System.out.println("生产者线程" +", 已生产" + count + "个商品,剩余:" + mList.size());
            this.notifyAll();
        }
    }

    public synchronized void add(String data) {
        //实例对象上锁
        mList.add(data);
    }

    /**
     * 消费几个商品
     * @param count,商品个数
     */
    public void consume(int count) {
        synchronized (this) {
            /*
            if (mList.size() < count ) {
                System.out.println("消费者线程" +  ", 空间不够等待生产。。。");
                try {
                    this.wait();
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }

            for (int i = 0; i < count; i++) {
                mList.remove(0);
            }

            System.out.println("消费者线程" +  ", 已消费" + count + "个商品,剩余:" + mList.size());
            this.notifyAll();
            */
        }

    }

    public static synchronized void delete(String data) {
        //类本身上锁
    }
}

 product函数体里调用了add方法, 而add方法是同步方法。 说明是可重入锁


在cmd窗口执行java -p -v SyncTest.class反编译成字节码。

       如上面字节锁所示, 一个synchronized代码块对应1个monitorenter和2个monitorexit, 但运行时monitorenter和monitorexit是成对出现的, 即只会执行一个monitorexit。


       对于同步方法, 反编译后就是多个ACC_SYNCHRONIZED标识符。 虚拟机是根据该标识符实现同步的, 实现原理是虚拟机会先获取monitor,获取成功后才执行函数体, 退出函数体时释放monitor; 在方法执行期间, 其它线程无法获取同一个monitor锁。 其实跟代码块原理是相同的, 区别是同步方法是隐式实现的,不需要字节码来实现



       小结:

synchronized关键字是通过设置monitor entry值实现锁的功能, 原理上跟Java CompareAndSet是一致的, 区别是synchronized关键字是在虚拟机上实现的, 而CAS是在CPU实现的原子操作。

猜你喜欢

转载自blog.csdn.net/brycegao321/article/details/79566665