回顾前面:
只有光头才能变强!
本文章主要讲的是Java多线程 加锁机制 ,有两种:
不得不唠叨几句:
其实 都比较坑 ,如果能先系统讲了Synchronized锁机制,接着讲显式Lock锁机制,那就很容易理解了。也不需要跨那么多章节。
那么接下来我们就开始吧~
synchronized是Java的一个 关键字 ,它能够将 代码块(方法)锁起来
public synchronized void test() { // 关注公众号Java3y // doSomething }
synchronized是一种 互斥锁
synchronized是一种 内置锁/监视器锁
Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了 对变量操作的原子性和其他线程对变量的可见性 ,从而确保了并发情况下的线程安全。
我们首先来看一段synchronized修饰方法和代码块的代码:
public class Main { //修饰方法 public synchronized void test1(){ } public void test2(){ // 修饰代码块 synchronized (this){ } } }
来反编译看一下:
同步代码块:
同步方法(在这看不出来需要看JVM底层实现)
synchronized底层是是 通过monitor对象,对象有自己的对象头,存储了很多信息,其中一个信息标示是被哪个线程持有 。
具体可参考:
synchronized一般我们用来修饰三种东西:
用的锁是 Java3y对象(内置锁)
public class Java3y { // 修饰普通方法,此时用的锁是Java3y对象(内置锁) public synchronized void test() { // 关注公众号Java3y // doSomething } }
用的锁是 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是不可行的
使用客户端锁,会将当前的实现与原本的list耦合了:
书上给出的办法是使用 组合 的方式(也就是装饰器模式)
获取到的是 类锁(类的字节码文件对象) :Java3y.class
public class Java3y { // 修饰静态方法代码块,静态方法属于类方法,它属于这个类,获取到的锁是属于类的锁(类的字节码文件对象)-->Java3y.class public synchronized void test() { // 关注公众号Java3y // 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(); } }
doSomething()
方法时, 此时拿到了LoggingWidget实例对象的锁 。 doSomething()
方法,它 又是被synchronized修饰 。 doSomething()
方法 还需要一把锁吗? 不需要的!
因为 锁的持有者是“线程”,而不是“调用” 。线程A已经是有了LoggingWidget实例对象的锁了,当再需要的时候可以继续 “开锁” 进去的!
这就是内置锁的 可重入性 。
Lock显式锁是JDK1.5之后才有的,之前我们都是使用Synchronized锁来使线程安全的~
Lock显式锁是一个接口,我们来看看:
随便翻译一下他的顶部注释,看看是干嘛用的:
可以 简单概括 一下:
前面说了,Lock显式锁给我们的程序带来了很多的灵活性,很多特性都是Synchronized锁没有的。那Synchronized锁有没有存在的必要??
必须是有的!!Lock锁在刚出来的时候很多性能方面都比Synchronized锁要好,但是从JDK1.6开始Synchronized锁就做了各种的优化(毕竟亲儿子,牛逼)
所以,到现在Lock锁和Synchronized锁的性能其实 差别不是很大 !而Synchronized锁用起来又特别简单。 Lock锁还得顾忌到它的特性,要手动释放锁才行 (如果忘了释放,这就是一个隐患)
所以说,我们 绝大部分时候还是会使用Synchronized锁 ,用到了Lock锁提及的特性,带来的灵活性才会考虑使用Lock显式锁~
公平锁理解起来非常简单:
非公平锁就是:
Lock和synchronize都是 默认使用非公平锁的 。如果不是必要的情况下,不要使用公平锁
本文讲了synchronized内置锁和简单描述了一下Lock显式锁,总得来说:
Lock锁这里只是介绍了一些些, 明天会详解它的相关子类和需要注意的地方,敬请期待 ~
之前在学习操作系统的时候根据《计算机操作系统-汤小丹》这本书也做了一点点笔记,都是比较 浅显的知识点 。或许对大家有帮助
参考资料:
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以 关注微信公众号:Java3y 。为了大家方便,刚新建了一下 qq群:742919422 ,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友