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

多线程基础(四)

作者: 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