Java多线程

Java多线程

概念

Java内存模型
Java内存模型,请参考深入理解Java内存模型(一)——基础

这里写图片描述

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

  • 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  • 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

volatile 与 synchronized
请参考JAVA多线程之volatile 与 synchronized 的比较

volatile关键字的作用是:使变量在多个线程间可见(可见性)

synchronized的使用方法参考Java中synchronized的用法

JAVA多线程之线程间的通信方式
参考JAVA多线程之线程间的通信方式

Thread vs Runnable

Java创建线程的两种方式

  • 继承Thread
  • 实现Runnable接口

这里写图片描述

两种方式的比较:

  • Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
  • Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同意资源的情况

线程的生命周期
这里写图片描述

  • 创建-新建一个线程对象,如Thread mt1 = new MyThread("窗口一");
  • 就绪-创建线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行条件,但不一定已经开始运行了)
  • 运行-处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑
  • 终止-线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态(这种方法已被淘汰掉了)
  • 阻塞-一个正在执行的线程在某些情况下,由于某些原因而暂时让出了CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法

Java线程

以下内容来自于深入浅出Java多线程

进程和线程

进程是程序(任务)的执行过程,是一个动态的概览。持有资源(共享内存,共享文件)和线程。

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

Java语言对线程的支持主要体现在Thread类和Runnable这个接口中。都在java.lang包,都有一个run()方法。

Thread常用方法
Thread常用方法

如下例子,Actor继承自Thread

public class Actor extends Thread {

    @Override
    public void run() {
        System.out.println(getName() + "是一个演员");
        int count = 0;
        boolean keepRunning = true;

        while(keepRunning){
            System.out.println(getName()+"登台演出:"+ (++count));

            if (count == 100) {
                keepRunning = false;
            }

            if (count % 10 == 0) {
                try {
                    Thread.sleep(1000);//休眠1s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

        System.out.println(getName() + "的演出结束了!");
    }
}

Actress实现了Runnable了接口:

class Actress implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread() + "是一个演员");
        int count = 0;
        boolean keepRunning = true;

        while(keepRunning){
            System.out.println(Thread.currentThread()+"登台演出:"+ (++count));

            if (count == 100) {
                keepRunning = false;
            }

            if (count % 10 == 0) {
                try {
                    Thread.sleep(1000);//休眠1s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

        System.out.println(Thread.currentThread() + "的演出结束了!");
    }

}

创建actor、actressThread线程如下:

    Thread actor = new Actor();
    //设置名称
    actor.setName("Mr.Thread");
    //启动线程
    actor.start();

    Thread actressThread = new Thread(new Actress(), "Ms.Runnable");
    actressThread.start();

Thread.yield()方法

Java线程中有一个Thread.yield( )方法,很多人翻译成线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。

该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。

参考


volatile保证了线程可以正确的读取其他线程写入的值。

volatile boolean keepRunning;

如军队线程ArmyRunnable

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

    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()+"结束了战斗");
    }

}

其它线程写入keepRunning值:

armyTaskOfSuiDynasty.keepRunning = false;

join()方法会使所有线程等待执行join()的线程执行完毕。

Java Thread join方法用来暂停当前线程直到join操作上的线程结束。java中有三个重载的join方法:

public final void join():此方法会把当前线程变为wait,直到执行join操作的线程结束,如果该线程在执行中被中断,则会抛出InterruptedException。

public final synchronized void join(long millis):此方法会把当前线程变为wait,直到执行join操作的线程结束或者在执行join后等待millis的时间。因为线程调度依赖于操作系统的实现,因为这并不能保证当前线程一定会在millis时间变为RUnnable。

public final synchroinzed void join(long millis, int nanos):此方法会把当前线程变为wait,直到执行join操作的线程结束或者在join后等待millis+nanos的时间。

Java Thread join示例与详解

其它解释:

Java线程的停止

Java的stop()方法是不正确的停止线程的方法。
正确的方法是设置退出旗标:

package thread.base;

public class ArmyRunnable implements Runnable {

    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()+"结束了战斗");
    }

}

停止线程的方法:

armyTaskOfSuiDynasty.keepRunning = false;

广为流传的错误方法—interrupt方法

interrupt()方法的初衷并不是用于停止线程。interrupt方法会使中断状态发生改变。

文档如下:

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

如下的例子:

package thread;

public class WrongWayStopThread extends Thread{

    public static void main(String[] args) {
        WrongWayStopThread thread = new WrongWayStopThread();
        System.out.println("Starting thread...");
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Interrupting thread...");
        thread.interrupt();
        System.out.println("Stopping application");
    }

    @Override
    public void run() {
        while(true) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            while(System.currentTimeMillis()-time < 1000) {
                //减少屏幕输出的空循环
            }
        }
    }

}

观察输出结果,会发现interrupt之后,thread还在继续:

Starting thread...
Thread is running...
Thread is running...
Thread is running...
Thread is running...
Interrupting thread...
Stopping application
Thread is running...
Thread is running...

下面把run()方法的内容改为:

@Override
public void run() {
    while(!this.isInterrupted()) {
        System.out.println("Thread is running...");
        long time = System.currentTimeMillis();
        while(System.currentTimeMillis()-time < 1000) {
            //减少屏幕输出的空循环
        }
    }
}

使用this.isInterrupted()来获取状态,输出结果为:

Starting thread...
Interrupting thread...
Stopping application

判断是否启动:isAlive()


线程交互

本例子使用了能量守恒来说明争用条件,导致了能量不守恒

争用条件(Race Condition)
当多个线程同时共享访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏(corrupted),这种情况成为争用条件。

争用条件

使用互斥与同步
使用互斥与同步使我们的线程正确的交互,从而达到正确处理数据的要求。

什么是互斥?
互斥在同一时间只能有一条线程对我们的关键数据或者临界区进行操作

什么是同步?
同步是线程之间的一种通信机制。就是一条线程做了一件事情,然后会用某种方式去告诉其它的线程我做完了。

1.增加一个锁对象private final Object lockObj = new Object();
2.通过synchronized关键字实现互斥行为

     synchronized(lockObj){
         //不能满足条件操作时
         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();

互斥:关键数据在同一时间只能被一个线程所访问。
互斥实现:synchronized(intrinsic lock)
同步实现:wait()/notify()/notifyAll(),这些都是Object对象的方法。

wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意:在调用此方法的时,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
notityAll():唤醒所有处入等待状态的线程,注意:不是给所有唤醒线程一个对象的锁,而是让它们竞争

实例的完整代码如下:
EnergySystem.java代码

/**
 * 宇宙的能量系统
 * 遵循能量守恒定律:
 * 能量不会凭空创生或消失,只会从一处转移到另一处
 */
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){
             //不能满足条件操作时
             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;
     }

}

EnergyTransferTask代码

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();
        }
    }

}

EnergySystemTest测试代码

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();
        }
    }

}

控制台输出:

TransferThread_50从50转移    496.37单位能量到19 能量总和: 100000.00
TransferThread_49从49转移    172.51单位能量到37 能量总和: 100000.00
TransferThread_49从49转移    770.51单位能量到82 能量总和: 100000.00
......

守护线程

Java线程分为2类:

1.用户线程:运行在前台,执行具体的任务。例如,程序的主线程、连续网络的子线程等都是用户线程
2.守护线程:运行在后台,为其它前台线程服务,它的特定是,一旦所有的用户线程结束运行,守护线程会随JVM一起结束工作

守护线程的应用:

  • 数据库连接池中的监测线程
  • JVM虚拟机启动后的监测线程

如何设置守护线程?
通过Thread类的setDaemon(true)方法来设置当前的线程为守护线程。
要注意setDaemon(true)方法必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
在守护线程中产生的新线程也是守护线程
不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑

例子,如下,在守护线程中,进行写文件操作,当主线程结束时,写文件操作也会终止

/*
 * 守护线程
 */
class DeamonThread implements Runnable{
    @Override
    public void run() {
        System.out.println("进入守护线程 " + Thread.currentThread().getName());

        writeToFile();

        System.out.println("退出守护线程 " + Thread.currentThread().getName());
    }

    private void writeToFile(){
        File file = new File("file/demon.txt");
        try {
            OutputStream os = new FileOutputStream(file, true);//追加操作
            int count = 0;
            while(count < 999){
                os.write(("\r\nword"+count).getBytes());
                System.out.println("守护线程 " + Thread.currentThread().getName()
                        +"向文件中写入了word" + count++);
                TimeUnit.SECONDS.sleep(1);//休眠1s
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class DaemonThreadDemo {

    public static void main(String[] args) {
        System.out.println("程序进入主线程 " + Thread.currentThread().getName());

        DeamonThread deamonThread = new DeamonThread();
        Thread thread = new Thread(deamonThread, "守护线程");
        thread.setDaemon(true);//守护线程
        thread.start();

        //输入后,主线程结束掉
        Scanner scanner = new Scanner(System.in);
        scanner.next();


        System.out.println("程序退出主线程 " + Thread.currentThread().getName());
    }

}

参考

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值