Java并发基础学习(七)——简述synchronized关键字

前言

前几篇博客算是总结完成了Java中多线程的基础内容,但是基础内容中缺失了synchronized关键字的角色,这里还是补充介绍一下,到这里Java多线程中基础中的基础算是完成了,后面会简单介绍一下JMM之后,开始学习总结各种JUC的工具类。

如果对Java多线程稍微有了解的,都应该知道synchronized关键字,这个关键字能够保证同一时刻最多只有一个线程执行其保护的代码,以达到保证并发安全的效果。

对象锁&类锁

针对synchronized这个关键字可以修饰方法,属性,代码块,从其基础使用层面来看,其有两种大的方式,一种是类锁,一种是对象锁。从字面意思理解并不能完全明白其含义,还是通过实例吧

对象锁

对象锁的形式包括方法锁(这个时候锁对象为this)和同步代码块(自己指定锁对象)两种形式

1、同步代码块

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:对象锁-同步代码块
 */
public class ObjectLockBlock implements Runnable {
    
    

    private static ObjectLockBlock instance = new ObjectLockBlock();

    Object lock = new Object();

    public static void main(String[] args) {
    
    
        Thread threadOne = new Thread(instance);
        Thread threadTwo = new Thread(instance);
        threadOne.start();
        threadTwo.start();

        //循环等待两个线程运行完毕
        while(threadOne.isAlive() || threadTwo.isAlive()){
    
    

        }

        System.out.println("all thread finished");
    }

    @Override
    public void run() {
    
    
        //synchronize代码块,默认对象锁就是当前这个对象,也可以指定任意对象
        synchronized (this) {
    
    
            System.out.println("对象锁的代码块,当前线程为:" + Thread.currentThread().getName());
            try {
    
    
                Thread.sleep(3000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }
}

2、修饰方法

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:对象锁示例2 方法锁
 */
public class ObjectLockMethod implements Runnable {
    
    
    private static ObjectLockMethod instance = new ObjectLockMethod();
    Object lock = new Object();

    @Override
    public void run() {
    
    
        method();
    }

    //这里直接修饰对象普通方法,锁对象依旧是当前对象
    public synchronized void method(){
    
    
        System.out.println("对象锁的方法修饰,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }

    public static void main(String[] args) {
    
    
        Thread threadOne = new Thread(instance);
        Thread threadTwo = new Thread(instance);
        threadOne.start();
        threadTwo.start();

        //简单粗暴的等待线程运行的方法
        while(threadOne.isAlive() || threadTwo.isAlive()){
    
    

        }

        System.out.println("all thread finished");
    }
}

类锁

与对象锁相对应,synchronized修饰的静态方法,和指定类对象为锁对象的方式,这种方式下锁对象为Class对象

1、synchronized修饰静态方法

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:类锁的第一种形式,static形式
 */
public class ClassLockStatic implements Runnable{
    
    

    private static ClassLockStatic instance1 = new ClassLockStatic();
    private static ClassLockStatic instance2 = new ClassLockStatic();
    @Override
    public void run() {
    
    
        method();
    }

    //修饰静态方法,当前的锁对象为 ClassLockStatic.class
    public synchronized static void method(){
    
    
        System.out.println("类锁的方法修饰,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }

    public static void main(String[] args) {
    
    
        Thread threadOne = new Thread(instance1);
        Thread threadTwo = new Thread(instance2);
        threadOne.start();
        threadTwo.start();

        while(threadOne.isAlive() || threadTwo.isAlive()){
    
    

        }

        System.out.println("all thread finished");
    }
}

2、指定类对象为锁对象

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:类锁的第二种形式,synchronized(*.class)
 */
public class ClassLockStaticBlock implements Runnable{
    
    

    private static ClassLockStaticBlock instance1 = new ClassLockStaticBlock();
    private static ClassLockStaticBlock instance2 = new ClassLockStaticBlock();
    @Override
    public void run() {
    
    
        method();
    }

    public void method(){
    
    
        //这里直接指定类锁
        synchronized (ClassLockStaticBlock.class) {
    
    
            System.out.println("类锁的方法修饰,当前线程为:" + Thread.currentThread().getName());
            try {
    
    
                Thread.sleep(3000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }

    public static void main(String[] args) {
    
    
        Thread threadOne = new Thread(instance1);
        Thread threadTwo = new Thread(instance2);
        threadOne.start();
        threadTwo.start();

        //这里是一种简单的等待线程运行结束的方式【不推荐】
        while(threadOne.isAlive() || threadTwo.isAlive()){
    
    

        }

        System.out.println("all thread finished");
    }
}

上述四种情况下,线程都能正常排队运行,这里就不贴出运行结果了。

一些复杂情况

1、对象锁情况下,如果多个线程访问不同的同步方法,依旧会同步运行

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:同时访问不同的同步方法
 */
public class SynchronizedMethodAndYesSame implements Runnable{
    
    

    private static SynchronizedMethodAndYesSame instance = new SynchronizedMethodAndYesSame();

    @Override
    public void run() {
    
    
        //不同线程调用的方法不同
        String threadName = Thread.currentThread().getName();
        if("Thread-0".equals(threadName)){
    
    
            syncMethod();
        }else{
    
    
            otherSyncMethod();
        }

    }

    //同步方法1
    public synchronized void syncMethod(){
    
    
        System.out.println("这个是同步加锁的方法,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }

    //同步方法2
    public synchronized void otherSyncMethod(){
    
    
        System.out.println("这个是【另一个】同步加锁的方法,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }


    public static void main(String[] args) {
    
    
        Thread threadOne = new Thread(instance);
        Thread threadTwo = new Thread(instance);
        threadOne.start();
        threadTwo.start();

        while(threadOne.isAlive() || threadTwo.isAlive()){
    
    

        }
        System.out.println("all thread finished");
    }
}

两个线程依旧能同步运行

请添加图片描述

这里需要补充一下,这种情况下两个线程依旧会出现同步运行的情况,但是如果其中一个方法抛出异常了,是会释放锁的,如果syncMethod方法改成如下代码

public synchronized void syncMethod(){
    
    
    System.out.println("这个是同步加锁的方法,当前线程为:" + Thread.currentThread().getName());
    try {
    
    
        Thread.sleep(3000);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
	//这里抛出异常
    throw new RuntimeException("测试异常,释放锁");
}

这个异常的抛出,是会释放锁的,锁释放之后,会让另一个线程进入其他同步方法运行

2、对象锁情况下,一个方法加锁,一个方法不加锁,不同的线程调用不同的方法,并不会出现同步的情况

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:同时访问同步方法和非同步方法
 */
public class SynchronizedMethodAndNoSame implements Runnable{
    
    

    private static SynchronizedMethodAndNoSame instance = new SynchronizedMethodAndNoSame();

    @Override
    public void run() {
    
    
        //不同线程调用不用的方法
        String threadName = Thread.currentThread().getName();
        if("Thread-0".equals(threadName)){
    
    
            syncMethod();
        }else{
    
    
            noSyncMethod();
        }

    }

	//加锁的同步方法
    public synchronized void syncMethod(){
    
    
        System.out.println("这个是同步加锁的方法,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }

	//没有加锁
    public void noSyncMethod(){
    
    
        System.out.println("这个是没有加锁的方法,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }

    public static void main(String[] args) {
    
    
        Thread threadOne = new Thread(instance);
        Thread threadTwo = new Thread(instance);
        threadOne.start();
        threadTwo.start();

        while(threadOne.isAlive() || threadTwo.isAlive()){
    
    

        }

        System.out.println("all thread finished");
    }
}

运行结果:

请添加图片描述

这种情况,并不会出现同步互斥的情况,而是出现并行运行。

3、类锁和方法锁,并不是同一个,二者修饰的方法被不同线程调用,并不会出现同步运行的情况。

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:同时访问对象锁的同步方法和类锁的同步方法
 */
public class StaticSynchronizedMethodAndNormalSame implements Runnable{
    
    

    private static StaticSynchronizedMethodAndNormalSame instance = new StaticSynchronizedMethodAndNormalSame();

    @Override
    public void run() {
    
    
        String threadName = Thread.currentThread().getName();
        //不同的线程访问不同的方法
        if("Thread-0".equals(threadName)){
    
    
            syncMethod();
        }else{
    
    
            noSyncMethod();
        }

    }

    //类锁的同步方法
    public static synchronized void syncMethod(){
    
    
        System.out.println("这个是同步加锁的方法【静态】,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }

    //对象锁的同步方法
    public synchronized void noSyncMethod(){
    
    
        System.out.println("这个是同步加锁的方法【非静态】,当前线程为:" + Thread.currentThread().getName());
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }


    public static void main(String[] args) {
    
    
        Thread threadOne = new Thread(instance);
        Thread threadTwo = new Thread(instance);
        threadOne.start();
        threadTwo.start();

        while(threadOne.isAlive() || threadTwo.isAlive()){
    
    

        }

        System.out.println("all thread finished");
    }

}

运行结果:

请添加图片描述

synchronized原理简述

可重入与不可中断

在简述synchronized原理之前,先说下synchronized的性质,synchronized锁加的锁是一个可重入的锁(另一个是ReentrantLock),同时也是不可中断的锁

所谓的可重入,指的是同一线程的外层函数获取锁之后,内层函数可以直接再次获取该锁。

所谓的不可中断,指的是一旦这个锁被其他线程获取了,如果当前线程想继续获取该锁,只能选择等待或者阻塞,直到其他线程释放这个锁,如果其他线程永远不释放锁,则当前线程就只能等待

关于synchronized可重入的实例

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:synchronized可重入
 */
public class SynchronizedCanReEnter {
    
    

    int a = 0;

    public synchronized void method01(){
    
    
        System.out.println("这里是method01,a="+a);
        if(0==a){
    
    
            a++;
            method01();
        }
        //外层method01里头调用了method02同步方法
        method02();
    }

    public synchronized void method02(){
    
    
        System.out.println("这里是method02");
    }

    public static void main(String[] args) {
    
    
        SynchronizedCanReEnter synchronizedCanReEnter = new SynchronizedCanReEnter();
        synchronizedCanReEnter.method01();
    }
}

并不会出现同步运行的情况,因为synchronized是可重入的锁

请添加图片描述

原理简述

通过一段代码,编译成字节码看看其原理

/**
 * autor:liman
 * createtime:2021-10-17
 * comment:
 */
public class SyncDeCompileDemo {
    
    

    private Object object = new Object();

    public void syncMethod(){
    
    
        synchronized (object){
    
    

        }
    }

}

进入到这个类的文件目录下之后,通过如下两行命令得到对应的字节码

javac SyncDeCompileDemo.java
javap -verbose SyncDeCompileDemo.class

对应字节码如下

  public void syncMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #3                  // Field object:Ljava/lang/Object;

         4: dup
         5: astore_1
         6: monitorenter ##获取锁的指令
         7: aload_1
         8: monitorexit ##正常释放
         9: goto          17
        12: astore_2
        13: aload_1
        14: monitorexit ##抛出异常释放
        15: aload_2
        16: athrow
        17: return
      Exception table:
         from    to  target type
             7     9    12   any
            12    15    12   any
      LineNumberTable:
        line 13: 0
        line 15: 7
        line 16: 17
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class com/learn/concurrency/foundataion/synchronizelearn/Sy
ncDeCompileDemo, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}

比较容易看到,synchronized是在需要运行的代码块上加上了monitorentermonitorexit的指令。之前的实例已经证明,synchronized在出现异常的时候,是会释放锁的,因此其一条monitorenter对应两条monitorexit指令。

同时需要说明的是,这个monitorenter和monitorexit指令虽然无法保证原子性,但是在Java的内存模型中,这个指令是保证了变量数据的可见性的。

synchronized缺陷

关于synchronized关键字的缺陷,其实也有一些,无非就是效率低,不够灵活(相对读写锁而言),同时无法保证数据修改的原子性,这个确实没法保证,毕竟monitorenter和monitorexit之间可相隔那么多指令。但是Java在不断的迭代之后,对synchronized也做了很多优化,其中锁膨胀机制的引入使得synchronized的效率也不是那么不堪了。

总结

关于synchronized的内容,先简单介绍上面的内容,后面在学习Java内存模型的时候,还会学习synchronized的内容,后续再进行补充

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/120895564