线程安全问题

1.线程安全的概念

想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

2.线程不安全的五个因素:
1、CPU抢占执行
2、内存可见性问题 volatile

为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题。

3、指令重排序问题 volatile

刚才那个例子中,单线程情况是没问题的,优化是正确的,但在多线程场景下就有问题了,什么问题呢。可能快递是在你写作业的10分钟内被另一个线程放过来的,或者被人变过了,如果指令重排序了,代码就会是错误的。

4、原子性问题

比如刚才我们看到的 n++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU (load)
  2. 进行数据更新 (calc)
  3. 把数据写回CPU (save)

5、多个线程同时修改同一个变量

3.volatile关键字
轻量级解决“线程安全”的方案:

(1)禁止指令重排序
(2)解决内存可见性问题(当操作完变量之后,强制删除线程工作内存中的变量)

4.synchrinized
1、实现原理
底层是使用操作系统的mutex lock 实现的

(1)操作系统:互斥锁
在这里插入图片描述

(2) JVM: 帮我们实现的监视器的加锁和释放锁操作
在这里插入图片描述

(3)Java: 锁存放在变量的对象头里面
在这里插入图片描述

2、synchronized作用

(1)原子性:synchronized保证语句块内操作是原子的
(2)可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)
(3)有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)

3、三种使用场景:
1、使用synchronized来修饰代码块(加锁对象自定义)
2、使用synchronized来修饰静态方法(加锁对象:当前类方法)
3、使用synchronized可以修饰普通方法(加锁对象:当前类的实例)

4、synchrinized JDK 1.6优化【四种状态】
(1)无锁
(2)偏向锁
(3)轻量级锁
(4)重量级锁

5.手动锁Lock
Interface Lock
在这里插入图片描述

从Lock接口中我们可以看到主要有个方法,这些方法的功能从注释中可以看出:

(1)lock():获取锁,如果锁被暂用则一直等待

(2)unlock():释放锁

(3)tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true

(4)tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间

(5)lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事

以下是lock的一个代码示例:

public class ThreadDemo32 {
    // 全局变量
    private static int number = 0;

    //循环次数
    public static final int maxSize = 100000;
    public static void main(String[] args) throws InterruptedException {
        //1.创建一个锁对象
        Lock lock = new ReentrantLock();
        //+10w
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <maxSize ; i++) {
                    //2.加锁
                    lock.lock();     //一定要把lock放在try外面:
                                    //1.如果将lock()方法加在try里面,那么当try里面的代码出现异常之后
                                    //那么就会执行finally里面的代码,但是这个时候枷锁还没成功 ,就去释放锁
                                   //2、如果将lock放在try里面,那么当执行finally里面释放锁的代码时
                    try {
                        number++;
                    }finally {
                        //3、释放锁
                        lock.unlock();
                    }


                }
            }
        });
        t1.start();

//-10w
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number--;
                    }finally {
                        lock.unlock();
                    }
                }
            }
        });
        t2.start();

        //等待两个线程执行完
        t1.join();
        t2.join();

        System.out.println("最终执行结果:" + number);
    }
}

注意事项:

一定要把lock()放到try外面:
(1)、如果将lock()方法放在 try 里面,那么当try
里面的代码出现异常之后,那么就会执行finally 里面的释放锁的代码,但这个时候加锁还没成功,就去释放锁。
(2)、如果将lock()方法放在 try
里面,那么当执行finally里面释放锁的代码的时候就会报错(线程状态异常),释放锁的异常会覆盖掉业务代码的异常报错,从而增加了排除错误成本。

6.volatile 和 synchronized区别?
区别:
volatile可以解决内存可见性问题和禁止指令重排序,但 volatile 不能解决原子性问题;synchronized是用来保证线程安全,也就是 synchronized可以解决任何关于线程安全的问题(关键代码排队执行,始终只有一个线程会执行加锁操作;原子性问题…)。
7.synchronized 和 Lock 区别?

1.synchronized 既可以修饰代码块,又可以修饰静态方法或者普通方法;而Lock 只能修饰代码块。
2.synchronized 只有非公平锁的锁策略,而Lock 既可以是公平锁也可以是非公平锁(ReentrantLock 默认是非公平锁,也可以通过构造函数设置 true声明它为公平锁)。
3.ReentrantLock更加的灵活(比如tryLock ) 。
4.synchronized是自动加锁和释放锁的,而 ReentrantLock需要自己手动加锁和释放锁。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值