zoukankan      html  css  js  c++  java
  • 多线程-线程安全问题

    线程安全问题

    在多个线程同时访问一个相同的资源的时候会发生线程安全问题。
    举个栗子:
    买票问题,三个窗口进行买票。

    public class ThreadSafe {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
            Thread t1 = new Thread(ticket);
            Thread t2 = new Thread(ticket);
            Thread t3 = new Thread(ticket);
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    class Ticket implements Runnable{
        
        private int ticket = 10;//有10张票
        
        @Override
        public void run() {
            while (true){
                if(ticket > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "窗口:" + ticket--);
                }else {
                    break;
                }
            }
        }
    }
    

    运行结果:

    很明显可以看出,在三个线程同时去访问Ticket类的时候,票的数量出现的重复错误(结果为0)的情况。

    为什么会出现这种情况呢?

    因为线程是并发的,并发就是三个线程同时进行。比如窗口一进入run方法,然后窗口二也进入了run方法,然后两个同时操作ticket的数量,所以数量出现重复。

    如何解决呢?

    解决线程安全有3中方法:使用同步代码块、同步方法、Lock锁。

    1、同步代码块

    同步代码块就是将操作共享资源的代码放入由synchronized修饰的代码块中。

    在使用同步代码块的时候需要使用一个锁将代码块锁起来,只允许一个线程进行访问。线程在进行操作数据前获得锁,操作结束后将锁释放。

    任何对象都可以是锁对象,但是锁对象必须是唯一的
    在这里我使用了当前这个对象来作为锁对象,因为我只声明了一个ticket对象,该对象是唯一的。
    也可以在Ticket类中声明一个对象,作为锁对象。

    public class ThreadSafe {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
            Thread t1 = new Thread(ticket);
            Thread t2 = new Thread(ticket);
            Thread t3 = new Thread(ticket);
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    class Ticket implements Runnable{
    
        private int ticket = 10;//有10张票
        //Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                synchronized (this) {//或者使用synchronized(obj)
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "卖:" + ticket-- + "号票");
                    } else {
                        break;
                    }
                }
            }
        }
    }
    
    

    运行结果:

    有结果可以看到,我们加上同步代码块之后,显示的结果就是正确的。

    2、同步方法

    在方法上加上synchronized关键字即可。同步方法默认的锁对象是当前对象即this对象。

    class Ticket implements Runnable{
    
        private int ticket = 10;//有10张票
    
        @Override
        public void run() {
            while (true) {
                sell();
            }
        }
        private synchronized void sell(){
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖:" + ticket-- + "号票");
            }
        }
    }
    

    运行结果:

    注意:在使用继承Thread类来创建线程的时候同步方法和同步代码块也会出现安全问题。因为默认使用this为锁对象,在运行的时候创建了三个ticket对象,所以三个线程使用的锁对象不一样。
    这里使用同步代码块和同步方法的结果都是一样的,这里就不展示同步方法的代码了。

    public class ThreadSafe {
        public static void main(String[] args) {
    
            Ticket t1 = new Ticket();
            Ticket t2= new Ticket();
            Ticket t3 = new Ticket();
    
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    class Ticket extends Thread{
    
        private int ticket = 10;//有10张票
    
        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "卖:" + ticket-- + "号票");
                    } else {
                        break;
                    }
                }
            }
        }
    }
    

    运行结果:

    从结果可以看出来,每个窗口都卖了10张票,因为三个线程有着不同的锁。
    解决办法就是使用当前类作为锁对象。

    synchronized(Ticket.class){
        //执行语句...
    }
    

    为什么使用Ticket.class可以呢?
    因为Ticket.class返回的是一个Class类的对象。该对象时唯一的。

    3、使用Lock锁

    使用Lock锁来解决线程安全问题时,需要使用到Lock对象中的两个方法:
    lock() 获得锁
    unlock() 释放锁

    class Ticket extends Thread{
    
        private int ticket = 10;//有10张票
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                lock.lock();
                try {//使用try-finally可以保证所被释放
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "卖:" + ticket-- + "号票");
                    } else {
                        break;
                    }
                }finally {
                    lock.unlock();
                }
            }
        }
    }
    

    运行结果:

    Lock和synchronized的区别?
    synchronized会自动释放锁,Lock需要手动启动锁和释放锁。

    三种方式优先级

    Lock锁 > 同步代码块 > 同步方法

  • 相关阅读:
    B树、B-树、B+树、B*树介绍,和B+树更适合做文件索引的原因
    异步请求数据加载到表格后根据不同状态改变表格背景颜色【表格背景色】
    Linux/windows查看设置环境变量指令
    【周期性执行事件】MySQL事件(Event)&任务调度
    DEDE列表页调用TAG标签
    poj2488 A Knight's Journey
    [置顶] Codeforces Round #190 (Div. 2)(完全)
    SharePoint 2010 用Event Receiver将文件夹自动变成approved状态 (2)
    .NET领域驱动设计—初尝(三:穿过迷雾走向光明)
    Android解决异常apk on device '0292bea1': Unable to open sync connection!
  • 原文地址:https://www.cnblogs.com/Z-Dey/p/12892216.html
Copyright © 2011-2022 走看看