目录
相信不少同学在上完Java课后,对于线程同步部分的实战,都会感到不知其然。
比如上课做实验的时候,按着老师的实验指导书中的描述完成了多线程的同步操作,就感觉自己已经掌握这个知识点了,实际运用中再次手足无措,就像我一样。 这里提问一下:synchronized对方法修饰,在别处调用这个方法时,谁被锁定了呢?另外,在新建线程中使用synchronized(this){ }结构时,如:
void methodA() { new Thread(() -> { synchronized (this) { this.methodB(); } }).start(); }
这个被锁的this又是谁呢?
这篇博文来详细介绍一下线程同步中涉及synchronized修饰的两种用法:同步方法和同步代码块。
才不会说这篇是我对一个项目代码中的线程同步机制感到迷惑而搜资料写的笔记(
先开始介绍synchronized修饰符本身的特性:
修饰一个普通方法时,作用域是当前调用对象,即只要还没出方法的作用域,其他试图获取该对象的锁线程都将被阻塞。
这里容易误解的就是,只是尝试获取该对象锁的线程会被阻塞,并不影响其他线程不获取锁瞎操作,所以要在涉及同步量操作的所有地方采用同步方法(如加锁),否则引起线程安全问题几乎是必然的。
因为类的静态方法属于类,而不属于类的某个特定实例,所以对类的静态方法修饰直接作用于类本身,相当于synchronized(ClassA.class),即直接锁定整个类。这里有不少别人的笔记写着,直接作用于类的所有对象,我觉得存在歧义,因为正常情况下,除非采用工厂模式之类的方法,不然很难获取到所有对象的引用,并且这种表述也是不符合直觉的。
由于同步是一个高开销操作,上面讲的同步方法其实是同步代码块的一个语法糖,平时应尽量使用synchronized同步关键代码,而不是对整个方法同步,要尽可能减少同步的内容。
对成员方法修饰 -> synchronized(this)
对静态方法修饰 -> synchronized(ClassA.class)
自己全部测试了一遍,重新验证了猜想,目测没有什么不符合直觉的地方,另外,对单独信号量,如byte[]之类的加锁操作,如果不释放锁,其他线程会全部阻塞在获取锁的过程中,这里不单列出来。
本文前言中提到的问题,答案即为新建这个线程的实例本身,而不是这个被新建的线程类。
这里看到结果就容易理解了,每个对象都自己与一个锁相关联,类静态本身也与一个锁关联,任何尝试获取锁的方法才可能会引起阻塞。
修饰对象/其他线程 | 同实例 | 其他实例 | 类 | ||||||
---|---|---|---|---|---|---|---|---|---|
阻塞/不阻塞 | 成员变量 | 非同步方法 | 同步方法 | 成员变量 | 非同步方法 | 同步方法 | 静态变量 | 静态非同步方法 | 静态同步方法 |
this | 不阻塞 | 不阻塞 | 阻塞 | 不阻塞 | 不阻塞 | 不阻塞 | |||
类的成员方法 | 不阻塞 | 不阻塞 | 阻塞 | 不阻塞 | |||||
类.class | - | 阻塞 | |||||||
类的静态方法 | 阻塞 |
这里就不多介绍了,下面遇到了再详细写。
java-synchronized-method-lock-on-object-or-method - stackoverflow
what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods - stackoverflow
Java线程同步的7种方式 - cnblogs
关于Java的构造方法在类初始化和类实例化中的实质 - CSDN
Java中Synchronized的用法 - CSDN
Java多线程安全之构造函数 - CSDN
正确理解Thread Local的原理与适用场景 - 个人博客
理解Java中的ThreadLocal - 个人博客
Java中的四种引用类型(强、软、弱、虚) - 简书