Java并发编程(十一)-- Java中的锁详解

上一章我们已经简要的介绍了Java中的一些锁,本章我们就详细的来说说这些锁。

synchronized锁 

synchronized锁是什么?

synchronized是Java的一个关键字,它能够将代码块(方法)锁起来

  • 它使用起来是非常简单的,只要在代码块(方法)添加关键字synchronized,即可以实现同步的功能~
 public synchronized void test() {
        ....
 }
synchronized是一种互斥锁
  • 一次只能允许一个线程进入被锁住的代码块
  • synchronized是一种内置锁/监视器锁
  • Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用**对象的内置锁(监视器)**来将代码块(方法)锁定的!

synchronized用处是什么?

  • synchronized保证了线程的原子性。(被保护的代码块是一次被执行的,没有任何线程会同时访问)
  • synchronized还保证了可见性。(当执行完synchronized之后,修改后的变量对其他的线程是可见的)

Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。

synchronized的原理

我们首先来看一段synchronized修饰方法和代码块的代码:

public class Main {
    //修饰方法
    public synchronized void test1(){

    }

    
    public void test2(){
    // 修饰代码块
        synchronized (this){

        }
    }
} 

同步代码块

  • monitorenter和monitorexit指令实现的

同步方法(在这看不出来需要看JVM底层实现)

  • 方法修饰符上的ACC_SYNCHRONIZED实现。

synchronized底层是是通过monitor对象,对象有自己的对象头,存储了很多信息,其中一个信息标示是被哪个线程持有

synchronized如何使用

synchronized一般我们用来修饰三种东西:

  • 修饰普通方法
  • 修饰代码块
  • 修饰静态方法

修饰普通方法:

用的锁是对象(内置锁)

public class Test1{

    // 修饰普通方法
    public synchronized void test() {      
        // doSomething
    }
}

修饰代码块:

用的锁是对象(内置锁)--->this

public class Test2 {
    
    public  void test() {
        // 修饰代码块
        synchronized (this){
            // doSomething
        }
    }
}

当然了,我们使用synchronized修饰代码块时未必使用this,还可以使用其他的对象(随便一个对象都有一个内置锁)

修饰静态方法

获取到的是类锁(类的字节码文件对象)

public class Test3 {

    // 修饰静态方法代码块,静态方法属于类方法,它属于这个类,获取到的锁是属于类的锁(类的字节码文件对象)
    public synchronized void test() {
        // doSomething
    }
}

类锁与对象锁

synchronized修饰静态方法获取的是类锁(类的字节码文件对象),synchronized修饰普通方法或代码块获取的是对象锁。

  • 它俩是不冲突的,也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的
  • public class SynchoronizedDemo {
    
        //synchronized修饰非静态方法
        public synchronized void function() throws InterruptedException {
            for (int i = 0; i <3; i++) {
                Thread.sleep(1000);
                System.out.println("function running...");
            }
        }
        //synchronized修饰静态方法
        public static synchronized void staticFunction()
                throws InterruptedException {
            for (int i = 0; i < 3; i++) {
                Thread.sleep(1000);
                System.out.println("Static function running...");
            }
        }
    
        public static void main(String[] args) {
            final SynchoronizedDemo demo = new SynchoronizedDemo();
    
            // 创建线程执行静态方法
            Thread t1 = new Thread(() -> {
                try {
                    staticFunction();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    
            // 创建线程执行实例方法
            Thread t2 = new Thread(() -> {
                try {
                    demo.function();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            // 启动
            t1.start();
            t2.start();
        }
    }

结果证明:类锁和对象锁是不会冲突的

重入锁

我们来看下面的代码:

public class Widget {

    // 锁住了
    public synchronized void doSomething() {
        ...
    }
}

public class LoggingWidget extends Widget {

    // 锁住了
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling doSomething");
        super.doSomething();
    }
}
  1. 当线程A进入到LoggingWidget的doSomething()方法时,此时拿到了LoggingWidget实例对象的锁
  2. 随后在方法上又调用了父类Widget的doSomething()方法,它又是被synchronized修饰
  3. 那现在我们LoggingWidget实例对象的锁还没有释放,进入父类Widget的doSomething()方法还需要一把锁吗?

不需要的!

因为锁的持有者是“线程”,而不是“调用”。线程A已经是有了LoggingWidget实例对象的锁了,当再需要的时候可以继续**“开锁”**进去的!

这就是内置锁的可重入性

释放锁的时机

  1. 当方法(代码块)执行完毕后会自动释放锁,不需要做任何的操作。
  2. 当一个线程执行的代码出现异常时,其所持有的锁会自动释放
  • 不会由于异常导致出现死锁现象~

Lock显式锁

Lock显式锁简介

Lock显式锁是JDK1.5之后才有的,之前我们都是使用Synchronized锁来使线程安全的~

Lock显式锁是一个接口,我们来看看:

随便翻译一下他的顶部注释,看看是干嘛用的:

简单概括一下:

  • Lock方式来获取锁支持中断、超时不获取、是非阻塞的
  • 提高了语义化,哪里加锁,哪里解锁都得写出来
  • Lock显式锁可以给我们带来很好的灵活性,但同时我们必须手动释放锁
  • 支持Condition条件对象
  • 允许多个读线程同时访问共享资源

synchronized锁和Lock锁使用哪个

前面说了,Lock显式锁给我们的程序带来了很多的灵活性,很多特性都是Synchronized锁没有的。那Synchronized锁有没有存在的必要??

必须是有的!!Lock锁在刚出来的时候很多性能方面都比Synchronized锁要好,但是从JDK1.6开始Synchronized锁就做了各种的优化

所以,到现在Lock锁和Synchronized锁的性能其实差别不是很大!而Synchronized锁用起来又特别简单。Lock锁还得顾忌到它的特性,要手动释放锁才行(如果忘了释放,这就是一个隐患)

所以说,我们绝大部分时候还是会使用Synchronized锁,用到了Lock锁提及的特性,带来的灵活性才会考虑使用Lock显式锁~

总得来说:

  • synchronized好用,简单,性能不差
  • 没有使用到Lock显式锁的特性就不要使用Lock锁了。

参考资料:

  • 《Java核心技术卷一》
  • 《Java并发编程实战》
  • 《Java并发编程的艺术》

猜你喜欢

转载自www.cnblogs.com/JackpotHan/p/9687826.html