# Synchronized
# 1. 简介
# 1.1 作用
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。
# 1.2 地位
- synchronized 是 Java 的关键字,被 Java 语言原生支持;
- 是最基本的互斥同步手段;
# 2. 两种用法
# 2.1 对象锁
# [同步代码块]
/**
* 对象锁 —— 同步代码块
*
* @author Hedon Wang
* @create 2021-02-22 9:01 PM
*/
public class SynchronizedObjectLockOne implements Runnable{
//对象锁,加 static 确保每一个实例对象都是用的同一个对象作为锁
static SynchronizedObjectLockOne instance = new SynchronizedObjectLockOne();
static int a = 0;
@Override
public void run() {
//同步代码块
synchronized (instance){
System.out.println(Thread.currentThread().getName()+"start.....");
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+"finished....");
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(new SynchronizedObjectLockOne());
Thread thread2 = new Thread(new SynchronizedObjectLockOne());
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("a==" + a); //a==20000
System.out.println("finished......");
}
}
# [同步方法]
/**
* 对象锁 —— 同步方法
*
* @author Hedon Wang
* @create 2021-02-22 9:01 PM
*/
public class SynchronizedObjectLockTwo implements Runnable{
//实例对象,必须为同一个实例对象,多个线程中的同步方法才是加的同一个对象锁
static SynchronizedObjectLockTwo instance = new SynchronizedObjectLockTwo();
static int a = 0;
@Override
public void run() {
plusA();
}
//同步方法
public synchronized void plusA(){
System.out.println(Thread.currentThread().getName()+"start.....");
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+"finished....");
}
public static void main(String[] args) {
//这里必须是同一个 instance,才会是同一把锁
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("a==" + a); //20000
System.out.println("finished......");
}
}
# 2.2 类锁
# [静态方法锁]
/**
* 类锁:静态方法锁
*
* @author Hedon Wang
* @create 2021-02-22 9:14 PM
*/
public class SynchronizedClassLockOne implements Runnable{
static int a = 0;
@Override
public void run() {
plusA();
}
//静态方法锁
public static synchronized void plusA(){
System.out.println(Thread.currentThread().getName()+"...started....");
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+"...finished....");
}
public static void main(String[] args) {
//不同的实例
Thread t1 = new Thread(new SynchronizedClassLockOne());
Thread t2 = new Thread(new SynchronizedClassLockOne());
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("a == " + a); //a==20000
System.out.println("finished......");
}
}
# [Class 类作锁]
/**
* 类锁:Class类作锁
*
* @author Hedon Wang
* @create 2021-02-22 9:14 PM
*/
public class SynchronizedClassLockTwo implements Runnable{
static int a = 0;
@Override
public void run() {
//Class类作锁
synchronized (SynchronizedObjectLockTwo.class){
System.out.println(Thread.currentThread().getName()+"...started....");
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+"...finished....");
}
}
public static void main(String[] args) {
//不同的实例
Thread t1 = new Thread(new SynchronizedClassLockTwo());
Thread t2 = new Thread(new SynchronizedClassLockTwo());
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("a == " + a); //a==20000
System.out.println("finished......");
}
}
# 3. 八种情况
核心思想:
- 一把锁同时只能被一个线程锁获取,其他没用获得该锁的线程只能等待;
- 关键是看是不是同一把锁;
- 无论方法是否正常执行,JVM 都会释放锁。
# ① 两个线程同时访问一个对象的同步方法
锁生效,先 Thread0 后 Thread1。
/**
* ① 两个线程同时访问一个对象的同步方法
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case1 implements Runnable {
static Case1 instance = new Case1();
static int a = 0;
@Override
public void run() {
synchronized (instance){
System.out.println(Thread.currentThread().getName()+"...started....");
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+"...finished....");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("a==" + a);
System.out.println("finished.......");
}
}
结果:
Thread-0...started....
Thread-0...finished....
Thread-1...started....
Thread-1...finished....
a==20000
finished.......
# ② 两个线程同时访问两个对象的同步方法
看用的是不是同一个锁,如果是 this 的话,那么锁不生效,两个线程同时进行。
/**
* ② 两个线程同时访问两个对象的同步方法
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case2 implements Runnable {
static Case2 instance = new Case2();
static int a = 0;
@Override
public void run() {
//如果是 instance,那么还是同一把锁,a==20000
//synchronized (instance){
//如果是 this,那么不同对象就是使用不同的锁 a!=20000
synchronized (this){
System.out.println(Thread.currentThread().getName()+"...started....");
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+"...finished....");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Case2());
Thread t2 = new Thread(new Case2());
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("a==" + a);
System.out.println("finished.......");
}
}
结果:
Thread-1...started....
Thread-0...started....
Thread-1...finished....
Thread-0...finished....
a==11529
finished.......
# ③ 两个线程同时访问 synchronized 静态方法
锁生效,先 Thread0 后 Thread1。
package sychro;
/**
* ③ 两个线程同时访问 synchronized 静态方法
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case3 implements Runnable {
static int a = 0;
@Override
public void run() {
plusA();
}
public static synchronized void plusA(){
System.out.println(Thread.currentThread().getName()+"...started....");
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println(Thread.currentThread().getName()+"...finished....");
}
public static void main(String[] args) {
Thread t1 = new Thread(new Case3());
Thread t2 = new Thread(new Case3());
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("a==" + a);
System.out.println("finished.......");
}
}
结果:
Thread-0...started....
Thread-0...finished....
Thread-1...started....
Thread-1...finished....
a==20000
finished.......
# ④ 两个线程同时访问同步与非同步方法
锁无效,非同步方法不受到影响,两个线程并行。
/**
* ④ 两个线程同时访问同步与非同步方法
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case4 implements Runnable {
static Case4 instance = new Case4();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
methodA();
}else{
methodB();
}
}
//同步方法
public synchronized void methodA(){
System.out.println(Thread.currentThread().getName()+"...同步方法 started....");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...同步方法 finished....");
}
//非同步方法
public void methodB(){
System.out.println(Thread.currentThread().getName()+"...非同步方法 started....");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...非同步方法 finished....");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("finished.......");
}
}
结果:
Thread-1...非同步方法 started....
Thread-0...同步方法 started....
Thread-0...同步方法 finished....
Thread-1...非同步方法 finished....
finished.......
# ⑤ 两个线程同时访问同一个对象的不同普通同步方法
锁生效,普通同步方法默认使用 this 作为锁,先 Thread0 后 Thread1。
/**
* ⑤ 两个线程同时访问同一个对象的不同普通同步方法
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case5 implements Runnable {
static Case5 instance = new Case5();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
methodA();
}else{
methodB();
}
}
//第一个同步方法
public synchronized void methodA(){
System.out.println(Thread.currentThread().getName()+"...第一个同步方法 started....");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...第一个同步方法 finished....");
}
//第二个同步方法
public synchronized void methodB(){
System.out.println(Thread.currentThread().getName()+"...第二个同步方法 started....");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...第二个同步方法 finished....");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("finished.......");
}
}
结果:
Thread-0...第一个同步方法 started....
Thread-0...第一个同步方法 finished....
Thread-1...第二个同步方法 started....
Thread-1...第二个同步方法 finished....
finished.......
# ⑥ 两个线程同时访问静态 synchronized 方法和非静态 synchronized 方法
锁无效,一个使用的是 instance 对象锁,一个使用的是 Class 类锁。
/**
* ⑥ 两个线程同时访问静态 synchronized 方法和非静态 synchronized 方法
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case6 implements Runnable {
static Case6 instance = new Case6();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
methodA();
}else{
methodB();
}
}
//静态同步方法
public static synchronized void methodA(){
System.out.println(Thread.currentThread().getName()+"...静态同步方法 started....");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...静态同步方法 finished....");
}
//非静态同步方法
public synchronized void methodB(){
System.out.println(Thread.currentThread().getName()+"...非静态同步方法 started....");
try {
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"...非静态同步方法 finished....");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("finished.......");
}
}
结果:
Thread-0...静态同步方法 started....
Thread-1...非静态同步方法 started....
Thread-0...静态同步方法 finished....
Thread-1...非静态同步方法 finished....
finished.......
# ⑦ 方法抛异常后,会释放锁吗?
synchronized 会。Lock 不会,需要手动在 finally 里面释放。
/**
* ⑦ 方法抛异常后,会释放锁吗?
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case7 implements Runnable {
static Case7 instance = new Case7();
@Override
public void run() {
synchronized (instance){
System.out.println(Thread.currentThread().getName()+"进入同步代码块了");
try{
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
int i=1/0; //故意抛出异常
System.out.println(Thread.currentThread().getName()+"释放锁");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("finished.......");
}
}
结果:
Thread-0进入同步代码块了
Thread-1进入同步代码块了
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at sychro.Case7.run(Case7.java:24)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-1" java.lang.ArithmeticException: / by zero
at sychro.Case7.run(Case7.java:24)
at java.lang.Thread.run(Thread.java:748)
finished.......
# ⑧ 两个线程同时访问一个对象中含有非同步方法的同步方法
非同步方法不会被上锁。
/**
* ⑧ 两个线程同时访问一个对象中含有非同步方法的同步方法
*
* @author Hedon Wang
* @create 2021-02-22 9:37 PM
*/
public class Case8 implements Runnable {
static Case8 instance = new Case8();
static int b = 0;
@Override
public void run() {
//一个线程去访问 methodA(),methodA() 中还有 methodB()
if (Thread.currentThread().getName().equals("Thread-0")){
methodA();
}else{
//另一个线程去访问 methodB()
methodB();
}
}
//同步方法
public synchronized void methodA(){
System.out.println(Thread.currentThread().getName()+"...同步方法 started....");
//同步方法中含有非同步方法
methodB();
System.out.println(Thread.currentThread().getName()+"...同步方法 finished....");
}
//非同步方法
public void methodB(){
System.out.println(Thread.currentThread().getName()+"...非同步方法 started....");
for (int i = 0; i < 10000; i++) {
b++;
}
System.out.println(Thread.currentThread().getName()+"...非同步方法 finished....");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){};
System.out.println("b==" + b);
System.out.println("finished.......");
}
}
结果:
Thread-0...同步方法 started....
Thread-1...非同步方法 started....
Thread-0...非同步方法 started....
Thread-0...非同步方法 finished....
Thread-1...非同步方法 finished....
Thread-0...同步方法 finished....
b==10282
finished.......
# 4. 性质
# 4.1 可重入
同一线程的外层函数获得锁之前,内层函数可以直接再次获取该锁。
好处:避免死锁、提升封装性。
粒度:线程而非调用。(pthread 是调用粒度的)
# [递归调用本方法]
package sychro;
/**
* @author Hedon Wang
* @create 2021-02-22 11:01 PM
*/
public class Reentrancy1 implements Runnable {
int a = 0;
@Override
public void run() {
plusA();
}
public synchronized void plusA(){
System.out.println(Thread.currentThread().getName() + " " + a);
if (a == 0){
a++;
//递归调用本身,能调用说明 —— 可重入
plusA();
}
}
public static void main(String[] args) {
new Thread(new Reentrancy1()).start();
}
}
# [递归调用本类不同同步方法]
/**
* @author Hedon Wang
* @create 2021-02-22 11:01 PM
*/
public class Reentrancy2 implements Runnable {
int a = 0;
int b = 0;
@Override
public void run() {
plusA();
}
public synchronized void plusA(){
if (a == 0){
a++;
System.out.println(Thread.currentThread().getName() + " a == " + a);
//递归调用本对象的不同方法,能调用说明 —— 可重入
plusA2();
}
}
public synchronized void plusA2(){
if (b == 0){
b++;
System.out.println(Thread.currentThread().getName() + " b == " + b);
}
}
public static void main(String[] args) {
new Thread(new Reentrancy2()).start();
}
}
# [调用不同类的同步方法]
package sychro;
/**
* @author Hedon Wang
* @create 2021-02-22 11:01 PM
*/
public class Reentrancy3 {
public synchronized void method(){
System.out.println("我是父类...");
}
public static void main(String[] args) {
Reentrancy3Child reentrancy3Child = new Reentrancy3Child();
reentrancy3Child.methodChild();
}
}
class Reentrancy3Child extends Reentrancy3{
public synchronized void methodChild(){
System.out.println("我是子类...");
super.method();
}
}
结果:
我是子类...
我是父类...
# 4.2 不可中断
一旦这个锁已经被一个线程获得了,其他线程只能等待这个锁被释放。
相比之前,相比之下,Lock 类可以拥有中断的能力:
第一点,如果我觉得我等的时间太长了, 有权中断现在已经获取到锁的线程的执行;
第二点,如果我觉得我等待的时间太长了不想再等了,也可以退出。
# 5. 原理
# 5.1 加锁/释放锁
# monitorenter
- 每个对象都与一个 monitor 相关联。
- 当且仅当拥有所有者时(被拥有),monitor 才会被锁定。执行到 monitorenter 指令的线程,会尝试去获得对应的 monitor,如下:
- 每个对象维护着一个记录着被锁次数的计数器,对象未被锁定时,该计数器为 0。线程进入 monitor(执行 monitorenter 指令)时,会把计数器设置为1。
- 当同一个线程再次获得该对象的锁的时候,计数器再次自增。(所以只有一次 monitorenter 而可能有多个 monitorexit)
- 当其他线程想获得该 monitor 的时候,就会阻塞,直到计数器为 0 才能成功。
# monitorexit
- monitor 的拥有者线程才能执行 monitorexit 指令。
- 线程执行 monitorexit 指令,就会让 monitor 的计数器减一。 如果计数器为 0,表明该线程不再拥有 monitor。其他线程就允许尝试去获得该 monitor 了。
# 5.2 可重入
- JVM负责跟踪对象被加锁的次数。
- 线程第一次给对象加锁的时候,计数变为 1。每当这个相同的线程在此对象上再次获得锁时,计数会递增。
- 每当任务离开时,计数递减,当计数为 0 的时候,锁被完全释放。
# 5.3 可见性
synchronized 关键字保证线程在释放锁的时候,JVM 就帮助我们将该线程修改的东西写入到内存当中,并且保证了其他线程在锁被释放之前不可以访问该变量,从而保证了每一个线程访问该变量的时候都是正确的。
# 6. 缺陷
- 效率低
- 锁的释放情况少
- 执行完毕
- 异常抛出
- 在 synchronized 代码被执行期间,线程可以调用对象的 wait() 方法,释放对象锁标志,进入等待状态,并且可以调用 notify() 或者 notifyAll() 方法通知正在等待的其他线程。notify()通知等待队列中的第一个线程,notifyAll() 通知的是等待队列中的所有线程。
- 试图获得锁不能设定超时
- 不能中断一个正在试图获得锁的线程
- 不够灵活
- 加锁和释放锁时机单一
- 无法知道是否成功获得锁
# 7. 面试
- 锁对象不能为空
锁信息是保存在对象头中的,对象没有的话,更没有对象头了。
作用域不宜过大
代码覆盖太多的话,效率太低。
避免死锁
如何选择 Lock 和 synchronized?
# 相同 二者都可以解决线程的安全问题; -------------------------------------------------------------------------------------------------------- # 不同 ## ① 用法 在需要同步的对象中加入 synchronized 控制,synchronized 既可以加在方法上,也可以加在特定代码中,括号中表示需要锁的对象。而 Lock 需要显示的指定起始位置和终点位置。synchronized 是托管给 JVM 执行的,而 Lock 的锁定是通过代码实现的,它有比 synchronized 更精确的线程定义。 ## ② 性能 在 JDK 5 中增加的 ReentrantLock。它不仅拥有和 synchronized 相同的并发性和内存语义,还增加了锁投票,定时锁,等候和中断锁等。它们的性能在不同情况下会不同:在资源竞争不是很激励的情况下,synchronized 的性能要优于 ReentrantLock,带在资源紧张很激烈的情况下,synchronized 的性能会下降的很快(锁升级),而 ReentrantLock 的性能基本保持不变。 ## ③ 锁机制 锁机制不一样。 synchronized 获得锁和释放锁的机制都在代码块结构中,当获得锁时,必须以相反的机制去释放,并且自动解锁,不会因为异常导致没有被释放而导致死锁。 而 Lock 需要开发人员手动去释放,并且写在 finally 代码块中,否则会可能引起死锁问题的发生。 此外,Lock 还提供的更强大的功能,可以通过 tryLock 的方式采用非阻塞的方式取获得锁。 而且,Lock 还提供公平锁,即锁的获得按照线程先来后到的顺序依次获得,不会产生饥饿现象。默认是非公平锁,可通过传入构造方法的参数实现公平锁。 synchronized 提供的是非公平锁。 -------------------------------------------------------------------------------------------------------- # 用哪个 Lock -> 同步代码块 -> 同步方法
多个线程等待同一个 synchronized 锁的时候,JVM 如何选择下一个获取锁的线程?
随机。
synchronized 提升性能的思路
- 优化覆盖范围;
- 使用其他类型的 Lock;
什么是锁的升级?降级?什么是 JVM 里的偏斜锁?轻量级锁?重量级锁?
- 所谓锁的升级就是锁随着并发量的上升,锁从轻量级锁升级为重量级锁而导致锁的性能急剧下降的过程,降级同理。
- 偏斜锁:就是 JVM 会倾向于把锁给当前拥有锁的线程,在无竞争情况下,直接把整个同步消除了,连乐观锁都不用,从而提高性能。;
- 轻量级锁:轻量级是相对于传统锁机制而言的,本意是没有多线程竞争的情况下,减少传统锁机制使用 OS 实现互斥所产生的性能损耗。实现原理很简单,就是类似乐观锁的方式。
- 重量级锁:即严格采用同步方法来加锁,效率最低。