Java多线程拾遗(四)——简单聊聊synchronized

前言

关于synchronized的作用,以及为什么要有synchronized,这里就不再总结,这里只是总结一下synchronized中容易忽略的几个点

synchronized的具体表现

1、synchronized提供了一种锁的机制,能够确保共享变量的互斥访问,从而防止数据出现不一致的情况。

2、从JVM字节码指令来看,synchronized关键字包括monitor enter和monitor exit两个JVM指令,它能够在任何时候任何线程执行到monitor enter成功之前都必须从主内存中读取数据,而不是从缓存中读取数据,在monitor exit运行成功之后,共享变量被更新后的值必须刷入到主内存(这个在后面会再拾遗一遍)。monitor exit指令之前必定要有一个monitor enter指令的存在。

synchronized的用法

synchronized可用于修饰代码块和方法(包括对象方法和静态方法),但是不能修饰变量和class

与monitor关联的对象不能为空,比如,如下的代码就是不正确的。

private final Object mutex = null;

public void syncMethod(){
	synchronized(mutex){//会抛出异常,这个mutex不能为null
		
	}
}

This Monitor

其实针对这种操作,不同的书籍有不同的名称,我个人愿意简单称之为对象监视器。查看如下代码

/**
 * autor:liman
 * createtime:2020/6/11
 * comment:this monitor的示例
 */
@Slf4j
public class ObjectMonitor {

    public synchronized void method01() {
        log.info(Thread.currentThread().getName() + " enter to method01");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

//    public synchronized void method02() {
//        log.info(Thread.currentThread().getName()+" enter to method02");
//        try {
//            TimeUnit.SECONDS.sleep(10);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//    }
    
    //也可以采用这种方式
    public void method02(){
        synchronized (this){
        log.info(Thread.currentThread().getName() + " enter to method02");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    	}
	}

    public static void main(String[] args) {
        ObjectMonitor thisMonitor = new ObjectMonitor();
        new Thread(thisMonitor::method01,"T1").start();
        new Thread(thisMonitor::method02,"T2").start();
    }
}

上述代码中,线程T1和线程T2并不能做到异步执行,在JDK文档中有如下一段话

When a thread invokes a synchronized method, it automatically acquires the
intrinsic lock for that method's object and releases it when the method returns. The
lock release occurs even if the return was caused by an uncaught exception.

当一个线程调用synchronized修饰的方法,会自动去获取该方法所在对象的对象锁,当方法正常执行完成,或遇到异常返回才会释放该对象锁

Class Monitor

差不多的代码,看看静态方法的synchronized对其的影响

/**
 * autor:liman
 * createtime:2020/6/11
 * comment:class monitor实例
 */
@Slf4j
public class ClassMonitor {

    public static synchronized void method01(){
        log.info(Thread.currentThread().getName()+" enter to method01");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

//    public static synchronized void method02(){
//            log.info(Thread.currentThread().getName()+ " enter to method02");
//        try {
//            TimeUnit.SECONDS.sleep(10);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

//    }

    public static synchronized void method02(){
        synchronized (ClassMonitor.class) {
            log.info(Thread.currentThread().getName() + " enter to method02");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(ClassMonitor::method01,"T1").start();
        new Thread(ClassMonitor::method02,"T2").start();
    }

}

上述代码线程T1和T2依旧无法做到并行运行,JDK文档中对于这种情况,有如下描述

since a static method is associated with a class, not an object. In this case,
the thread acquires the intrinsic lock for the Class object associated with the class.
Thus access to class's static fields is controlled by a lock that's distinct
from the lock for any instance of the class.

一个静态方法与类关联,而不是一个对象,在这种情况下一个线程获取的是内部相关的类锁,这样一来,通过这个类的任何对象,访问这个类中的静态属性和静态代码快都会受到内部类锁的控制。

再来看一个实例

@Slf4j
public class ClassMonitorUpdate {

    static {
        synchronized (ClassMonitorUpdate.class){
            //try {
            //    log.info("static block thread name : "+Thread.currentThread().getName());
            //    Thread.sleep(1000L);
            //} catch (InterruptedException e) {
            //    e.printStackTrace();
            //}
        }
    }

    public static synchronized void method01(){
        log.info(Thread.currentThread().getName()+" enter to method01");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void method02(){
        synchronized (ClassMonitorUpdate.class) {
            log.info(Thread.currentThread().getName() + " enter to method02");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void method03(){
        log.info(Thread.currentThread().getName() + " enter to method03");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//另外一个Main类
public class ClassMonitorMain {

    public static void main(String[] args) {
        new Thread(ClassMonitorUpdate::method01,"T1").start();
        new Thread(ClassMonitorUpdate::method02,"T2").start();
        new Thread(ClassMonitorUpdate::method03,"T3").start();
    }

}

如果将注释的代码打开,则上述三个线程会出现串行化运行的情况。这个也不难理解,我们在调用method03的时候会初始化静态代码块,然后在静态代码块的时候,需要去获取类锁。

总结

其实这个之前总结过,这里只是在梳理一下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章