java多线程并发基础

时间:2023-01-05 18:04:05

一、进程:(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覆盖了.


java多线程并发基础


二、互斥与同步

    互斥:同一时间只有一个线程对临界区进行操作
    同步:通信机制,告诉其他线程,本线程已执行完临界区    

    实现互斥:


            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