Synchronized性质:可重入、不可中断

Synchronized的作用

  • 同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的
  • 能够保证在同一时刻 最多只有一个线程执行该段代码,以达到保证并发安全的效果

Synchronized的地位

  • Synchronized是Java的关键字,被Java语言原生支持
  • 最基本的护持同步手段
  • 是并发编程中的元老级角色,是并发编程的必学内容

不用并发手段会发生什么后果

代码实战:两个线程同时a++,最后结果会比预计的少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* @author: AmberZxh
* @DateTime: 2021/1/11 11:30 下午
* @description: test
*/
public class DisappearRequest1 implements Runnable{
static DisappearRequest1 instance = new DisappearRequest1();
static int count = 0;


public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println(count);
}

@Override
public void run() {
for (int j = 0; j < 100000; j++) {
count++;
}
}
}

运行结果

1
2
3
4
第一次运行:140623
第二次运行:142235
第三次运行:125453
第四次运行:186434

理应的值应该是20000,但为什么实际结果不一致?

我们来看具体步骤

  • 读取count
  • count++
  • 写入count

分析:

t1运行

线程t1读取到count=9,它可能因为一些原因阻塞了。

t2运行

t2读取到count=9,它使count++,但使没有写入内存,t2阻塞

t1继续运行

因为t2改变的count的值,但是没有将它写入内存,所以t1读到的还是9

t1将count=10写入内存,t2也将count=10写入内存

综上所诉就是我们所说的:线程不安全;

synchronized的两个用法:第一个用法

对象锁

包括 方法锁 (默认锁对象为this当前实例对象)和同步代码块锁 (自己指定锁对象)

类锁

指synchronized修饰 静态 的方法或指定锁为 Class对象

对象锁实例
  • 代码块形式:手动指定锁对象
  • 方法锁形式:synchronized修饰普通方法,锁对象默认为this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* @author: AmberZxh
* @DateTime: 2021/1/12 12:24 上午
* @description: 对象锁和方法锁测试用例
*/
public class SynchronizedObjectCodeBlock2 implements Runnable{

static SynchronizedObjectCodeBlock2 instance=new SynchronizedObjectCodeBlock2();
@Override
public void run() {

synchronized (this) {
System.out.println("我是对象锁的代码块形式" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
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");
}
}

1
2
3
4
5
6
7
运行结果:

我是对象锁的代码块形式Thread-0
Thread-0 运行结束
我是对象锁的代码块形式Thread-1
Thread-1 运行结束
finished

将代码修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object lock1=new Object();
Object lock2=new Object();

@Override
public void run() {

synchronized (lock1) {
System.out.println("我是lock1" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " lock1运行结束 ");
}
synchronized (lock2) {
System.out.println("我是lock2" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " lock2运行结束 ");
}
}

运行结果

1
2
3
4
5
6
7
8
9
我是lock1Thread-0
Thread-0 lock1运行结束
我是lock2Thread-0
我是lock1Thread-1
Thread-0 lock2运行结束
Thread-1 lock1运行结束
我是lock2Thread-1
Thread-1 lock2运行结束
finished

这里不难看出两个线程时并行的。

当吧lock2改为lock1是,则是顺序运行的,代码就不赘述了。

1
2
3
4
5
6
7
8
9
我是lock1Thread-0
Thread-0 lock1运行结束
我是lock1Thread-0
Thread-0 lock1运行结束
我是lock1Thread-1
Thread-1 lock1运行结束
我是lock1Thread-1
Thread-1 lock1运行结束
finished

注意一哈 idea的调试,超级好用

方法锁实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @author: AmberZxh
* @DateTime: 2021/1/12 9:54 下午
* @description:
*/
public class SynchronizedObjectMethod3 implements Runnable{

static SynchronizedObjectMethod3 instance=new SynchronizedObjectMethod3();

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");
}
@Override
public void run() {

method();
}

public synchronized void method(){
System.out.println("我是对象锁的方法修饰符形式,我叫"+Thread.currentThread().getName());

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("运行结束");
}
}

1
2
3
4
5
我是对象锁的方法修饰符形式,我叫Thread-0
运行结束
我是对象锁的方法修饰符形式,我叫Thread-1
运行结束
Finished

总结:

  • 代码块形式:手动指定锁对象
  • 方法锁形式:synchronized修饰普通方法,锁对象默认为this

synchronized的两个用法:第二个用法:类锁

  • 概念(重要):Java类可能有很多对象,但只有1个Class对象
  1. 本质:所以所谓的类锁,不过是Class对象的锁而已。(概念而已,帮助我们理解所谓的类锁)
  2. 用法和效果:类锁只能在同一时刻被一个对象拥有。
  • 形式1: synchronized加在static方法上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* @author: AmberZxh
* @DateTime: 2021/1/12 10:20 下午
* @description: 类锁的第一种形式 static
*/
public class SynchronizedClassStatic4 implements Runnable {
static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();

@Override
public void run() {
method();


}

public synchronized void method() {
System.out.println("我是类锁的第一种形式static,我叫 " + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 运行结束");
}

public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()) {

}
System.out.println("Finished");
}
}

1
2
3
4
5
我是类锁的第一种形式static,我叫 Thread-1
我是类锁的第一种形式static,我叫 Thread-0
Thread-1 运行结束
Thread-0 运行结束
Finished

这是没有给method方法加static情况的运行结果,可以看出两个线程,是并行的

加上static后

1
2
3
4
5
6
7
8
9
public static synchronized void method() {
System.out.println("我是类锁的第一种形式static,我叫 " + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 运行结束");
}
1
2
3
4
5
我是类锁的第一种形式static,我叫 Thread-0
Thread-0 运行结束
我是类锁的第一种形式static,我叫 Thread-1
Thread-1 运行结束
Finished
  • 形式2:synchronized(*.class)代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.TreeMap;

/**
* @author: AmberZxh
* @DateTime: 2021/1/12 10:35 下午
* @description: 类锁的第二种形式
*/
public class SynchronizedClassClass5 implements Runnable {

static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();

@Override
public void run() {

try {
method();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private void method() throws InterruptedException {
synchronized (SynchronizedClassClass5.class) {

System.out.println("我是类锁的第二种形式:synchronized(*.class).我叫 " + Thread.currentThread().getName());

Thread.sleep(3000);

System.out.println(Thread.currentThread().getName() + " 运行结束");

}

}

public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()) {

}
System.out.println("Finished");

}
}

1
2
3
4
5
我是类锁的第二种形式:synchronized(*.class).我叫 Thread-1
Thread-1 运行结束
我是类锁的第二种形式:synchronized(*.class).我叫 Thread-0
Thread-0 运行结束
Finished

可以将 synchronizedClassClass5.class 改成this ,线程就还会是并行的

可见 不管类有多少个实例,只要类被锁上了,你所有的线程都要一个一个的串行运行

原理:加解锁原理、可重入原理、可见性原理

Synchronized的缺陷:效率低、不够灵活、无法预判是否成功获取到锁

如何选择Lock或Synchronized