线程安全-重点

本文详细探讨了线程安全在Java中的重要性,通过实例分析了synchronized关键字的互斥和可重入特性,以及死锁的产生条件和解决方法。同时介绍了volatile关键字的内存可见性作用,并对比了两者在保证程序正确性的不同点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.概念

2.产生原因

3.解决办法

3.1 synchronized关键字

3.1.1 特性

3.1.2 死锁

3.1.2.1 产生情况

3.1.2.2 产生原因

3.1.2.3 解决

3.1.2.4 面试题

3.2 volatile关键字

3.3 内存可见性


在上篇博客中,我们学习了进程和线程的概念,以及线程的一些内容,这篇博客我们继续学习线程中的一个重点内容—线程安全,这也是线程里面最复杂的一部分。

1.概念

如果多线程环境下代码运⾏的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

接下来,我们用代码直观感受一下线程不安全。(答案与预期不符合)

public class Demo13 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            //对count 进行自增
            for (int i = 0; i < 50000; i++) {
                    count++;
                }
        });
        Thread t2 = new Thread(() -> {
            //对count 进行自增
            for (int i = 0; i < 50000; i++) {
                    count++;
                }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        //预期结果是10w
        System.out.println(count);   //但结果不唯一
    }
}

我们可以直观看到答案与我们预期结果10w不相符,这就是一个典型的线程不安全。

经过这样更改了之后,虽然达到了预期结果10w,但也不是我们想要的,因为这样虽然是写在两个线程中了,但并不是“同时执行”,即在线程t1执行的时候,t2并不会启动。

2.产生原因

1.操作系统中,线程的调度顺序是随机的(抢占式执行),罪魁之首,万恶之源(系统内核里实现的,没办法进行修改)

2.两个线程,针对同一个变量,进行修改 (有些情况下可以通过调整代码结构,规避上述问题)

3.修改操作,不是原子的(此时的count++,就是非原子的操作,先读,再修改)(想办法让count++这里的三步走,成为原子的   -  加锁)

4.内存可见性问题

可⻅性指,⼀个线程对共享变量值的修改,能够及时地被其他线程看到。

5.指令重排序问题

是编译器的一种优化模式。

3.解决办法

加锁就可以解决,那么如何进行加锁呢?

其中最常通过使用synchronized关键字

synchronized修饰的是一个代码块,同时会指定一个“锁对象”  -> 有且只有一个,当两个线程同时尝试对一个对象加锁,此时就会出现“锁冲突”/“锁竞争” 一旦竞争出现,一个线程能够拿到锁,继续执行代码;一个线程拿不到锁,就会阻塞等待,等待前一个线程释放锁之后,它才有机会拿到锁,

继续执行  ->  本质上把“并发执行”改成了“串行执行”

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值