java线程安全与同步技术

卖票案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SellTicketRunnableImpl implements Runnable {
private int ticket = 20;
@Override
public void run() {
while(ticket > 0){
System.out.println("线程 "+ Thread.currentThread().getName() +" 出售第" + ticket + "张票");
try {
Thread.sleep(1); // 增加耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
}
1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
SellTicketRunnableImpl stri = new SellTicketRunnableImpl();
new Thread(stri).start();
new Thread(stri).start();
new Thread(stri).start();
new Thread(stri).start();
new Thread(stri).start();
}
}

卖票结果

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
线程 Thread-2 出售第20张票
线程 Thread-4 出售第20张票
线程 Thread-0 出售第20张票
线程 Thread-1 出售第20张票
线程 Thread-3 出售第20张票
线程 Thread-2 出售第19张票
线程 Thread-4 出售第17张票
线程 Thread-1 出售第16张票
线程 Thread-0 出售第17张票
线程 Thread-3 出售第17张票
线程 Thread-2 出售第15张票
线程 Thread-3 出售第14张票
线程 Thread-4 出售第13张票
线程 Thread-2 出售第12张票
线程 Thread-0 出售第10张票
线程 Thread-1 出售第11张票
线程 Thread-3 出售第9张票
线程 Thread-4 出售第8张票
线程 Thread-2 出售第8张票
线程 Thread-1 出售第5张票
线程 Thread-3 出售第6张票
线程 Thread-0 出售第5张票
线程 Thread-2 出售第3张票
线程 Thread-4 出售第3张票
线程 Thread-1 出售第1张票
线程 Thread-3 出售第2张票

进程已结束,退出代码 0

找出问题

  • 多线程访问共享数据就会出现类似的不安全问题:一张票被卖掉了多次,本来20张票,买了26张,这样用户一定是不愿意的。

  • 出现的根本问题是多线程在进行相同的操作或者操作相同的数据的时候,是异步的,他们之间互不关心,是假定了操作前判断的时候的值在操作的时候并不改变,也就是假定了不存在别的线程,所以一定是出现问题的。

  • 解决问题的办法就是采用同步技术:多个线程进行相同的操作或者操作相同的数据的时候要关注是否有别的线程在进行相同的操作,有的话就阻塞等待,也就是保证共享的数据的修改或者使用必须是单线程进行,这样保证了安全

同步代码块

  • 同步代码块: synchronized关键字可以用于方法的某个区块中,表示只对这个区块的资源实行互斥访问。

  • 格式:

    1
    2
    3
    synchronized(同步锁){
    // 需要同步操作的代码
    }
  • 同步锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁

    1. 锁对象 可以是任意类型。
    2. 多个线程对象 要使用同一把锁
    3. 注意: 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入同步代码块,其它的线程只能在外等着(BLOCKED).

使用同步代码块卖票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SellTicketRunnableImpl implements Runnable {
private int ticket = 20;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
if(ticket>0){
System.out.println("线程 "+ Thread.currentThread().getName() +" 出售第" + ticket + "张票");
try {
Thread.sleep(1); // 增加耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
SellTicketRunnableImpl stri = new SellTicketRunnableImpl();
new Thread(stri).start();
new Thread(stri).start();
new Thread(stri).start();
new Thread(stri).start();
new Thread(stri).start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//结果
线程 Thread-0 出售第20张票
线程 Thread-0 出售第19张票
线程 Thread-0 出售第18张票
线程 Thread-4 出售第17张票
线程 Thread-2 出售第16张票
线程 Thread-2 出售第15张票
线程 Thread-2 出售第14张票
线程 Thread-2 出售第13张票
线程 Thread-2 出售第12张票
线程 Thread-2 出售第11张票
线程 Thread-2 出售第10张票
线程 Thread-2 出售第9张票
线程 Thread-2 出售第8张票
线程 Thread-2 出售第7张票
线程 Thread-2 出售第6张票
线程 Thread-2 出售第5张票
线程 Thread-2 出售第4张票
线程 Thread-2 出售第3张票
线程 Thread-2 出售第2张票
线程 Thread-2 出售第1张票

使用同步方法

和同步代码块类似:同步方法也会把方法内部的代码锁住,只让一个线程执行

使用步骤:

  1. 把访问了共享数据的代码抽取出来,放到一个方法中
  2. 在方法上添加synchronized修饰符

比如卖票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//设置线程任务,卖票
@Override
public void run(){
while(true){
sellTicket();
}
}

//这样的一个方法就是线程安全的方法了
public synchronized void sellTicket(){
if(ticket>0){
// 输出显示System.out.println("balabala");
ticket--;
}
}

问题:同步方法的锁对象是谁,没有直接显示,其实是对象自己,也就是this

同步方法也可以写成

1
2
3
4
5
6
7
8
public /*synchronized*/ void sellTicket(){
synchronized(this){
if(ticket>0){
// 输出显示System.out.println("balabala");
ticket--;
}
}
}

静态同步方法

  • 静态的同步方法

  • 锁对象是谁?

  • 不能是this,因为this是创建对象之后产生的,静态方法优先于对象

  • 其实静态方法的锁对象是本类的class属性—> class文件对象(反射)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class RunnableImpl implements Runnable {
private static int ticket = 100;

@Override
public void run() {
while(true){
sellTicket();
}
}

public static synchronized void sellTicket(){
if(ticket>0){
System.out.println("线程 "+ Thread.currentThread().getName() +" 出售第" + ticket + "张票");
try {
Thread.sleep(1); // 增加耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket--;
}
}
}

java线程安全与同步技术
https://blog.wangxk.cc/2020/08/30/java线程安全与同步技术/
作者
Mike
发布于
2020年8月30日
许可协议