• Java学习笔记45(多线程二:安全问题以及解决原理)


    线程安全问题以及解决原理:

    多个线程用一个共享数据时候出现安全问题

    一个经典案例:

    电影院卖票,共有100座位,最多卖100张票,买票方式有多种,网上购买、自主售票机、排队购买

    三种方式操作同一个共享数据,这时候会出现安全问题:

    示例:

    package demo1;
    
    public class Tickets implements Runnable {
        private int ticket = 100;
        
        public void run(){
            while(true){
                if (ticket>0) {
                    System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
                }
            }
        }
    }
    package demo1;
    
    public class ThreadDemo {
        public static void main(String[] args) {
            Tickets t = new Tickets();
            Thread t0 = new Thread(t);
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            t0.start();
            t1.start();
            t2.start();
        }
    }

    一般不会出现问题,但是要想到这种问题

    但是,假设只剩下最后最后一张票,一个线程抢到CPU资源执行,在判断结束时候,CPU资源被其他线程抢到,其他线程判断然后执行,

    这时候轮到开始时候的线程,由于已经判断完,继续执行,这时候票数就会变成负数,这里就出现了问题

    解决方法:

    同步代码块

    原理:一个线程进入数据操作的时候,阻止其他线程执行

    package demo1;
    
    public class Tickets implements Runnable {
        private int ticket = 100;
        private Object obj1 = new Object();
        public void run() {
            while (true) {
                synchronized (obj1) {
                    if (ticket > 0) {
                        System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "张票");
                    }
                }
            }
        }
    }

    不过,虽然安全了,但是运行速度下降

    但是,我们为了安全性可以不顾及速度,无论如何都要保证安全性

    这里传入的对象参数简称作:同步锁,专业名称:对象监视器

    原理:

    没有锁的线程不能执行,只能等待

    线程遇到同步代码块后判断是否有同步锁,如果有,拿走锁,进入同步中执行,执行完毕后将锁对象还回去

    另一个线程遇到代码块后没有锁,无法进入,原来的线程把锁还回去之后新线程再获取锁,循环下去

    这里明显可以看出,这么多的过程,速度自然就慢下来了

    采用同步方法解决问题:

    优点:代码量更低

    package demo1;
    
    public class Tickets implements Runnable {
        private int ticket = 100;
        
        public void run() {
            while (true) {
                payTicket();
            }
        }
    
        public synchronized void payTicket() {
            //同步方法的对象锁是本类对象引用:即为this
            //静态方法的锁是本类类名.class
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "张票");
            }
        }
    }

     缺点:如果出现了异常,方法的锁对象没有释放,不出同步,锁不会释放

    这里就需要用到一个Lock接口:

    提供了更广泛的锁定操作

    改进之前的售票案例:

    package demo1;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Tickets implements Runnable {
        private int ticket = 100;
        private Lock lock = new ReentrantLock();
    
        public void run() {
            while (true) {
                lock.lock();
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "张票");
                }
                lock.unlock();
            }
        }
    }

    死锁:

    同步锁引发的弊端:

    当线程任务中出现了多个同步时,如果同步中嵌套了其他的同步,这时候就会引发一种现象,程序出现无限等待,这种现象称之为死锁

    通俗解释:两个人吃一碗面,却只有一双筷子,两个人一人抢到一支筷子,规定不能用手抓,这时候就无法吃面

    代码实现:

    package demo1;
    
    public class LockA {
        private LockA(){}
        
        public final static LockA locka =new LockA();
    }
    package demo1;
    
    public class LockB {
        private LockB(){}
        
        public final static LockB lockb =new LockB();
    }
    package demo1;
    
    public class DeadLock implements Runnable {
        private int i = 0;
    
        public void run() {
            while (true) {
                if (i % 2 == 0) {
                    synchronized (LockA.locka) {
                        System.out.println("if...locka");
                        synchronized (LockB.lockb) {
                            System.out.println("if...lockb");
                        }
                    }
                } else {
                    synchronized (LockB.lockb) {
                        System.out.println("else...lockb");
                        synchronized (LockA.locka) {
                            System.out.println("else...locka");
                        }
                    }
                }
                i++;
            }
        }
    }
    package demo1;
    
    public class DeadLockDemo {
        public static void main(String[] args) {
            DeadLock dead = new DeadLock();
            Thread t0 = new Thread(dead);
            Thread t1 = new Thread(dead);
            t0.start();
            t1.start();
        }
    }

    运行后发现,会卡在某一处不动,但是并没有停止

  • 相关阅读:
    ok6410驱动usb摄像头
    自己动手写CPU之第五阶段(1)——流水线数据相关问题
    ListView嵌套ListView时发生:View too large to fit into drawing cache的问题
    算法导论 第8章 线性时间排序(计数排序、基数排序、桶排序)
    Android_通过ContentObserver监听短信数据变化
    【MyEcplise】导入项目报错:Errors running builder 'JavaScript Validator' on project '项目名'. java.lang.ClassCastException
    【js】js中const,var,let区别
    【Node.js】2.开发Node.js选择哪个IDE 开发工具呢
    【Node.js】1.安装步骤
    【POI】对于POI无法处理超大xls等文件,官方解决方法【已解决】【多线程提升速率待定】
  • 原文地址:https://www.cnblogs.com/xuyiqing/p/8320162.html
Copyright © 2020-2023  润新知