转载

Java锁机制了解一下

前言

回顾前面:

  • 多线程三分钟就可以入个门了!
  • Thread源码剖析
  • 多线程基础必要知识点!看了学习多线程事半功倍

只有光头才能变强!

本文章主要讲的是Java多线程 加锁机制 ,有两种:

  • Synchronized
  • 显式Lock

不得不唠叨几句:

  • 在《Java核心技术卷 一》是先讲比较难的显式Lock,而再讲的是比较简单的Synchronized
  • 而《Java并发编程实战》在前4章零散地讲解了Synchronized,将显式Lock放到了13章

其实 都比较坑 ,如果能先系统讲了Synchronized锁机制,接着讲显式Lock锁机制,那就很容易理解了。也不需要跨那么多章节。

那么接下来我们就开始吧~

一、synchronized锁

1.1synchronized锁是什么?

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

  • 它使用起来是非常简单的,只要在代码块(方法)添加关键字synchronized,即可以 实现同步 的功能~
    public synchronized void test() {
        // 关注公众号Java3y
        // doSomething
    }

synchronized是一种 互斥锁

  • 一次只能允许一个线程进入被锁住的代码块

synchronized是一种 内置锁/监视器锁

  • Java中 每个对象 都有一个 内置锁(监视器,也可以理解成锁标记) ,而synchronized就是使用 对象的内置锁(监视器) 来将代码块(方法)锁定的!

1.2synchronized用处是什么?

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

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

1.3synchronized的原理

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

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

    }

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

        }
    }
}

来反编译看一下:

Java锁机制了解一下

同步代码块:

  • monitorenter和monitorexit指令实现的

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

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

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

具体可参考:

  • https://blog.csdn.net/chenssy/article/details/54883355
  • https://blog.csdn.net/u012465296/article/details/53022317

1.4synchronized如何使用

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

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

1.4.1修饰普通方法:

用的锁是 Java3y对象(内置锁)

public class Java3y {


    // 修饰普通方法,此时用的锁是Java3y对象(内置锁)
    public synchronized void test() {
        // 关注公众号Java3y
        // doSomething
    }

}

1.4.2修饰代码块:

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

public class Java3y {
    
    public  void test() {
        
        // 修饰代码块,此时用的锁是Java3y对象(内置锁)--->this
        synchronized (this){
            // 关注公众号Java3y
            // doSomething
        }
    }
}

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

所以,我们可以这样干:

public class Java3y {


    // 使用object作为锁(任何对象都有对应的锁标记,object也不例外)
    private Object object = new Object();


    public void test() {

        // 修饰代码块,此时用的锁是自己创建的锁Object
        synchronized (object){
            // 关注公众号Java3y
            // doSomething
        }
    }

}

上面那种方式(随便使用一个对象作为锁)在书上称之为--> 客户端锁 ,这是 不建议使用的

书上想要实现的功能是:给ArrayList添加一个 putIfAbsent() ,这需要是线程安全的。

假定直接添加synchronized是不可行的

Java锁机制了解一下

使用客户端锁,会将当前的实现与原本的list耦合了:

Java锁机制了解一下

书上给出的办法是使用 组合 的方式(也就是装饰器模式)

Java锁机制了解一下

1.4.3修饰静态方法

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

public class Java3y {

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

        // 关注公众号Java3y
        // doSomething
    }
}

1.4.4类锁与对象锁

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();
    }
}

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

Java锁机制了解一下

1.5重入锁

我们来看下面的代码:

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.6释放锁的时机

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

二、Lock显式锁

2.1Lock显式锁简单介绍

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

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

Java锁机制了解一下

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

Java锁机制了解一下

可以 简单概括 一下:

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

2.2synchronized锁和Lock锁使用哪个

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

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

  • 优化操作:适应自旋锁,锁消除,锁粗化,轻量级锁,偏向锁。
  • 详情可参考: https://blog.csdn.net/chenssy/article/details/54883355

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

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

2.3公平锁

公平锁理解起来非常简单:

  • 线程将按照它们 发出请求的顺序来获取锁

非公平锁就是:

  • 线程发出请求的时可以 “插队” 获取锁

Lock和synchronize都是 默认使用非公平锁的 。如果不是必要的情况下,不要使用公平锁

  • 公平锁会来带一些性能的消耗的

四、最后

本文讲了synchronized内置锁和简单描述了一下Lock显式锁,总得来说:

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

Lock锁这里只是介绍了一些些, 明天会详解它的相关子类和需要注意的地方,敬请期待 ~

之前在学习操作系统的时候根据《计算机操作系统-汤小丹》这本书也做了一点点笔记,都是比较 浅显的知识点 。或许对大家有帮助

  • 操作系统第一篇【引论】
  • 操作系统第二篇【进程管理】
  • 操作系统第三篇【线程】
  • 操作系统第四篇【处理机调度】
  • 操作系统第五篇【死锁】
  • 操作系统第六篇【存储器管理】
  • 操作系统第七篇【设备管理】

参考资料:

  • 《Java核心技术卷一》
  • 《Java并发编程实战》
  • 《计算机操作系统-汤小丹》

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以 关注微信公众号:Java3y 。为了大家方便,刚新建了一下 qq群:742919422 ,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友

原文  http://zhongfucheng.bitcron.com/post/duo-xian-cheng/javasuo-ji-zhi-liao-jie-yi-xia
正文到此结束
Loading...