JAVA多线程共享资源问题
场景引入
这个场景是一个典型的多线程共享资源的场景,主要目的是测试和观察多个线程对共享变量 sum 进行并发操作时是否会出现线程安全问题
场景描述
共享资源:
- 共享变量 sum,初始值为 0。
- 多个线程同时对 sum 进行操作,一个线程负责自增操作,另一个线程负责自减操作。
线程操作:
- 每个线程在循环中对 sum 进行 1000 次操作。
- 自增线程每次将 sum 增加 1,自减线程每次将 sum 减少 1。
日志记录:
- 使用 Logger 记录每次操作的线程名称、操作类型(加法或减法)以及操作后的 sum 值。
目的
- 测试线程共享问题:通过观察 sum 的值,测试是否存在线程安全问题。如果没有同步机制,可能会出现数据不一致的情况。
- 验证并发操作的正确性:在没有同步机制的情况下,多个线程同时操作共享变量 sum,可能会导致数据竞争和不正确的结果。
预期结果
- 线程安全问题:由于没有使用同步机制,多个线程同时操作 sum,可能会导致数据竞争,最终 sum 的值可能不正确。
日志输出:日志会记录每次操作的详细信息,可以通过日志观察 sum 的变化情况。
public class syncThreadDemo01 extends Thread{
Logger logger = LoggerFactory.getLogger(syncThreadDemo01.class);
static Integer sum = 0;
// 需求: 基于sum 开启多个线程,一个线程自增,一个线程减,测试是否有线程共享问题
private Boolean isAdd;
private String threadName;
public syncThreadDemo01(Boolean isAdd,String threadName) {
this.isAdd = isAdd;
this.threadName = threadName;
}
@Override
public void run() {
for (int i = 0 ;i<1000;i++){
if (isAdd){
sum+=1;
}else {
sum-=1;
}
logger.info("线程:{}开始执行。。。。。。。。。。。,执行模式:{},输出结果:{}",threadName,isAdd?"加法":"减法",sum);
}
}
}
public class ThreadMainDemo {
public static void main(String[] args) {
// ThreadDemo thread = new ThreadDemo("线程1");
// thread.start();
// 测试 资源共享问题
syncThreadDemo01 addThread = new syncThreadDemo01(true, "加线程");
syncThreadDemo01 delThread = new syncThreadDemo01(false, "减线程");
addThread.start();
delThread.start();
}
}
输出结果:
临界区
- 一个程序运行多个线程本身是没有问题的
- 问题出在多个线程访问共享资源
- 多个线程读共享资源其实也没有问题
- 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
- 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
例如,下面代码中的临界区
synchronized
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
- 阻塞式的解决方案:synchronized,Lock
- 非阻塞式的解决方案:原子变量
本次使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
输出结果:0
竟然说synchronizzed 是对象锁,那么我通过创建对象的方式,将对象作为形参会有什么效果,以下是静态对象和成员对象的方式
输出结果:不符合预期结果,产生了对资源共享的问题
分析:
由于创建线程的时候,分别创建了两次,所以在这个当中,对象锁锁的是它这个线程的本身,并没有对其他线程进行加锁。
竟然如此,换算成静态成员对象的方式,是否可以解决这个问题。
测试结果:符合预期,解决了多个线程 对 共享资源的问题。
变量的线程安全分析
成员变量和静态变量是否线程安全?
- 如果它们没有共享,则线程安全
- 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
- 如果只有读操作,则线程安全
- 如果有读写操作,则这段代码是临界区,需要考虑线程安全