源码角度理解Synchronized

2019-10-07  本文已影响0人  Aaron_Swartz
public class AccountingSync implements Runnable {
    // 共享资源: 这里是静态资源才可以被多个线程操作
    static int i = 0;

    public synchronized void increase(){
        ++i;
    }
    public static  synchronized  void increase(){
        ++i;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100000; ++i) {
            increase();
        }
    }

    public static void main(String[] args) {
        AccountingSync async = new AccountingSync();
        AccountingSync async1 = new AccountingSync();
        Thread t1 = new Thread(async); 
        Thread t2 = new Thread(async1);
        try{
            t1.start(); // 当调用线程的t1.run()方法的时候其实并没有启动线程的操作,仅仅是函数调用而已。
            t2.start();
            t1.join();
            t2.join();
        } catch (InterruptedException e){
            //
        }
        System.out.println(i);
    }
}

当传入不同对象的时候,并不能起到加锁的目的。synchronized作用于非静态方法时,锁住的是实例,此时此实例的其它synchronized方法也不能被其他线程访问,但是非synchronized 方法可以被其他线程访问。当synchronized作用于静态方法时锁住的是类。

public class AccountingSync implements Runnable {

    static Object instance = new Object();
   // Object instance = new Object(); 这样写会有问题

    // 共享资源: 这里是静态资源才可以被多个线程操作
    static int i = 0;

    public static synchronized void staticIncrease(){
        ++i;
    }

    @Override
    public void run() {
        synchronized (instance) {
            for (int j = 0; j < 100000; ++j) {
                ++i;
            }
        }
    }
    public static void main(String[] args) {
        Thread t1 = new Thread(new AccountingSync());
        Thread t2 = new Thread(new AccountingSync());
        try{
            t1.start(); // 当调用线程的t1.run()方法的时候其实并没有启动线程的操作,仅仅是函数调用而已。
            t2.start();
            t1.join();
            t2.join();
        } catch (InterruptedException e){
            //
        }
        System.out.println(i);
    }
}

从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。

方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。

锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

synchronized (obj) {
       obj.wait();
       obj.notify();
       obj.notifyAll();         
 }

需要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。同时,线程sleep()方法会导致线程进入阻塞态。

参考:
1 synchronized的三种应用方式

上一篇下一篇

猜你喜欢

热点阅读