• 多线程-线程安全问题


    线程安全问题

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

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

  • 相关阅读:
    想要快速上手 Spring Boot?看这些教程就足够了!| 码云周刊第 81 期
    Eclipse Java注释模板设置详解以及版权声明
    java跨域解决
    微信公众号创建
    Java与JS生成二维码与条形码
    Java基础break、continue语句的用法
    分布式文件系统介绍
    Hadoop的RPC机制及简单实现
    RPC简介与hdfs读过程与写过程简介
    JVM的Client模式与Server模式
  • 原文地址:https://www.cnblogs.com/Z-Dey/p/12892216.html
Copyright © 2020-2023  润新知