Java多线程4

54 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第33天,点击查看活动详情

3.2、卖票案例的问题

  • 卖票出现了问题

    • 相同的票出现了多次

      image-20220812232526428

    • 出现了负数的票

      image-20220812232510297

  • 问题产生原因

    线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

3.3、同步代码块解决数据安全问题【应用】

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

    • 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境
  • 怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

    • Java提供了同步代码块的方式来解决

  • 同步代码块格式:

    synchronized(任意对象) { 
    	多条语句操作共享数据的代码 
    }
    

    synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

  • 同步的好处和弊端

    • 好处:解决了多线程的数据安全问题

    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

  • 代码演示

    public class SellTicket implements Runnable {
        private int tickets = 100;
        private Object obj = new Object();
    
        @Override
        public void run() {
            while (true) {
                synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
                    //t1进来后,就会把这段代码给锁起来
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                            //t1休息100毫秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //窗口1正在出售第100张票
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--; //tickets = 99;
                    }
                }
                //t1出来了,这段代码的锁就被释放了
            }
        }
    }
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            SellTicket st = new SellTicket();
    
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

3.4、同步方法解决数据安全问题【应用】

  • 同步方法的格式

    同步方法:就是把synchronized关键字加到方法上

    修饰符 synchronized 返回值类型 方法名(方法参数) { 
    	方法体;
    }
    

    同步方法的锁对象是什么呢?

    ​ this

  • 静态同步方法

    同步静态方法:就是把synchronized关键字加到静态方法上

    修饰符 static synchronized 返回值类型 方法名(方法参数) { 
    	方法体;
    }
    

    同步静态方法的锁对象是什么呢?

    ​ 类名.class

  • 代码演示

    public class MyRunnable implements Runnable {
        private static int ticketCount = 100;
    
        @Override
        public void run() {
            while(true){
                if("窗口一".equals(Thread.currentThread().getName())){
                    //同步方法
                    boolean result = synchronizedMthod();
                    if(result){
                        break;
                    }
                }
    
                if("窗口二".equals(Thread.currentThread().getName())){
                    //同步代码块
                    synchronized (MyRunnable.class){
                        if(ticketCount == 0){
                           break;
                        }else{
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            ticketCount--;
                            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                        }
                    }
                }
    
            }
        }
    
        private static synchronized boolean synchronizedMthod() {
            if(ticketCount == 0){
                return true;
            }else{
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                return false;
            }
        }
    }
    
    public class Demo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();     
      Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
    
        t1.setName("窗口一");
        t2.setName("窗口二");
    
        t1.start();
        t2.start();
    }  
    }
    

3.5、Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

  • ReentrantLock构造方法

    方法名说明
    ReentrantLock()创建一个ReentrantLock的实例
  • 加锁解锁方法

    方法名说明
    void lock()获得锁
    void unlock()释放锁
  • 代码演示

    public class Ticket implements Runnable {
        //票的数量
        private int ticket = 100;
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock();
                    if (ticket <= 0) {
                        //卖完了
                        break;
                    } else {
                        Thread.sleep(100);
                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    public class Demo {
        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();
        }
    }