多线程同步

一.什么是线程安全
1.1为什么会有线程安全
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
二.案例
需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
二.线程不安全的情况

/**
 * @description 线程不安全
 * @date 2019/8/5 13:37
 **/
public class ThreadTrain1 implements Runnable {
    //货票总数,多个线程同时共享资源
    private int trainCount=100;

    @Override
    public void run() {
        while(trainCount>0){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){

            }
            sale();
        }
    }

    public void sale(){
        if (trainCount>0){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){

            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
            trainCount--;
        }
    }
}

启动方法
 ThreadTrain1 threadTrain = new ThreadTrain1(); // 定义 一个实例
Thread thread1 = new Thread(threadTrain, "一号窗口");
Thread thread2 = new Thread(threadTrain, "二号窗口");
 thread1.start();
 thread2.start();
运行结果:
一号窗口,出售 第1张票.
......
二号窗口,出售 第34张票.//出现重复购票
一号窗口,出售 第34张票.//出现重复购票
......
二号窗口,出售 第100张票.

三.线程安全的解决方法
3.1同步代码块
什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}

/**
 * @description 同步代码块
 * @date 2019/8/8 8:25
 **/
public class ThreadTrain2 implements Runnable {
    //货票总数,多个线程同时共享资源
    private int trainCount = 100;
    private Object mutex = new Object();

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        while(trainCount>0){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){

            }
            sale();
        }
        long endTime=System.currentTimeMillis();
        System.out.println("同步代码块耗时:"+(endTime-beginTime)+"ms");
    }

    public void sale() {
        synchronized (mutex) {
            System.out.println("当前线程"+Thread.currentThread().getName()+"获取到锁");
            if (trainCount > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {

                }
                System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
                trainCount--;
            }
        }
    }
}

3.2同步函数
什么是同步函数?
答:在方法上修饰synchronized 称为同步函数
同步函数用的是什么锁?
答:同步函数使用this锁。
证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。

/**
 * @description 同步函数
 * @date 2019/8/8 8:37
 **/
public class ThreadTrain3 implements Runnable {
    private int trainCount = 100;

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        while (trainCount > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {

            }
            sale();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("同步代码块耗时:" + (endTime - beginTime) + "ms");
    }

    public synchronized void sale() {
        System.out.println("当前线程" + Thread.currentThread().getName() + "获取到锁");
        if (trainCount > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {

            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
            trainCount--;
        }
    }
}


3.3静态同步函数
答:什么是静态同步函数?
方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是 该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前 类名.class 表示。

/**
 * @description 静态同步函数
 * @date 2019/8/8 8:51
 **/
public class ThreadTrain4 implements Runnable {
    private int trainCount = 100;

    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        while (trainCount > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {

            }
            sale();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("同步代码块耗时:" + (endTime - beginTime) + "ms");
    }


    public void sale() {
//        System.out.println("当前线程" + Thread.currentThread().getName() + "获取到锁");
        if (trainCount > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {

            }
            synchronized (ThreadTrain4.class) {
                System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
                trainCount--;
            }
        }
    }
}

拓展:一个线程使用同步函数 一个线程使用同步代码块
证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。

/**
 * @Description 一个线程使用同步代码块, 一个线程使用同步函数
 * @DATE 2019/8/8 8:42
 **/
public class ThreadTrain5 implements Runnable {
    public boolean flag = true;
    private Object mutex = new Object();
    private int trainCount = 100;

    @Override
    public void run() {
        if (flag){
            while(true){
                synchronized (mutex){
                    if (trainCount>0){
                        try{
                            Thread.sleep(40);
                        }catch (Exception e){

                        }
                        System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
                        trainCount--;
                    }
                }
            }
        }else{
            while (true){
                sale();
            }
        }
    }
    public synchronized void sale() {
        if (trainCount > 0) {
            try {
                Thread.sleep(40);
            } catch (Exception e) {

            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
            trainCount--;
        }
    }
}

结果:

一号窗口,出售 第1张票.
一号窗口,出售 第2张票.//重复消费
二号窗口,出售 第2张票.//重复消费
									//第3张票未被消费
二号窗口,出售 第4张票.
一号窗口,出售 第4张票.
二号窗口,出售 第6张票.
......
一号窗口,出售 第100张票.
二号窗口,出售 第101张票.//消费出第101张

四:多线程死锁

/**
 * @description TODO
 * @DATE 2019/8/8 8:55
 **/
public class ThreadTrain6 implements Runnable {
    private int trainCount=100;
    public boolean flag=true;
    private Object mutex=new Object();
    @Override
    public void run() {
        if (flag){
            while (true){
                synchronized (mutex){
                    // 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.
                    // 如果flag为true 先拿到 obj锁,在拿到this 锁、 才能执行。
                    // 如果flag为false先拿到this,在拿到obj锁,才能执行。
                    // 死锁解决办法:不要在同步中嵌套同步。
                    sale();
                }
            }
        }else{
            while (true){
                sale();
            }
        }
    }
    public synchronized void sale() {
        synchronized (mutex) {
            if (trainCount > 0) {
                try {
                    Thread.sleep(40);
                } catch (Exception e) {

                }
                System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
                trainCount--;
            }
        }
    }
}

五:多线程同步常见面试题
问:什么是多线程安全?
答:当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。做读操作是不会发生数据冲突问题。
问:如何解决多线程之间线程安全问题?
答:使用多线程之间同步或使用锁(lock)。
问:为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。被包裹的代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
问:什么是多线程之间同步?
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
问:什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包括起来。只能让当前一个线程进行执行,被包裹的代码执行完成之后才能释放所,让后才能让其他线程进行执行。
问:多线程同步的分类?
1.使用同步代码块?
synchronized(同一个数据){
可能会发生线程冲突问题
}
2.使用同步函数
在方法上修饰synchronized 称为同步函数
3.静态同步函数
方法上加上static关键字,使用synchronized 关键字修饰 为静态同步函数
静态的同步函数使用的锁是 该函数所属字节码文件对象
问:同步代码块与同步函数区别?
答:
同步代码使用自定锁(明锁)
同步函数使用this锁
问:同步函数与静态同步函数区别?
注意:有些面试会这样问:例如现在一个静态方法和一个非静态静态怎么实现同步?
答:
同步函数使用this锁
静态同步函数使用字节码文件,也就是类.class
问:什么是多线程死锁?
答:
同步中嵌套同步
解决办法:同步中尽量不要嵌套同步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值