谈谈synchronized

一.线程安全问题

​ 在单线程的情况下不会出现线程安全问题,而在多线程中,有可能会出现同时访问一个共享可变资源的情况。共享意味着该资源可被多个线程共享,可变意味着资源可被多个线程修改,这样很可能出现线程安全问题。

看一段线程安全的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SynchronizedTest1 {
private int count = 0;
public void synchronizedTest1() {
for(int i=0;i<1000;i++) {
count++;
}
}
public static void main(String[] args) {
SynchronizedTest1 t1 = new SynchronizedTest1();
for(int i=0;i<10;i++) {
new Thread(new Runnable() {
@Override
public void run() {
t1.synchronizedTest1();
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println(t1.count);
}
}

在这里我开启了10个线程,每个线程我都将它们累加1000,结果应该是10*1000=10000,但是运行程序之后发现,结果count是小于10000,这是为什么呢?

这是由于count++不是原子操作,它由三步组成,首先要获取count的值,再对它进行加1,最后更新count.假设有两个线程,线程一先读到count=10;线程二这时候也读到count=10;线程二对count加1,

count=11,这时候线程二被阻塞,线程一开始执行,进行加1操作,并成功更新值,count=11,线程一执行完成后,线程二开始执行,由于线程二在之前已经进行了加1操作,这时候只需要将count写回到主存,最后count=11,两个线程在执行自增操作的时候,只加了1,这就是共享变量带来的并发问题。

二.synchronized解决线程不安全

要解决上述的线程不安全问题,可以使用synchronized解决,我们只需要在方法上添加synchronized

1
2
3
4
5
public synchronized void synchronizedTest1() {
for(int i=0;i<1000;i++) {
count++;
}
}

synchronized是一种对象锁,用它来对某个方法或者代码块进行标记后,当某个线程对这个方法或者对象进行访问的时候,这个线程便获得了这把锁,别的线程只能等待该线程将锁释放,才能访问这个方法或者代码块。

三.synchronized的实现机制

编写一个简单的同步代码

1
2
3
4
5
6
7
8
9
10
public class SynchronizedTest2 {
public void test2() {
synchronized (this) {

}
}
public static void main(String[] args) {

}
}

通过javap -v SynchronizedTest2.class,获取字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_1
5: monitorexit
6: goto 14
9: astore_2
10: aload_1
11: monitorexit
12: aload_2
13: athrow
14: return

可以看到,使用synchronized进行同步操作,关键是需要对对象的monitor 进行获取,monitorenter让对象的锁计数器加1,monitorexit会让对象的锁计数器减一。

## 四.synchronized实现同步的方式

同步方法

添加在非静态方法的同步:获取的是实例对象的锁

1
2
3
public synchronized void synchronizedTest1() {

}

添加在静态方法的同步:获取的是类对象的锁

1
2
3
public synchronized static void synchronizedTest2() {

}

同步代码块

非静态方法中的代码块

1
2
3
4
5
public void synchronizedTest4() {
synchronized(this) { //实例对象的锁

}
}

静态方法中的代码块

1
2
3
4
5
public static void synchronizedTest3() {
synchronized(SynchronizedTest1.class) { //类对象的锁

}
}

5.使用synchronized需要注意的地方

  1. 对于 synchronized方法 或者 synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。  
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
public class TestException {

public synchronized void t1() {
while(true) {
int i = 10/0; // java.lang.ArithmeticException: / by zero 释放锁
}
}
public synchronized void t2() {
System.out.println("我是t2,我正在执行");
}

public static void main(String[] args) {
TestException t = new TestException();
new Thread(new Thread() {
@Override
public void run() {
t.t1();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Thread() {
@Override
public void run() {
t.t2();
}
}).start();
}
}
  1. 当一个对象的synchronized的方法正在被一个线程访问,其它线程不能访问该对象的其它synchronized方法,但是可以访问其它非synchronized的方法。这应该很好理解,一个对象就一把锁,被一个线程占用后,其它线程必须等带该锁被释放。
  2. synchronized锁住的是对象
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
public class TestObject {

private Object obj = new Object();
public void t1() {
synchronized(obj) {
System.out.println(Thread.currentThread().getName());
obj = new Object(); //改变obj,指向另外一个对象
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
TestObject t = new TestObject();
new Thread(new Runnable() {
@Override
public void run() {
t.t1();
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
t.t1();
}
}).start();
}
}

线程1先执行,将obj = new Object(); 指向另外一个对象,这时候阻塞两秒,线程2执行,获取锁,由于obj是一个新对象所以获取锁成功,线程1和线程2交替执行

运行结果

1
2
3
4
Thread-0
Thread-1
Thread-0
Thread-1
文章目录
  1. 1. 一.线程安全问题
  2. 2. 二.synchronized解决线程不安全
  3. 3. 三.synchronized的实现机制
    1. 3.1. 同步方法
    2. 3.2. 同步代码块
  4. 4. 5.使用synchronized需要注意的地方
|
载入天数...载入时分秒...