一、进程:(QQ)
1、程序(任务)的执行过程
2、持有资源(共享内存,共享文件)和线程
二、线程:(文字聊天、收发文件)
三、线程之间的交互:
1、同步:协同完成某个进程
2、互斥:资源的使用
四、java对线程的支持:
1、java对线程的支持
1> Thread
2> Runnable
public void run();方法提供线程实际运行时执行的内容
package com.zy.concurrent;
public class Actor extends Thread {
// 覆写run方法
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 {
// 线程休眠1秒钟
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
System.out.println(getName() + "的演出结束了!");
}
public static void main(String[] args) {
Thread actor = new Actor();
actor.setName("Mr.Thread");
actor.start();
Thread actressThread = new Thread(new Actress(),"Ms.Runnable");
actressThread.start();
// cpu智能运行一个线程,当一个线程休眠的时候,另外一个线程才获得运行的机会
}
}
class Actress implements Runnable {
// 覆写run方法
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "是一个演员!");
int count = 0;
boolean keepRunning = true;
while (keepRunning) {
System.out.println(Thread.currentThread().getName() + "登台演出:" + (++count));
if (count == 100) {
keepRunning = false;
}
if (count % 10 == 0) {
try {
// 线程休眠1秒钟
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
System.out.println(Thread.currentThread().getName() + "的演出结束了!");
}
}
2、线程的创建、启动和常用方法
隋唐演义:(示例)
*隋唐演义大舞台*
package com.zy.concurrent.base;
/**
* 隋唐演义大戏舞台
*/
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();
}
armyTaskOfSuiDynasty.keepRunning = false;
armyTaskOfRevolt.keepRunning = false;
try {
armyOfRevolt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正当双方激战正酣,半路杀出了个程咬金");
Thread mrChen = new KeyPersonThread();
mrChen.setName("程咬金");
System.out.println("程咬金的立项就是结束战争,使百姓安居乐业!");
// 停止军队作战
// 停止线程的方法
armyTaskOfSuiDynasty.keepRunning = false;
armyTaskOfRevolt.keepRunning = false;
// 使军队作战真实停下来,舞台资源让给关键人物
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 历史大戏留给关键人物
mrChen.start();
// 所有人物等关键人物完成历史使命
try {
mrChen.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("战争结束,人们安居乐业,程先生实现了人生梦想");
System.out.println("隋唐演义结束,谢谢观看!");
}
public static void main(String[] args) {
new Stage().start();
}
}
一、模拟军队作战(继承runnable接口)
package com.zy.concurrent.base;
//军队线程
//模拟作战双方的行为
public class ArmyRunnable implements Runnable {
// volatile保证了线程可以正确的读取其他线程写入的值(军队停止进攻不是军队自己下达的命令,而是别的线程)
// 可见性 ref JMM;java内存模型
volatile boolean keepRunning = true;
@Override
public void run() {
while (keepRunning) {
// 发动五连击
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "进攻对方[" + i + "]");
// 使线程让出当前cpu处理器资源,再所有线程同时去竞争cpu资源
// 让出了处理器时间,下次该是谁进攻还不一定呢!
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName() + "结束了战斗!");
}
}
二、模拟关键人物作战(新建线程)
join()方法:其他线程会等调用了join方法的线程执行完毕
在控制程序结束的地方十分有用!
package com.zy.concurrent.base;
public class KeyPersonThread extends Thread {
// 覆写关键人物的run方法,关键人物业务代码
public void run() {
System.out.println(Thread.currentThread().getName() + "开始了战斗!");
// 普通军队只能进行5连击的攻击,而关键人物可以进行10连击的攻击
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "左突右杀,攻击隋军...");
}
System.out.println(Thread.currentThread().getName() + "结束了战斗!");
}
}
4、如何停止线程
一、不使用stop()方法
原因: 戛然而止、不知道线程完成了什么、不知道线程哪些工作还没做、清理工作无法进行
二、使用退出标志
volatile boolean keepRunning = true;
线程在下达停战命令前,有序的执行完手头的业务,然后才停止,而不是戛然而止
三、不使用interrupt()方法
原因:当线程调用join(),sleep()方法时,中断会被清除,抛出中断异常
5、线程的交互
能量守恒(示例):
package com.zy.concurrent.racecondition;
/**
* 宇宙的能量系统 遵循能量守恒定律 能量不会凭空创生或消失,只会从一处转到另一处
*
*/
public class EnergySystem {
// 能量盒子,能量存储的地方
private final double[] energyBoxes;
/**
* 能量总值
*
* @param n 能量盒子的数量
* @param initalEnergy 每个能量盒子初始含有的能量值
*/
public EnergySystem(int n, double initalEnergy) {
energyBoxes = new double[n];
for (int i = 0; i < energyBoxes.length; i++) {
energyBoxes[i] = initalEnergy;
}
}
public void transfer(int from, int to, double amount) {
// 能量转出的单元能量不足时,终止本次操作
if (energyBoxes[from] < amount)
return;
System.out.print(Thread.currentThread().getName());
energyBoxes[from] -= amount;
// System.out.printf(),java对应格式输出
System.out.printf("从%d转移%10.2f单位能量到%d",from, amount, to);
energyBoxes[to] += amount;
System.out.printf("能量总和:%10.2f%n",getTotalEnergies());
}
/**
* 返回能量盒子能量的总和
*
* @return
*/
public double getTotalEnergies() {
double sum = 0;
for (double amount : energyBoxes) {
sum += amount;
}
return sum;
}
/**
* 返回能量盒子的长度
*
* @return
*/
public int getBoxAmount() {
return energyBoxes.length;
}
}
package com.zy.concurrent.racecondition;
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 = fromBox;
this.maxAmount = max;
}
// while模拟宇宙中永不停止的能量转移
@Override
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();
}
}
}
package com.zy.concurrent.racecondition;
public class EnergySystemTest {
// 将要构建的能量世界中能量盒子数量
public static final int BOX_AMOUNT = 100;
// 每个盒子初始能量
public static final double INITAL_ENERGY = 1000;
public static void main(String[] args) {
EnergySystem eng = new EnergySystem(BOX_AMOUNT,INITAL_ENERGY);
for (int i = 0; i < BOX_AMOUNT; i++) {
EnergyTransferTask task = new EnergyTransferTask(eng, i, INITAL_ENERGY);
Thread t = new Thread(task,"TransferThread_" + i);
t.start();
}
}
}
一、争用条件
当多个线程同时共享访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏,这种现象称为争用条件.
线程1先从能量盒子导出对应位置的能量值5000,线程1未将转换后的能量值5500写入能量盒子,能量值5500存在线程1的缓存中,执行时间就耗尽了,这时线程2开始执行,把线程1在能量盒子所对应位置的能量值5000导出,然后把转换后的能量值5900写入,此时能量盒子对应位置的能量值就是5900,线程2执行时间 耗尽,这时线程1又有机会执行,将缓存中的能量值5500写入,此时就把线程2写入的能量值5900覆盖了.
二、互斥与同步
互斥:同一时间只有一个线程对临界区进行操作
同步:通信机制,告诉其他线程,本线程已执行完临界区
实现互斥:
1、增加一个锁对象
private final Object lockObj = new Object();
2、synchronized关键字,可以出现在方法体之上,也可以出现在方法体之中以块的形式出现
synchronized (lockObj) {
}
3、关键的业务代码移动到synchronized块中
public void transfer(int from, int to, double amount) {
synchronized (lockObj) {
// 能量转出的单元能量不足时,终止本次操作
if (energyBoxes[from] < amount)
return;
System.out.print(Thread.currentThread().getName());
energyBoxes[from] -= amount;
// System.out.printf(),java对应格式输出
System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
energyBoxes[to] += amount;
System.out.printf("能量总和:%10.2f%n", getTotalEnergies());
}
}
**上述代码如果当转移的能量源不足时,是不可能的,所以锁内的代码会退出,退出后此线程仍然有机会去获取cpu资源,再次要求进行加锁,加锁是会有开销的,会降低系统 的性能。
解决办法:当这个条件不满足时,也就是当转移的能量源不足时,应该让线程去等待,直到有足够能量去转移,降低线程去获取锁的开销, 提高整体的性能.(下方的Wait Set讲解)
// 能量转出的单元能量不足时,终止本次操作
// if (energyBoxes[from] < amount)
// return;
// while循环,保证条件不满足时任务会被条件阻挡
// 而不是继续竞争cpu资源
while (energyBoxes[from] < amount) {
try {
// 使线程进入等待的状态,避免线程去持续的申请锁
lockObj.wait();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
4、此线程执行完临界区,唤醒所有锁上等待的线程
// 唤醒所有在lockObj对象上等待的线程(同步)
lockObj.notifyAll();
三、Wait Set
当一个线程获得锁资源进入临界区执行某项操作时,发现某些条件不满足导致该线程无法继续执行,则调用wait()方法,然后该线程会释放锁资源,并且进入锁对象的Wait Set,由于锁资源被释放,则其他线程可以获取锁资源进入临界区完成操作,当操作完毕,调用notify(),notifuAll()方法,对WaitSet中的线程进行唤醒,等待的线程重新 获取锁进 入临界区执行.
四、Java并发编程 拓展
1>JMM
描述了java线程如何通过内存进行交互
happens-before
synchronized,volatile & final
2>Locks & Condition
Java锁机制和等待条件的高层实现
java.util.concurrent.locks
3>线程安全性
原子性与可见性
java.util.concurrent.atomic
synchronized & volatile
DeadLocks 死锁,死锁产生的原因,避免死锁
4>多线程编程常用的交互模型(实习类)
Producer-Consumer模型
Read-Write Lock模型
Future模型
Worker Thread模型
5>Java5中并发编程工具
java.util.concurrent
线程池ExcutorService
Collable & Future
BlockingQueue
6>书:(java并发)
Java concurrency in practice