美文网首页
多线程基础(四)

多线程基础(四)

作者: Maxinxx | 来源:发表于2019-03-07 12:15 被阅读0次

    题目描述

    某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

    方式一:继承Thread类

    public class SellTicket extends Thread {
        // 定义100张票,为了让多个线程共享这100张票,所以应该用静态修饰
        private static int tickets = 100;
    
        @Override
        public void run() {
            while (tickets > 0) {
                System.out.println(getName() + "正在出售第" + (100 - tickets-- + 1) + "张票");
            }
        }
    }
    
        public static void main(String[] args) {
            SellTicket st1 = new SellTicket();
            SellTicket st2 = new SellTicket();
            SellTicket st3 = new SellTicket();
            
            st1.setName("窗口1");
            st2.setName("窗口2");
            st3.setName("窗口3");
            
            st1.start();
            st2.start();
            st3.start();
        }
    
    运行结果

    方式二:实现Runnable接口

    推荐使用方式二来实现多线程。

    public class SellTicketInter implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            while (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (101 - tickets--) + "张票");
            }
        }
    }
    
        public static void main(String[] args) {
            SellTicketInter sti = new SellTicketInter();
            
            Thread t1 = new Thread(sti, "窗口1");
            Thread t2 = new Thread(sti, "窗口2");
            Thread t3 = new Thread(sti, "窗口3");
            
            t1.start();
            t2.start();
            t3.start();
        }
    

    改进:为了模拟真实情况,加入延时

    public class SellTicketInter implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            while (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (101 - tickets--) + "张票");
            }
        }
    }
    
    出现负数
    数据出现重复

    加入延迟后,就产生了两个问题:
    A:相同的票卖了多次

    原因:CPU的一次操作必须是原子性的。
    由于在SellTicketInter中需要执行tickets--这一项,计算机先记录tickets的原始值,然后tickets--,然后输出原始值,然后更新tickets值,CPU一共做了四步。但是在线程休眠那一行,可能有多个线程都处于休眠状态,当第一个线程醒过来的时候,正好做到输出tickets的值的时候,第二个线程也醒了,在更新tickets值之前醒过来了,所以第二个线程输出的也是原始值,所以这就导致了多个线程输出一样的数字,即相同的票卖了多次。

    B:出现了负数票

    随机性和延迟导致的。在还剩最后一张票的时候,多个线程进来了,然后处于休眠状态,然后第一个线程醒过来,输出正在卖第1张票,更新tickets值后,第二个线程醒过来,输出正在卖第0张票,更新tickets值后,第三个线程醒了过来,输出正在卖第-1张票。这就导致了出现负数的票。

    解决方法

    • 要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序会有线程安全问题的标准)
      A:是否是多线程环境
      B:是否有共享数据
      C:是否有多条语句操作共享数据
    • 上面的程序满足这三个原因,但是因为A和B的问题改变不了,所以只能改变原因C。
    • 思想:把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行即可。利用Java提供的同步机制
    同步代码块

    格式:

    synchronized(对象){需要同步的代码;}
    注意:同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能。多个线程必须是同一把锁。

    public class SellTicketInter implements Runnable {
        private int tickets = 100;
        //创建锁对象
        private Object obj = new Object();
        
        @Override
        public void run() {
            synchronized (obj) {
                while (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                }
            }
        }
    }
    
    同步的特点
    • 同步的前提
      多个线程
      多个线程使用的是同一个锁对象
    • 同步的好处
      同步的出现解决了多线程的安全问题。
    • 同步的弊端
      当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
    注意
    • 同步代码块的锁对象是谁?
      任意对象
    • 同步方法的格式及锁对象问题?
      把同步关键字加在方法上,该方法就变成了同步方法。
    private synchronized void sellTicket(){
            ...
    }
    

    问题:那么同步方法的锁对象是谁呢?
    答:this

    • 静态同步方法及锁对象问题?
      静态方法的锁对象根本不可能是this,因为静态方法随着类的加载而加载,这个时候this根本就不存在。所以静态方法的锁对象是类的字节码文件对象,即.class文件对象。(字节码的相关知识请参考“反射”)

    线程安全的类

    StringBuffer sb = new StringBuffer();
    Vector<String> v = new Vector<String>();
    Hashtable<String, String> h = new Hashtable<String, String>();
    

    Vector是线程安全的时候才去考虑用的,但实际上还是很少用,那么用谁呢(如何把一个线程不安全的集合类变成一个线程安全的集合类呢)?

    //public static <T> List<T> synchronizedList (List<T> list)
    List<String> list1 = new ArrayList<String>();//线程不安全
    List<String> list2 = Collections.synchronizedList(new ArrayList<String>());//线程安全
    

    相关文章

      网友评论

          本文标题:多线程基础(四)

          本文链接:https://www.haomeiwen.com/subject/cffcpqtx.html