zoukankan      html  css  js  c++  java
  • 线程安全

    线程安全

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。

    程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    案例:

    卖票(只能卖100张票)

    初始:

    public static void main(String[] args) {
            //创建票对象
            Ticket ticket = new Ticket();
            
            //创建3个窗口
            Thread t1  = new Thread(ticket, "窗口1");
            Thread t2  = new Thread(ticket, "窗口2");
            Thread t3  = new Thread(ticket, "窗口3");
            
            t1.start();
            t2.start();
            t3.start();
        }
    }
    public class Ticket implements Runnable {
    //共100票
    int ticket = 100;
    
    @Override
    public void run() {
        //模拟卖票
        while(true){
            if (ticket > 0) {
                //模拟选坐的操作
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
            }
        }
    }

    结果:

    运行结果发现:上面程序出现了问题

    票出现了重复的票

    错误的票 0、-1

    总结:

    其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作。

    一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    -1和0的出现原因:

    当刚刚判断完还有票时CPU资源被其它线程占用进入休眠状态,当有空闲资源在进行时就不会再判断直接进行了减票操作就会出现0和-1的状况

    解决方式:

    线程同步

    利用(Synchronized)

    线程同步的方式有两种:

    方式1:同步代码块

    方式2:同步方法

    同步代码块(上厕所原理)

    在代码块声明上 加上synchronized

    synchronized (锁对象) {
        可能会产生线程安全问题的代码
    }

    注意:

    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

    public class Tickets implements Runnable{
    private /*static*/ int ticket=100;
    //对象锁
    private Object obj=new Object();
    public void run() {
        while(true){
            synchronized (obj) {
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                        System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
                }else{
                    return;
                }
            }
        }
    }

    原理:线程运行首先要拿到锁(obj)才能进行下面的程序,如果锁被占用那么只能等待,线程循环运行完后自动归还锁供下一个线程循环运行。

    同步方法

    在方法声明上加上synchronized

    public synchronized void method(){

        可能会产生线程安全问题的代码

    }

    注意:

    同步方法中的锁对象是 this

    ②同步锁别名:对象锁,对象监视器

    演示:

    private /*static*/ int ticket=100;
    public void run() {
        while(true){
            sale();
        }
    }
    public /*static*/ synchronized void sale(){
        if(ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
        }else{
            return;
        }
    }

    静态同步方法:

    在方法声明上加上static synchronized

    public static synchronized void method(){
    可能会产生线程安全问题的代码
    }

    静态同步方法中的锁对象是     类名.class

    补充:

    StringBuilder和StringBuffer真正区别体现:

    StringBuffer(方法都有synchronized,都是同步的方法,但这样就会降低速度)

    新方式:

    Lock接口

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

    常用方法:

    演示(还是拿上面的卖票为例):

    public class MewTicket implements Runnable{
        private int ticket=100;
        private Lock lock=new ReentrantLock();
        public void run() {
            while(true){
                lock.lock();
                if(ticket>0){
                    try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }finally{
                        //释放锁
                        lock.unlock();
                    }
                }
            }
        }
    }

    死锁现象

    同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。

    这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。

    synchronzied(A锁){
        synchronized(B锁){
    
        }
    }


    定义锁对象:
    public class lockA {
        private lockA(){
        }
        public static final lockA locka=new lockA();
    }
    public class lockB {
        private lockB(){
        }
        public static final lockB lockb=new lockB();
    }

    任务:

    public class deadlock implements Runnable{
        private int i=0;
        public void run() {
            while(true){
                if(i%2==0){
                    //偶数先进A再进B
                    synchronized (lockA.locka) {
                        System.out.println("if......lockA");
                        synchronized (lockB.lockb) {
                            System.out.println("if......lockB");
                        }
                    }
                }else{
                    //奇数先进B再进A
                    synchronized (lockB.lockb) {
                        System.out.println("else......lockB");
                        synchronized (lockA.locka) {
                            System.out.println("else......lockA");
                        }
                    }
                }
                i++;
            }
        }
    }

    创建线程:

    public static void main(String[] args) {
        deadlock d1=new deadlock();
        Thread t1=new Thread(d1);
        Thread t2=new Thread(d1);
        t1.start();
        t2.start();
    }

    等待唤醒机制

    线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。

    而这种手段即—— 等待唤醒机制。

    等待唤醒机制所涉及到的方法:

    ①wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

    ②notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

    ③notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

    其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。

    同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

    举例:

    分析:

    1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();

    2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。

    演示:

    public class Resouce {
        public String name;
        public String sex;
        public boolean flag=false;
    }

    输入:

    public class Input implements Runnable{
        private Resouce r;
        public Input(Resouce r) {
            super();
            this.r = r;
        }
        private int i=0;
        public void run(){
            while(true){
                //注意要用同一个锁对象,保证同步(r)
                synchronized (r) {
                    //标记为true
                    if(r.flag==true){
                        try {
                            //这些方法在使用时必须标明所属锁
                            r.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    if(i%2==0){
                        r.name="张三";
                        r.sex="男";
                    }else{
                        r.name="李四";
                        r.sex="nv";
                    }
                    //改变flag,唤醒Output
                    r.flag=true;
                    r.notify();
                }
                i++;
            }
        }
    }

    输出:

    public class Output implements Runnable{
        private Resouce r;
        public Output(Resouce r) {
            super();
            this.r = r;
        }
    
        public void run() {
            while(true){
                synchronized (r) {
                    //标记为false就wait
                    if(r.flag==false){
                        try {
                            r.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    //如果为true就打印
                    System.out.println(r.name+"..."+r.sex);
                    //更改flag唤醒input
                    r.flag=false;
                    r.notify();
                }
            }
        }
    }

    测试:

    public class text {
        public static void main(String[] args) {
            Resouce r=new Resouce();//定义同一个对象
            Input in=new Input(r);
            Output out=new Output(r);
            Thread tin=new Thread(in);
            Thread tout=new Thread(out);
            tin.start();
            tout.start();
        }
    }

    练习:

    有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300};

    创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”,随机从arr数组中获取奖项元素并打印在控制台上,格式如下:

    抽奖箱1 又产生了一个 10 元大奖

    抽奖箱2 又产生了一个 100 元大奖

    。。。。。。

    演示:

    定义奖池:

    public class jiangchi {
        public int[] arr={10,5,20,50,100,200,500,800,2,80,300};
        public boolean flag=false;
    }

    抽奖箱1:

    public class xiang1 implements Runnable{
        private jiangchi j;
        Random a=new Random();//创建随机数对象
        public xiang1(jiangchi j) {
            super();
            this.j = j;
        }
        public void run() {
            while(true){
                synchronized (j) {
                    if(j.flag==true){
                        try {
                            j.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }else{
                        //随机抽取奖品
                        int arr[]=j.arr;
                        int rdm=0;
                        int random = a.nextInt(11);
                        rdm=random;
                        System.out.println("抽奖箱1 又产生了一个 "+arr[rdm]+"元大奖");
                        j.flag=true;
                        j.notify();
                    }
                }
            }
        }
    }

    抽奖箱2:

    public class xiang2 implements Runnable{
        private jiangchi j;
        Random a=new Random();//创建随机数对象
        public xiang2(jiangchi j) {
            super();
            this.j = j;
        }
        public void run() {
            while(true){
                synchronized (j) {
                    if(j.flag==false){
                        try {
                            j.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }else{
                        //随机抽取奖品
                        int arr[]=j.arr;
                        int rdm=0;
                        int random = a.nextInt(11);
                        rdm=random;
                        System.out.println("抽奖箱2 又产生了一个 "+arr[rdm]+"元大奖");
                        j.flag=false;
                        j.notify();
                    }
                }
            }
        }
    }

    测试:

    public static void main(String[] args) {
        jiangchi jc=new jiangchi();
        xiang1 x1=new xiang1(jc);
        xiang2 x2=new xiang2(jc);
        Thread xiang1=new Thread(x1);
        Thread xiang2=new Thread(x2);
        xiang1.start();
        xiang2.start();
    }
     
  • 相关阅读:
    一个小时学会MySQL数据库
    4种解决json日期格式问题的办法
    一个小时学会Git
    docker 常用操作
    Fine-Grained Image (细粒度图像) – Papers, Codes and Datasets
    Pytorch在colab和kaggle中使用TensorBoard/TensorboardX可视化
    训练集,验证集,测试集比例
    深度学习模型评估指标
    注意力机制(Attention Mechanism)应用——自然语言处理(NLP)
    自注意力机制(Self-attention Mechanism)——自然语言处理(NLP)
  • 原文地址:https://www.cnblogs.com/qq1312583369/p/10248753.html
Copyright © 2011-2022 走看看