1. java对线程的支持
java对线程的支持主要体现在Thread类以及Runable接口上,他们都位于java.lang包下,无论是Thread类还是Runable接口,它们都有public void run()方法,这个run方法为我们提供了线程实际工作时的代码,换句话说,我们的逻辑代码就可以写在run方法体中。
那么什么时候该用Thread,什么时候该用Runable呢
继承Thread实现的模式是 定义多个线程,各自完成各自的任务. 实现Runnable实现的模式是 定义多个线程,实现一个任务.其实在实现一个任务用多个线程来做也可以用继承Thread类来实现只是比较麻烦,一般我们用实现Runnable接口来实现,简洁明了。大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。
2. 线程的常用方法
3. 如何正确地停止线程
不要调用线程的stop()方法来停止线程,调用stop方法停止线程会有一种戛然而止的感觉,你并不知道它是执行到哪一步停止的,也不知道还有什么逻辑没执行完。应该设置一个结束标志来结束线程。也不要调用interrupt()方法来停止线程,因为interrupt()方法的初衷并不是用于停止线程的,而是用于中断线程。
4 interrupt
① interrupt的作用就是中断线程,调用interput()方法会设置线程的中断状态。
② 然后调用线程的interrupted()【这是一个静态方法】方法或isInterrupted()【这是一个实例方法】方法将会返回一个boolean值(线程的中断状态)。
③ 当一个线程,调用join,sleep方法而被阻塞时,会使线程的中断状态被清除,然后后面调用interrupted()或isInterrupted()方法都不能得到一个正确的结果,并且当前线程会收到一个InterruptedException异常,这也就是为什么我们需要在代码sleep方法和join方法中进行try catch的原因。
package com.glassbox.thread.demo;
/**
* @auther xiehuaxin
* @create 2018-08-08 10:16
* @todo
*/
public class InterruptTest extends Thread {
public static void main(String[] args) {
System.out.println("begin>>>>>>>>>>>>>>>");
Thread interruptTest = new InterruptTest();
interruptTest.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("interrupt Thread>>>>>>>");
interruptTest.interrupt();
System.out.println("stop>>>>>>>>>>>>>>>>");
}
@Override
public void run() {
while (true) {
System.out.println("Thread is running...");
/**
* 这里用这种方式代替sleep是因为调用sleep的时候会改变当前线程的“中断状态”,从而抛出一个InterreptException异常
*/
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 1000) {
}
}
}
}
输出: 可以看到,当调用interrupt方法后,线程仍在输出
begin>>>>>>>>>>>>>>>
Thread is running...
Thread is running...
Thread is running...
Thread is running...
interrupt Thread>>>>>>>
stop>>>>>>>>>>>>>>>>
Thread is running...
Thread is running...
Thread is running...
Thread is running...
可以把isInterrupted()的返回结果当做结束标志
@Override
public void run() {
while (!this.isInterrupted()) {
System.out.println("Thread is running...");
/**
* 这里用这种方式代替sleep是因为调用sleep的时候会改变当前线程的“中断状态”,从而抛出一个InterreptException异常
*/
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 1000) {
}
}
}
5. 线程中的能量不守恒
“争用条件”:当多个线程同时共享访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏(corrupted),这种现象称为争用条件。
package com.glassbox.thread.demo2;
/**
* @auther xiehuaxin
* @create 2018-08-08 10:52
* @todo
*/
/**
* 宇宙的能量系统
* 遵循能量守恒定律
* 能量不会凭空产生也不会凭空消失,只会从一处转移到另一处
*/
public class EnergySystem {
//能量盒子,能存储能量的地方
private final double[] energyBox;
/**
*
* @param n 能量盒子的数量
* @param initalEnergy 每个能量盒子初始含有的能量值
*/
public EnergySystem(int n, double initalEnergy) {
this.energyBox = new double[n];
for (int i = 0; i < energyBox.length; i++) {
energyBox[i] = initalEnergy;
}
}
/**
* 能量的转移,从一个盒子转移到另外一个盒子
* @param from 能量源
* @param to 能量终点
* @param amount 要转移的能量值
*/
public void transfer(int from, int to, double amount) {
if(energyBox[from] < amount) {
return;
}
System.out.println(Thread.currentThread().getName());
energyBox[from] -= amount;
System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
energyBox[to] += amount;
System.out.printf("能量总和:%10.2f%n", getTotalEnergies());
}
/**
* 获取能量世界的能量总和
* @return
*/
private double getTotalEnergies() {
double sum = 0;
for (double amount : energyBox) {
sum += amount;
}
return sum;
}
/**
* 返回能量盒子的长度
* @return
*/
public int getBoxAmount() {
return energyBox.length;
}
}
package com.glassbox.thread.demo2;
/**
* @auther xiehuaxin
* @create 2018-08-08 11:48
* @todo
*/
public class EnergyTransferTask implements Runnable {
//共享的能量世界
private EnergySystem energySystem;
//能量转移的能源盒子下班
private int fromBoxIndex;
//单次能量转移最大单元
private double maxAmount;
//最大休眠时间(毫秒)
private int DELAY = 10;
public EnergyTransferTask(EnergySystem energySystem, int fromBoxIndex, double max) {
this.energySystem = energySystem;
this.fromBoxIndex = fromBoxIndex;
this.maxAmount = max;
}
@Override
public void run() {
try {
while (true) {
int toBox = (int) (energySystem.getBoxAmount() * Math.random());
double amount = maxAmount * Math.random();
energySystem.transfer(fromBoxIndex, toBox, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.glassbox.thread.demo2;
/**
* @auther xiehuaxin
* @create 2018-08-08 11:59
* @todo
*/
public class EnergySystemTest {
//要构建的能量世界的盒子的数量
public static final int BOX_AMOUNT = 100;
//每个盒子的初始能量
public static final double INITIAL_ENERGY = 1000;
public static void main(String[] args) {
EnergySystem energySystem = new EnergySystem(BOX_AMOUNT,INITIAL_ENERGY);
for (int i = 0; i < BOX_AMOUNT; i++) {
EnergyTransferTask task = new EnergyTransferTask(energySystem,i,INITIAL_ENERGY);
Thread thread = new Thread(task,"TransferThread:" + i);
thread.start();
}
}
}
输出
从38转移 174.83单位能量到0能量总和: 100000.00
TransferThread:38
从38转移 27.28单位能量到9能量总和: 100000.00
TransferThread:52
TransferThread:98
从98转移 584.27单位能量到49能量总和: 99390.63
TransferThread:98
从98转移 126.47单位能量到20能量总和: 99390.63
TransferThread:98
从98转移 264.87单位能量到98能量总和: 99390.63
TransferThread:74
从74转移 873.16单位能量到42能量总和: 99390.63
TransferThread:50
从50转移 439.71单位能量到47能量总和: 99390.63
TransferThread:70
从70转移 29.80单位能量到34能量总和: 99390.63
能量总和: 100000.00
TransferThread:75
从75转移 377.40单位能量到78能量总和: 99390.63
TransferThread:95
从95转移 74.24单位能量到76能量总和: 99390.63
能量总和: 100000.00
TransferThread:71
从71转移 285.92单位能量到10能量总和: 99390.63
TransferThread:83
从83转移 778.90单位能量到66能量总和: 99390.63
TransferThread:46
从46转移 515.10单位能量到63能量总和: 99390.63
从52转移 609.37单位能量到40能量总和: 100000.00
6. 那么如何保持能量守恒呢
互斥与同步
互斥:字面意思是互相排斥,实际含义是,同一时间只能有一个线程对临界区或关键数据进行操作(通过锁)。
同步:线程间的一种通信机制。一个线程它可以做一件事情,然后它可以用某种方式告诉别的线程它已经做完了(notify,wait,notifyAll)。
package com.glassbox.thread.demo2;
/**
* @auther xiehuaxin
* @create 2018-08-08 10:52
* @todo
*/
/**
* 宇宙的能量系统
* 遵循能量守恒定律
* 能量不会凭空产生也不会凭空消失,只会从一处转移到另一处
*/
public class EnergySystem {
//能量盒子,能存储能量的地方
private final double[] energyBox;
//添加一个锁的对象
private final Object lockObj = new Object();
/**
*
* @param n 能量盒子的数量
* @param initalEnergy 每个能量盒子初始含有的能量值
*/
public EnergySystem(int n, double initalEnergy) {
this.energyBox = new double[n];
for (int i = 0; i < energyBox.length; i++) {
energyBox[i] = initalEnergy;
}
}
/**
* 能量的转移,从一个盒子转移到另外一个盒子
* @param from 能量源
* @param to 能量终点
* @param amount 要转移的能量值
*/
public void transfer(int from, int to, double amount) {
/**
* 通过对lockObj对象加锁实现互斥
*/
synchronized (lockObj) {
//这里当能量不足的时候就退出,但是在退出之后,这条线程仍然有机会去获取cpu资源,从而再次要求加锁,但是加锁操作时有开销的,这样会降低系统的性能,所以当条件不满足的时候我们可以让这条线程等待,从而降低这条线程获取锁的开销
/*if(energyBox[from] < amount) {
return;
}*/
/**
* while循环,保证条件不满足时任务都会被阻挡,而不是竞争cpu资源
*/
while (energyBox[from] < amount) {
try {
//当线程调用lockObj对象的wait()方法之后,当前的线程就会被置于lockObj对象的“等待集合wait set”中
lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
energyBox[from] -= amount;
System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
energyBox[to] += amount;
System.out.printf("能量总和:%10.2f%n", getTotalEnergies());
//唤醒所有在lockObj对象上等待的线程
lockObj.notifyAll();
}
}
/**
* 获取能量世界的能量总和
* @return
*/
private double getTotalEnergies() {
double sum = 0;
for (double amount : energyBox) {
sum += amount;
}
return sum;
}
/**
* 返回能量盒子的长度
* @return
*/
public int getBoxAmount() {
return energyBox.length;
}
}
输出
从68转移 765.10单位能量到80能量总和: 100000.00
TransferThread:69
从69转移 564.22单位能量到33能量总和: 100000.00
TransferThread:64
从64转移 941.32单位能量到96能量总和: 100000.00
TransferThread:96
从96转移 511.90单位能量到74能量总和: 100000.00
TransferThread:4
从4转移 424.99单位能量到70能量总和: 100000.00
TransferThread:7
从7转移 429.73单位能量到33能量总和: 100000.00
7. 如何扩展java并发编程的知识
① 了解Java Memory Model
JMM描述了java线程如何通过内存进行交互的(通过这部分学习可以了解什么是happens-before原则)
happens-before
synchronized,volatile&final(java是如何通过这几个关键字来实现happens-before原则的)
② Locks对象和Condition对象(通过这两个对象可以了解如何对程序进行加锁以及同步的通信)
java锁机制和等待条件的高层实现
java.util.concurrent.locks
③ 线程安全性
原子性、可见性
java.util.concurrent.atomic(如何通过这个包来避免原子性编程的问题)
synchronized & volatile(当一个原子操作由多个语句构成时,又是如何通过synchronized实现原子性操作的)
DeadLocks
④ 多线程常用的交互模型(在java并发实现当中,有哪些类是实现了这些模型,可以供我们直接调用的)
Producer-Consumer模型
Read-Write Lock模型
Future模型
Woker Thread模型
⑤ Java5引入的并发编程工具
java.util.concurrent
线程池ExecutorService
Callable & Future
BlockingQueue
最后推荐两边书
练习巩固
题目:创建两个线程,一个线程输出1、3、5、…、99,另一个线程输出2、4、6、…、100。最后的效果是两个线程相互交替输出,达到输出1、2、3、4、…、99、100这样顺序输出的效果。
以下是我自己的实现:
package com.glassbox.thread.test;
/**
* @auther xiehuaxin
* @create 2018-08-08 16:08
* @todo
*/
public class PrintNum implements Runnable {
int i = 1;
@Override
public void run() {
while (true) {
//如果不是使用实现Runnable这种方式创建线程,这里不要用this(自己创建一个锁对象)
synchronized (this) {
//这里先调用锁对象的notify方法再调用wait方法是巧妙之处,大家细细体会,如果还不明白的话可以把notify方法放到wait方法后,然后运行输出结果就一目了然了
notify();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + i);
i++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
package com.glassbox.thread.test;
import java.util.concurrent.*;
/**
* @auther xiehuaxin
* @create 2018-08-08 15:41
* @todo
*/
public class ThreadExercise {
/**
* 任务队列
*/
private static ArrayBlockingQueue workQueue = new ArrayBlockingQueue(10);
public static void main(String[] args) {
/*
线程池最大数量
*/
int maximumPoolSizeSize = 100;
/*
线程活动保持时间
*/
long keepAliveTime = 1;
/*
线程池的基本大小
*/
int corePoolSize = 10;
//建议手动创建线程池并且以线程池的形式创建线程
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSizeSize, keepAliveTime, TimeUnit.SECONDS, workQueue, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("hahaThread");
return thread;
}
});
PrintNum num = new PrintNum();
executor.execute(num);
executor.execute(num);
}
}