0
点赞
收藏
分享

微信扫一扫

【线程安全问题和解决方法】

早安地球 2022-04-16 阅读 91
java-ee

概念

线程不安全指的是程序在多线程的执行结果不符合预测。

线程不安全元素

1.抢占式执行

2.多个线程修改同一个变量

//举例 如果是单个线程执行此操作,结果正确,为0

/**
* 线程不安全问题
*/

public class ThreadDemo15 {
private static int num;

static class Counter {
//++操作
public void increment(int count) {
for (int i = 0; i < count; i++) {
num++;
}
}

//--操作
public void decrment(int count) {
for (int i = 0; i < count; i++) {
num--;
}
}

public int getNum() {
return num;
}
}

public static void main(String[] args) {
int count = 10000;
Counter counter = new Counter();
counter.increment(count);
counter.decrment(count);
System.out.println("++操作和--操作最后的结果:" + counter.getNum());
}
}

在这里插入图片描述

//如果是多个线程,多个线程会出现抢占式执行(此程序中也是多个线程修改同一个变量),从而使结果错误。
public class ThreadDemoVolatile {
private static int num = 0;
static class Counter {
public void increment(int count) {
for (int i = 0; i < count; i++) {
num++;
}
}

public void decrment(int count) {
for (int i = 0; i < count; i++) {
num--;
}
}

public int getNum() {
return num;
}
}

public static void main(String[] args) throws InterruptedException {
int count = 100000;
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
counter.increment(count);
});
thread1.start();

Thread thread2 = new Thread(() -> {
counter.decrment(count);
});
thread2.start();

//等待线程执行完
thread1.join();
thread2.join();
System.out.println("最终结果:" + counter.getNum());
}

}

在这里插入图片描述

3.非原子性操作

原子性:指不可分割性,操作要么一次性不间断的执行完,要么一个也不执行。比如,一段代码就是一个房间,每个线程就是进入房间的人,如果A进入这个房间之后没有出来,B不可以进入。否则就是不具备原子性。

如果将上述第二个程序改为串行执行,最终结果就是正确的:
在这里插入图片描述
必须保证第一个线程执行完成后,再执行第二个线程,此时计算的结果才是正确的。这一点与抢占式执行有关,如果线程不是“抢占式执行”,就算是原子性关系也不大。

4.内存可见性

可见性指一个线程对共享变量值的修改,能够及时被其他线程看见。
在这里插入图片描述
因为线程之间的共享变量存在主内存中,每个线程都有自己的工作内存。当线程要修改一个共享变量的时候,会先修改工作内存中的副本,再同步回主内存。工作时线程要先读取共享变量,先把变量从主内存中拷贝到各自的工作内存,再从工作内存读取数据。当修改线程1工作内存中的值,线程2的工作内存不一定会及时变化。
5.指令重排序
编译器优化的本质是调整代码的执行顺序,在单线程下不会出错,但在多线程下会容易出现混乱,从而造成线程安全问题。

解决方法

1.volatile 解决内存可见性和指令重排序

在这里插入图片描述
如果上述程序变量前没有加volatile,结果就是线程1开始执行,线程2也只执行了将flag修改为false,但是线程1不能结束。因为内存不可见性,就算线程2修改了变量,但是线程1不知道,所以线程1的变量值没有改变,将会一直执行。如果将volatile加到变量前将会解决这个问题。volatile会强制读写内存,将改变后线程2的副本值从工作内存刷新到主内存中,代码就可以从主内存中读取最新值到线程1的工作内存中,然后再从工作内存中读取副本值。
结果就会从
在这里插入图片描述
变为:
在这里插入图片描述
缺点:volatile不能解决原子性问题。

2.使用锁解决线程安全问题(最主要的手段)

一般分为 synchronized 和 lock 锁

2.1 synchronized的基本使用

2.1.1 修饰静态方法


/**
* Synchronized
* 1.修饰静态方法
*/

public class ThreadSynchronized {
private static int num = 0;
static class Counter {
//循环次数
private static int count = 100000;

public synchronized static void increment() {
for (int i = 0; i < count; i++) {
num++;
}
}

public synchronized static void decrment() {
for (int i = 0; i < count; i++) {
num--;
}
}

}

public static void main(String[] args) throws InterruptedException {

Thread thread1 = new Thread(() -> {
Counter.increment();
});
thread1.start();

Thread thread2 = new Thread(() -> {
Counter.decrment();
});
thread2.start();

//等待线程执行完
thread1.join();
thread2.join();
System.out.println("最终结果:" + num);
}

}


2.1.2 修饰普通方法

/**
* 2.修饰普通方法
*/

public class ThreadSynchronized2 {
private static int num = 0;

static class Counter {
//循环次数
private static int count = 100000;

public synchronized void increment() {
for (int i = 0; i < count; i++) {
num++;
}
}

public synchronized void decrment() {
for (int i = 0; i < count; i++) {
num--;
}
}

}

public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
counter.increment();
});
thread1.start();

Thread thread2 = new Thread(() -> {
counter.decrment();
});
thread2.start();

//等待线程执行完
thread1.join();
thread2.join();
System.out.println("最终结果:" + num);
}
}

2.1.3 修饰代码块

修饰代码块是给对象加锁,有三种方法:
1.在实例类中使用this
在这里插入图片描述

2.在静态类中使用xxx.class
在这里插入图片描述

3.自定义锁对象(最常使用)
在这里插入图片描述

2.2 synchronized 特性

2.2.1 互斥

某个线程执行到某个对象的synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。

2.2.2 刷新内存

2.2.3 可重入

synchronized 对同一条线程来说是可重入的。


public class ThreadSynchronized4 {
public static void main(String[] args) {
synchronized (ThreadSynchronized4.class){
System.out.println("得到第一把锁");
synchronized (ThreadSynchronized4.class){
System.out.println("得到第二把锁");
}
}
}
}

2.3 Lock

lock.lock();
try{
}
finally{
lock.unlock()
}
举报

相关推荐

0 条评论