慕课网 - 深入浅出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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容