synchronized

同步代码块

  使用synchronized关键字给代码块加锁,synchronized后的括号内可以填入任意对象,被填入的对象将作为同步监视器;线程在执行同步代码块之前,必须先获得对同步监视器的锁定,当同步代码执行完成后,线程会释放对同步监视器的锁定;如果同步监视器已经被其他线程锁定,则当前线程进入阻塞队列,等待同步监视器的锁定被释放,这种方式可以确保同一时刻只有一个线程访问加锁的代码块()。

  如下代码,lock对象充当同步监视器,当一个线程调用test方法并进入同步代码块之前,必须先获得lock对象的锁定,如果已经有其他线程持有了该锁定,当前线程将进入阻塞状态。

1
2
3
4
5
6
7
8
9
public class Main {
private final Object lock = new Object();
public void test() {
synchronized (lock) {
// 执行需要同步的代码
// ...
} // 在代码块执行完成后,会自动释放对同步监视器的锁定
}
}

  如果多个线程调用了同一个Main对象的test方法,它们将竞争同一个lock对象的锁定,那么只有一个线程能够获得该对象的锁定,并执行同步代码块中的代码,其他线程将被阻塞,直到获得了锁定的线程释放锁。
  但如果多个线程调用不同Main对象的test方法,那么每个线程都有不同的lock对象作为同步监视器,获得不同的同步监视器的锁定,所以test方法中的同步代码是独立的,不会发生阻塞。
  若要使多个线程能够同步调用不同Main对象的test方法,可以将lock设为静态,此时多个线程将竞争同一个静态锁,只有一个线程能够获得静态锁并执行同步代码块中的代码,其他线程将被阻塞,直到获得锁的线程释放锁。

1
2
3
4
5
6
7
8
9
10
11
public class Main {

// 设为静态
private static final Object lock = new Object();
public void test() {
synchronized (lock) {
// 执行需要同步的代码
// ...
} // 在代码块执行完成后,会自动释放对同步监视器的锁定
}
}

同步方法

普通同步方法

  普通同步方法,以当前实例对象作为同步监视器

1
2
3
4
5
6
7
8
9
10
11
// 同步方法
public synchronized void test() {
// 执行需要同步的代码
}

// 等同于
public void test() {
synchronized (this) {
// 执行需要同步的代码
}
}

  如下代码执行时,多个线程同时执行syncTest,操作value值递减,由于运行中的线程时间片用完时会进入阻塞状态,假设当前线程在即将操作value值时进入阻塞状态,阻塞期间其他得到时间片的线程继续对value值操作,当阻塞的线程重新获得时间片并执行时,进入阻塞状态之前所操作过的value可能已经被其他线程改变,所以当输出value时会出现相同的值,甚至出现负数

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 SyncTest implements Runnable {

private int value = 100;

public static void main(String[] args) {
SyncTest syncTest = new SyncTest();

Thread thread1 = new Thread(syncTest);
Thread thread2 = new Thread(syncTest);
Thread thread3 = new Thread(syncTest);

thread1.start();
thread2.start();
thread3.start();
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
// sleep会使线程主动放弃执行权,模拟被调度器将时间片分配给其他线程
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

if (value > 0) {
System.out.println("value=" + (--value));
}
}
}
}

  对value值修改部分加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
// sleep会使线程主动放弃执行权,模拟被调度器将时间片分配给其他线程
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (this) {
if (value > 0) {
System.out.println("value=" + (--value));
}
}
}
}

  或者将修改value值的部分提取到一个方法,并将方法设为同步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
// sleep会使线程主动放弃执行权,模拟被调度器将时间片分配给其他线程
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

sub();
}
}

private synchronized void sub() {
if (value > 0) {
System.out.println("value=" + (--value));
}
}

静态同步方法

  静态同步方法,以当前类的class对象作为同步监视器

1
2
3
4
5
6
7
8
9
10
11
// 静态同步方法
public synchronized static void test() {
// 执行需要同步的代码
}

// 等同于
public static void test() {
synchronized (Main.class) {
// 执行需要同步的代码
}
}

  如下synchronized的用法并不能起到代码同步的作用,因为synchronized后的括号中填入了this,代表以当前对象作为代码块的同步监视器,而三个线程使用了不同的对象,所以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
public class TestThread extends Thread{

private static int value = 100;

public static void main(String[] args) {
new TestThread().start();
new TestThread().start();
new TestThread().start();
}

@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (this) {
if (value > 0) {
System.out.println("value=" + (--value));
}
}
}
}
}

  要使代码在多个线程中同步,需要确定同步监视器的唯一,可以用当前类的class对象作为同步监视器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (TestThread.class) {
if (value > 0) {
System.out.println("value=" + (--value));
}
}
}
}

  或则创建一个静态对象,作为同步监视器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private final static Object lock = new Object();

@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (lock) {
if (value > 0) {
System.out.println("value=" + (--value));
}
}
}
}

  又或者使用静态同步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

sub();
}
}

private synchronized static void sub() {
if (value > 0) {
System.out.println("value=" + (--value));
}
}