深入浅出Java多线程

时间:2023-01-27 20:47:40

进程与线程

进程:程序(任务)的执行过程(动态的),持有资源(共享内存,共享文件)和线程

如在我们电脑上的QQ,如果只是放在那里,并不是进程,只有当你点击它运行后,才启动了一个进程。

线程:如QQ,可以文字聊天,同时首发文件,这就是2个线程。

线程是系统中最小的执行单元,同一个进程可以拥有多个线程,线程共享进程的资源。

线程的交互:互斥(竞争)与同步(合作)

如,在同一个班级中,每个同学都是一个线程,大家共享公共资源如桌椅,黑板,学习资料等。同学间可以交流,关系有互斥与同步。如当同学A与同学B同时向使用某个学习资料时,就产生了互斥,需要一个一个使用;当学校组织文艺汇演时,需要同学间互相合作,才能表演优秀的节目。

 

 Java线程之初体验

  • Java对线程的支持
    • Thread类与Runnable接口,都有run()方法
  • 线程的创建和启动
  • 线程常用方法
    • 深入浅出Java多线程
  •  

以隋唐英雄演绎这个实战项目来进一步了解Java的多线程

  • 根据故事梗概,我们需要“军队-ArmyRunnable”,“英雄人物-KeyPersonThread”,“舞台-Stage”这三个类;
  • 深入浅出Java多线程深入浅出Java多线程
    /**
    * 军队线程
    * 模拟作战双方的行为
    *
    @author
    *
    */
    public class ArmyRunnable implements Runnable {
    //volatile(可见性 JMM, happens-before原则)保证线程可以正确读取其他线程写入的值
    volatile boolean keepRunning = true;

    @Override
    public void run() {
    // TODO Auto-generated method stub
    while(keepRunning) {
    //发动5连击
    for(int i=0; i<5; i++) {
    System.out.println(Thread.currentThread().getName()
    +
    "进攻对方第[" + i + "]击");
    //让出了CPU时间,下次该谁进攻还不一定呢!
    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()
    + "结束了战斗!");
    }
    }


    /**
    * 隋唐演义的大戏舞台
    *
    @author
    *
    */
    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) {
    // TODO Auto-generated catch block
    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) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }

    //历史大戏留给关键人物
    mrCheng.start();
    try {
    mrCheng.join();
    }
    catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }

    System.out.println(
    "战争结束,人民安居乐业,程先生实现了积极的人生梦想,为人民作出了贡献");
    System.out.println(
    "谢谢观看隋唐演义,谢谢!");

    }

    public static void main(String[] args) {
    // TODO Auto-generated method stub
    new Stage().start();
    }

    }
    深入浅出Java多线程

     

  • 如何正确的停止线程
    • stop()方法就是一个不正确的停止线程的方法;它会使线程戛然而止,我们不知道它完成了什么工作,没有完成什么工作,因此是错误的。
    • 使用退出旗标:如上面的例子中keepRunning就是一个结束标志。
    • interrupt()方法:初衷并不是用于停止线程

 

   Java线程交互

  • 消失的能量
    • 什么是争用条件(Race Condition)
      • 当多个线程同时访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据破坏(corrupted),这种现象称为争用条件。
      • 深入浅出Java多线程
      • 如上图所示,当某个时间片时,线程1获得了CPU时间,从主存中读取了energyBox[to]的值,然后加上500,但还没有写回内存时,CPU时间耗尽;然后线程2抢到了CPU,读取的energyBox[to]的值仍为5000,然后它操作加上900,并写回内存5900,然后CPU时间耗尽。而下面又是线程1获得了时间片,将它上面计算完的5500写入内存,导致最终内存中存储的值为5500.而通过线程1和线程2的修改,最终写入内存的energyBox[to]的值应该为6400才是正确的!经过这样的方式运行,就发生了能量不守恒的结果。
      • 那么,如何修改代码,使能量变得守恒呢?
        • 互斥与同步,得到线程正确的进行交互。
        • 互斥:关键数据在同一时间只能被一个线程访问。实现:synchronized(intrinsic lock)
        • 同步:由于资源某些条件不满足,导致所有的线程陷入等待状态;而当访问资源的条件满足后,会唤醒所有的线程,进入互斥状态。实现:wait()/notify()/notifyAll()——都是Object对象的方法。
        • wait()/notify()/notifyAll()不是在同一个线程的同一次操作中执行的。因为同步是指多个线程之间的同步,只有一个线程的话是不需要同步的!!
        • 当一个线程要访问一个临界区(Critical Section)时,首先要获得该临界区的锁,当获得了锁后去判断是否满足获得该资源的条件,当条件不满足时,将释放锁(使得其他线程可以进入临界区),并进入该锁对象上的等待(wait())队列中等待。当其它线程使用完了临界区后,将告知(notifyAll())所有等待队列中的线程:目前临界区内的条件有变化,大家可以开始竞争锁对象。

 

 

    要点回顾

  • 如何创建线程及线程的基本操作;
  • 可见性及volatile关键字;
  • 争用条件;
  • 线程的互斥synchronized;
  • 线程的同步wait/notifyAll();

 

  扩展

  • Java Memory Mode
    • JMM描述了Java线程如何通过内存进行交互
    • happens-before原则
    • synchronized, volatile & final
  • Locks & 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
    • 线程池ExcecutorService
    • Callable & Future
    • BlockingQueue