初识Java多线程之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
37
38
39
package com.example.socket.multithreading;

public class SafetyThread {

private static int num = 0;

public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});Thread thread2 = new Thread(() -> {
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});
// 存在线程安全问题
thread1.start();
thread2.start();
Thread thread3 = new Thread(() -> {
int num = 0;
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});Thread thread4 = new Thread(() -> {
int num = 10;
while (num < 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
}
});
// 不存在线程安全问题
thread3.start();
thread4.start();
}
}

synchronized是什么?

synchronized是Java当中的一种同步锁,使用synchronized锁住的代码块都是同步方法
synchronized解释

如何使用synchronized解决线程安全问题

一下代码均取自初识Java多线程之创建多线程的几种方式(一)中的代码只修改了其中的run方法。

用synchronized解决实现Runnable接口的线程安全问题

  1. 锁住整个run方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Override
    public synchronized void run() {
    while (num > 0) {
    try {
    Thread.sleep(50);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
    num--;
    }
    }
  2. 锁住this

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    public void run() {
    synchronized (this) {
    while (num > 0) {
    System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
    num--;
    }
    }
    }
  3. 锁住一个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private Object obj = new Object();

    @Override
    public void run() {
    synchronized (obj) {
    while (num > 0) {
    System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
    num--;
    }
    }
    }
  4. 锁住当前类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    public void run() {
    synchronized (RunnableImpl.class) {
    while (num > 0) {
    System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
    num--;
    }
    }
    }

用synchronized解决继承Thread类的线程安全问题

  1. 锁住一个静态方法
    注:必须将synchronized加在静态方法上,防止run或者非静态方法上都是无效的,因为此时的锁不是同一个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    public void run() {
    show();
    }

    public synchronized static void show() {
    while (num > 0) {
    System.out.println(currentThread().getName() + "当前票号:" + num);
    num--;
    }
    }
  2. 锁静态对象
    注:该对象必须是静态的,原因同1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private static Object object = new Object();

    @Override
    public void run() {
    synchronized (object) {
    while (num > 0) {
    System.out.println(currentThread().getName() + "当前票号:" + num);
    num--;
    }
    }
    }
  3. 锁住当前类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    public void run() {
    synchronized (ExtendThread.class) {
    while (num > 0) {
    System.out.println(currentThread().getName() + "当前票号:" + num);
    num--;
    }
    }
    }

对于继承Thread的线程不能通过锁this来保证线程安全,因为此时的this不是同一对象。

synchronized解惑

synchronized锁对象的要求是什么?

synchronized锁住的对象,对于多个线程来说必须是同一对象,换言之,多个线程必须使用同一把锁。若多个线程使用不同的锁,将不能保证线程安全问题

将synchronized加在方法上时锁住的到底是什么?

synchronized加在普通方法上时,锁住的是当前的this。加在静态方法上时锁住的是当前类

synchronized缺陷

synchronized会导致所有方法都是同步执行的对效率有一定影响,所以要控制好锁力度,能锁部分方法体就锁部分方法体,不要锁住整个方法。对于不会发生线程安全的代码块不要随意加锁。