线程安全

线程安全概念

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

线程不安全
  • 举例
    在这里插入图片描述

客户端检查还有1张票,将票卖出,还没有执行更新数据库,客户端B检查了票数发现大于0于是又卖了一次票然后A将票数更新到数据库这就出现了同一张票卖了两次

  • 原子性

提供互斥访问同一时刻只能有一个线程对数据进行操作
(多行指令(一行代码可能会分解为多条指令执行),如果指令前后有依赖关系,不能插入影响我执行结果的指令,如果能狗插入就是没有原子性不能插入就是有原子性)

举例:n++有三条指令操作

  1. 从内存把数据读到CPU
  2. 进行数据更新
  3. 把数据写会到CPU

举例:我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还
没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样
就保证了这段代码的原子性了。
有时也把这个现象叫做同步互斥,表示操作是互相排斥的。

  • 可见性

一个主内存的线程被修改可以及时被其他线程观察到
(主内存:线程共享;工作内存:线程私有内存+CPU高速缓存/寄存器;主存共享数据的操作:从主存读取到工作内存,工作内存修改,然后写回主存)

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

  • 有序性

如果两个线程不能从happens-before原则观察出来。那么就不能观察他们的有序性,虚拟机可以随意对他们进行重排序,导致其观察结果杂乱无序

happens-before原则

重排序:

代码是这样的:

  1. 去前台取下 U 盘
  2. 去教室写 10 分钟作业
  3. 去前台取下快递
    如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序

代码重排序会给多线程带来什么问题

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

线程不安全讲解
public class Test4 {
    private static int COUNT = 0;
    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
            Thread[] threads = new Thread[20];
            for(int i=0; i<20; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                            COUNT++;
                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

预期是20000
实际:小于20000

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程不安全解决

synchronized关键字: 同步锁或重量级锁(JAVA SE1.6之后胡引入l偏向锁和轻量级锁),具有可重入性

作用: 对某段代码加锁,让某段代码满足原子性,可见性有序性
synchronized的原子性,有序性,可见性

原理: 多个线程间同步互斥(一段代码在任意一个时间点只有一个线程执行:加锁,释放锁)

锁的对象不同分为对象锁和类锁

  • 对象锁:
    1. 对于普通的同步方法锁的就是当前实例的对象
    2. 对于同步方法块,如果synchronized括号里配置的是类的实例对象,则锁的是配置的对象
  • 类锁:(class 对象锁)
    1. 对于静态同步方法,锁的是当前类
    2. 对于同步方法块,如果synchronized括号里配置的是类的Class对象,则锁的是当前类
public class Test4 {
    private static int COUNT = 0;
    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
            Thread[] threads = new Thread[20];
            for(int i=0; i<20; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                                //加锁后结果就是20000
                                synchronized (Test4.class){
                                    COUNT++;
                                }

                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

synchronized加锁后20个线程同步互斥执行,有一个线程加锁执行时,剩余19个线程处于阻塞状态,当前运行线程执行完毕后解锁,剩余线程竞争抢占执行。

synchronized只有对同一个对象加锁才会产生同步互斥的作用
可重入性:同一个对象可以对同一个对象锁多次申请,基于重入计数器来实现

  • synchronized 传入类对象-类名.class获取类对象
public class Test4 {
    private static int COUNT = 0;
    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
        Class clazz=Test4.class;
            Thread[] threads = new Thread[20];
            for(int i=0; i<20; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                                //加锁后结果就是20000
                                synchronized (clazz){
                                    COUNT++;
                                }

                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

  • synchronized方法传入实例对象
public class Test4 {
    private static int COUNT = 0;

    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
            Class clazz=Test4.class;
            Test4 st=new Test4();
            Thread[] threads = new Thread[20];
            for(int i=0; i<20; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                           synchronized (st){
                               COUNT++;
                           }

                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}
  • 静态同步方法
public class Test4 {
    private static int COUNT = 0;

    //静态同步方法
    public synchronized static void decrement(){
        COUNT++;
    }
    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
        Class clazz=Test4.class;
            Thread[] threads = new Thread[20];
            for(int i=0; i<20; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                            //静态同步方法
                            decrement();

                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

  • 实例同步方法
public class Test4 {
    private static int COUNT = 0;

    //实例同步方法
    public synchronized  void decrement(){
        COUNT++;
    }
    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
            Class clazz=Test4.class;
            Test4 st=new Test4();
            Thread[] threads = new Thread[20];
            for(int i=0; i<20; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                            //静态同步方法
                            st.decrement();

                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

  • 拆分写法同理-sychronized必须锁同一对象才能实现同步互斥
  1. 一部分加锁一部分未加锁
public class Test4 {
    private static int COUNT = 0;

    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
            Class clazz=Test4.class;
            Test4 st=new Test4();
            Thread[] threads = new Thread[20];
            for(int i=0; i<19; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                           synchronized (st){
                               COUNT++;
                           }
                        }
                    }
                });
            }
            for(int i=0; i<1; i++){
                threads[19+i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                                COUNT++;
                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

  1. 加锁同一对象
public class Test4 {
    private static int COUNT = 0;

    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
            Class clazz=Test4.class;
            Test4 st=new Test4();
            Thread[] threads = new Thread[20];
            for(int i=0; i<19; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                           synchronized (st){
                               COUNT++;
                           }
                        }
                    }
                });
            }
            for(int i=0; i<1; i++){
                threads[19+i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                            synchronized (st){
                                COUNT++;
                            }
                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

  1. 加锁不同对象
public class Test4 {
    private static int COUNT = 0;

    public static void main(String[] args) throws InterruptedException {
    //有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
    //等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
            Class clazz=Test4.class;
            Test4 st=new Test4();
            Thread[] threads = new Thread[20];
            for(int i=0; i<19; i++){
                threads[i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                           synchronized (st){
                               COUNT++;
                           }
                        }
                    }
                });
            }
            for(int i=0; i<1; i++){
                threads[19+i] = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for(int j=0; j<1000; j++){
                            synchronized (clazz){
                                COUNT++;
                            }
                        }
                    }
                });
            }
            for(Thread t : threads){
                t.start();
            }
            //让main线程阻塞等待所有的20个子线程执行完毕
            for(Thread t : threads){
                t.join();
            }

            System.out.println(COUNT);
        }
}

synchronized对象锁之间的关系

  • Class t=Test.class
  • Test t1=new Test()
  • Test t2=new Test()

在这里插入图片描述

synchronized流程图

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值