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常用方法
如下例子,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()
方法只能让同优先级的线程有执行的机会。
参考
- What are the main uses of yield(), and how does it differ from join() and interrupt()?
- Java Thread.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线程的停止
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());
}
}