java多线程并发基础

一、进程:(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张图片


二、互斥与同步

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

    实现互斥:


            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


     


你可能感兴趣的:(实用方法)