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锁 > 同步代码块 > 同步方法

  • 相关阅读:
    Elementary Methods in Number Theory Exercise 1.3.13
    Elementary Methods in Number Theory Exercise 1.3.17, 1.3.18, 1.3.19, 1.3.20, 1.3.21
    数论概论(Joseph H.Silverman) 习题 5.3,Elementary methods in number theory exercise 1.3.23
    Elementary Methods in Number Theory Exercise 1.2.31
    数论概论(Joseph H.Silverman) 习题 5.3,Elementary methods in number theory exercise 1.3.23
    Elementary Methods in Number Theory Exercise 1.3.13
    Elementary Methods in Number Theory Exercise 1.3.17, 1.3.18, 1.3.19, 1.3.20, 1.3.21
    Elementary Methods in Number Theory Exercise 1.2.31
    Elementary Methods in Number Theory Exercise 1.2.26 The Heisenberg group
    4__面向对象的PHP之作用域
  • 原文地址:https://www.cnblogs.com/Z-Dey/p/12892216.html
Copyright © 2011-2022 走看看