慕课网 - 深入浅出Java多线程

课程地址:深入浅出Java多线程
课程讲师:Arthur

进程

  • 程序(任务)的执行过程 (动态性-当双击运行)

  • 持有资源(共享内存,共享文件)和线程(进程是资源的载体,也是线程的载体,脱离进程去谈论线程没有意义;)

  • 线程是系统最小的执行单元

  • 同一进程中有多个线程

  • 线程共享进程的资源

线程交互方式:互斥,同步。

011.png
/**
 * 军队线程
 * 模拟作战双方的行为
 */
public class ArmyRunnable implements Runnable {

    //volatile保证了线程可以正确的读取其他线程写入的值
    //可见性 ref JMM, happens-before原则
    volatile boolean keepRunning = true;

    @Override
    public void run() {
        while(keepRunning){
            //发动5连击
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"进攻对方["+i+"]");
                //让出了处理器时间,下次该谁进攻还不一定呢!
                Thread.yield();
            }
                    
        }
        
        System.out.println(Thread.currentThread().getName()+"结束了战斗!");

    }

}
/**
  * 英雄任务
  */
public class KeyPersonThread extends Thread {

    public void run(){
        System.out.println(Thread.currentThread().getName()+"开始了战斗!");
        
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"左突右杀,攻击隋军...");
        }
        System.out.println(Thread.currentThread().getName()+"结束了战斗!");
    }
}
/**
 * 隋唐演义大戏舞台
 */
public class Stage extends Thread {

    public void run(){
        
        System.out.println("欢迎观看隋唐演义");
        //让观众们安静片刻,等待大戏上演
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println("大幕徐徐拉开");
        
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        
        System.out.println("话说隋朝末年,隋军与农民起义军杀得昏天黑地...");
        
        ArmyRunnable armyTaskOfSuiDynasty = new ArmyRunnable();
        ArmyRunnable armyTaskOfRevolt = new ArmyRunnable();
        
        //使用Runnable接口创建线程
        Thread  armyOfSuiDynasty = new Thread(armyTaskOfSuiDynasty,"隋军");
        Thread  armyOfRevolt = new Thread(armyTaskOfRevolt,"农民起义军");
        
        //启动线程,让军队开始作战
        armyOfSuiDynasty.start();
        armyOfRevolt.start();
        
        //舞台线程休眠,大家专心观看军队厮杀
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("正当双方激战正酣,半路杀出了个程咬金");
        
        Thread  mrCheng = new KeyPersonThread();
        mrCheng.setName("程咬金");
        
        System.out.println("程咬金的理想就是结束战争,使百姓安居乐业!");
        
        //停止军队作战
        //停止线程的方法
        armyTaskOfSuiDynasty.keepRunning = false;
        armyTaskOfRevolt.keepRunning = false;
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        /*
         * 历史大戏留给关键人物
         */
        mrCheng.start();
        
        //万众瞩目,所有线程等待程先生完成历史使命
        try {
            mrCheng.join();   // 所有线程会等待调用join()方法的这个线程执行完成后,再执行后续内容
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("战争结束,人民安居乐业,程先生实现了积极的人生梦想,为人民作出了贡献!");
        System.out.println("谢谢观看隋唐演义,再见!");
        
    }
    
    public static void main(String[] args) {
        new Stage().start();
    }
}

如何正确的停止Java中的线程

  • 废弃stop()方法

  • 使用退出标志

  • interrupt()方法初衷并不是用于停止线程,isInterrupted() 和 interrupted()测试当前线程是否被中断

public class WrongWayStopThread extends Thread{

    public static void main(String[] args){
        WrongWayStopThread thread = new WrongWayStopThread();
        System.out.println("Starting thread ...");
        thread.start() ;

        try{
            Threaf.sleep(3000) ;
        }catch(InterruptedException e){
            e.printStackTrace() ;
        }

        System.out.println("Interrupting thread ...");
        thread.interrupt() ;    // 并不能使程序停下来

        try{
            Threaf.sleep(3000) ;
        }catch(InterruptedException e){
            e.printStackTrace() ;
        }

        System.out.println("Stopping thread ...");
    }

    public void run(){
    //  while(true){
        while(!this.isInterrupted()){  // 线程停下,其实相当于退出旗标的方法
            System.out.println("Thread is running ...");
        /*  long time = System.currentTimeMillis() ;
            while((System.currentTimeMillis()  - time) < 1000){
                // 减少屏幕输入的空循环
            }
        */
            // 换成等效代码,线程不能正常结束,且抛出异常。原因是:当现场调用某些方法进入阻塞状态,此时该线程再被调用interrupt()方法,会产生两个结果,第一是中断状态被清除(this.isInterrupted()),线程的isInterrupted()方法不能返回表示是否被中断的正确状态;第二是sleep()方法会收到一个InterruptedException异常
            Threaf.sleep(3000) ; 
        }
    }
}

推荐使用退出旗标的方式退出线程

线程的交互

/**
 * 宇宙的能量系统
 * 遵循能量守恒定律:
 * 能量不会凭空创生或消失,只会从一处转移到另一处
 */
public class EnergySystem {
    
    //能量盒子,能量存贮的地方
     private final double[] energyBoxes;
     private final Object lockObj = new Object();  // 锁对象
     
     /**
      * 
      * @param n    能量盒子的数量
      * @param initialEnergy 每个能量盒子初始含有的能量值
      */
     public EnergySystem(int n, double initialEnergy){
         energyBoxes = new double[n];
         for (int i = 0; i < energyBoxes.length; i++)
             energyBoxes[i] = initialEnergy;
     }
     
     /**
      * 能量的转移,从一个盒子到另一个盒子
      * @param from 能量源
      * @param to     能量终点 
      * @param amount 能量值
      */
     public void transfer(int from, int to, double amount){
         
         synchronized(lockObj){
             
//           if (energyBoxes[from] < amount)
//               return;
            //while循环,保证条件不满足时任务都会被条件阻挡
             //而不是继续竞争CPU资源
             // Wait set
             while (energyBoxes[from] < amount){
                 try {
                    //条件不满足, 将当前线程放入Wait Set
                    lockObj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             }
             
             
             System.out.print(Thread.currentThread().getName());
             energyBoxes[from] -= amount;
             System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
             energyBoxes[to] += amount;
             System.out.printf(" 能量总和:%10.2f%n", getTotalEnergies());
            //唤醒所有在lockObj对象上等待的线程
             lockObj.notifyAll();
         }
         
     }
     
     /**
      * 获取能量世界的能量总和 
      */
     public double getTotalEnergies(){
         double sum = 0;
         for (double amount : energyBoxes)
             sum += amount;
         return sum;
     }
     
     /**
      * 返回能量盒子的长度
      */
     public  int getBoxAmount(){
         return energyBoxes.length;
     }
     
}
public class EnergyTransferTask implements Runnable{

    //共享的能量世界
    private EnergySystem energySystem;
    //能量转移的源能量盒子下标
    private int fromBox;
    //单次能量转移最大单元
    private double maxAmount;
    //最大休眠时间(毫秒)
    private int DELAY = 10;
    
    public EnergyTransferTask(EnergySystem energySystem, int from, double max){
        this.energySystem = energySystem;
        this.fromBox = from;
        this.maxAmount = max;
    }
    
    public void run() {
        try{
            while (true){
                int toBox = (int) (energySystem.getBoxAmount()* Math.random());
                double amount = maxAmount * Math.random();
                energySystem.transfer(fromBox, toBox, amount);
                Thread.sleep((int) (DELAY * Math.random()));
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}
public class EnergySystemTest {

    //将要构建的能量世界中能量盒子数量
    public static final int BOX_AMOUNT = 100;
    //每个盒子初始能量
    public static final double INITIAL_ENERGY = 1000;

    public static void main(String[] args){
        EnergySystem eng = new EnergySystem(BOX_AMOUNT, INITIAL_ENERGY);
        for (int i = 0; i < BOX_AMOUNT; i++){
            EnergyTransferTask task = new EnergyTransferTask(eng, i, INITIAL_ENERGY);
            Thread t = new Thread(task,"TransferThread_"+i);
            t.start();
        }
    }

}

争用条件:当多个线程同时共享同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏(corrupted),这种现象称为争用条件。

012.png

线程交互:互斥与同步

互斥:同一时间只能有一条线程对我们的关键数据或临界区进行操作。

同步:线程之间的通信机制,如某一线程做了一件事,用某种方式告诉其他的线程做完了。

同步实现:wait() / notify() / notifyAll()

如何扩展Java并发的知识

  • Java Memory Mode

    • JMM描述了Java线程如何通过内存进行交互
    • happens-before原则
    • synchronized、volatile & final怎么实现这一原则
  • Lock & Condition

    • Java锁机制和等待条件的高层实现
    • java.util.concurrent.locks
  • 线程安全性

    • 原子性与可见性
    • java.util.concurrent.atomic
    • synchronized & volatile
    • DeadLocks
  • 多线程编程常用的交互模型

    • Producer-Consumer模型
    • Read-Write Lock模型
    • Future模型
    • Worker Thread模型
  • Java5中并发编程工具

    • java.util.concurrent
    • 线程池ExecutorService
    • Callable & Future
    • BlockingQueue

推荐阅读更多精彩内容