技术理论-【Thread】- java线程知识总结

概念:

背景

  • 程序,进程,线程,多任务,主线程
  • 三高应用(高可用,高性能,高并发)
  • 学习理论(守破离,断舍离),
  • lambda(JDK8,内部类<静态,局部,匿名,lambda>,函数式编程),
  • 定时器(timer,task),
  • 类协作(设计模式),代理模式(静态代理<婚庆公司>,动态代理,真实角色,代理角色),单例模式,
  • CAS(compare and swap),悲观锁,乐观锁

知识点

  • 线程实现(Thread,Runnable,Callable),
  • 线程启动(方式,时机),
  • 线程状态(new新生,runnable<就绪,运行>,terminated死亡,block阻塞,time_wait时间等待,wait阻塞等待),
  • 同步synchronize(方法,块,容器),
  • 数据可见性和代码有序性(volatile),
  • 守护线程,
  • ThreadLocal,JUC
  • happen before,CPU指令重排机制

实际问题

  • 共享资源问题(并发问题),
  • 并发案例(龟兔赛跑,12306,电影院,取钱),
  • 共享资源(全局对象、变量、方法),
  • 死锁(哲学家就餐问题),
  • 线程协作,生产者消费者模型(缓存法,信号灯法),
  • 单例模式使用了dubble checked。

线程命题

  1. 线程是为了解决应用的多任务并行问题。
  2. 线程是轻量级的进程。
  3. 线程并行是单CPU模拟并行,所以线程会争夺CPU的使用时间。
  4. 应用并发是某一功能被多人同时使用。
  5. 应用并发造成线程并发问题、服务器资源问题。
  6. 线程并发问题是线程调度机制和操作公共资源共同造成的。
  7. 线程调度机制衍生出线程状态和改变调度逻辑问题。
  8. 线程并发问题有三个因素,原子性、可见性、有序性。
  9. synchronize来解决原子性。
  10. synchronize可能造成死锁问题。
  11. synchronize是悲观锁。
  12. ThreadLocal是乐观锁。
  13. volatile解决数据可见性和代码有序性。
  14. volatile用来解决CPU指令重排造成的脏数据问题。
  15. 生产者消费者模型是解决并发问题的异步方案。
  16. synchronize是解决并发问题的同步方案。

演化过程

线程的来历

  1. 计算机简化模型:程序+计算。
  2. 计算机使用CPU代替人工计算。
  3. 程序是算法+数据。
  4. 操作系统也是一种程序,可以运行应用程序。
  5. 操作系统管理计算机硬件和程序执行过程。
  6. 计算机只能一次运行一个程序
  7. 操作系统通过进程调度机制实现计算机同时运行多个程序。
  8. 进程是程序的一次执行过程。
  9. 每个进程有独立内存空间。
  10. 进程调度机制让多个进程轮流使用CPU。
  11. 最初一个程序执行一个任务。
  12. 线程让程序同时执行多个任务。
  13. 线程调度机制让多个线程轮流使用CPU
  14. 线程使用进程的内存空间和CPU时间。
    技术理论-【Thread】- java线程知识总结_第1张图片

创建线程方式

  1. 继承Thread类;
    • 对象可以直接调用start()启动。
  2. 实现Runnable接口,线程不可以返回数据和抛异常;
    • 对象需要放入Thead代理对象后,代理对象调用start()启动。
  3. 实现Callable接口,线程可以返回数据和抛异常;
    • 对象放入Thead代理对象后,代理对象调用start()启动。
    • 对象放入FutureTask代理对象后,使用ExecutorService线程池对象的execute()启动。

线程状态

  1. 创建状态(NEW):创建一个线程对象。
  2. 运行状态(RUNNABLE):线程启动后,争取或者获得CPU使用权。
  3. 死亡状态(TERMINATED):线程逻辑(run方法)运行完毕。
  4. 时间等待状态(TIME_WAIT):等待指定时间后(sleep/join/yield),才能争取CPU使用权。
  5. 等待状态(WAIT):等待别人唤醒(notify)。
  6. 阻塞状态(BLOCK):获取同步锁失败,等待别人释放锁。
    技术理论-【Thread】- java线程知识总结_第2张图片

线程并发问题(原子性:代码级别)

问题原因:CPU使用权
  1. 多个线程同时操作公共资源。
  2. 由于线程调度机制,线程被剥夺了CPU使用权,导致暂停写入步骤。
  3. 其他线程可能拿到未修改之前的脏数据。
  4. 从而出现数据不一致问题。
解决方案:悲观锁
  1. 使用synchronize同步锁机制。
  2. 操作公共资源之前,先锁定资源使用权,阻止其他人使用资源。
  3. 将资源操作步骤原子化。
  4. 所有资源操作步骤都完成后,才释放资源锁。
  5. 加锁的资源类型:
    • 同步方法,等同于锁定this对象(一个线程对象启动多个线程的情况);
    • 同步块,等同于锁定数据对象(class对象也可以锁定=锁定new操作);
      技术理论-【Thread】- java线程知识总结_第3张图片

线程并发问题(可见性:指令级别)

问题原因:CPU寄存器缓存
  1. 线程指令运行时,会从主存中拷贝数据到寄存器;
  2. 由于寄存器中的数据拷贝,多线程操作同一个主存数据时;
  3. 会出现拷贝数据不一致;

解决方案:volatile

  1. volatile申明的变量,在修改后会标识缓存对象为无效;
  2. 缓存无效后,CPU会重新加载主存数据;
    技术理论-【Thread】- java线程知识总结_第4张图片

线程并发问题(有序性:代码级别)

问题原因:JVM指令重排
  1. JVM在保证代码执行效果一直的情况,进行的指令顺序重新排列;
  2. 多线程情况下,变量变化顺序,可能造成逻辑判断混乱;
int i = 0;              //语句1            
boolean flag = false;   //语句2 
i = 1;                  //语句3  
flag = true;            //语句4

执行顺序可能是:1234、1243、2134、2143。

解决方案:volatile

  1. volatile申明的变量,在编译时,会保证改变量操作前后代码不变;
  2. 比如5条代码,变量是第3条,那么12不会到3之后,45不会到3之前;
  3. 只能是:12345,12354,21345,21354
int i = 0;              //语句1            
boolean flag = false;   //语句2 
ticket = 99;			//语句3
i = 1;                  //语句4 
flag = true;            //语句5

火车票并发案例

/**
 * 线程同步机制:synchroneze
 * 使用同步块的方式,创建一个锁对象lock;
 * sleep会继续占用lock;
 * 
 * @author jeadong
 *
 */
public class SynchronizeTest {

	public static void main(String[] args)  {
		//启动3个买票线程,模拟并发抢票
		new Thread(new Web12306()).start();
		new Thread(new Web12306()).start();
		new Thread(new Web12306()).start();
	}

}

//模拟12306买票操作
class Web12306 implements Runnable{

	private static Object lock = new Object();//买票锁,保证减库存操作的线程安全
	private static int tikects = 99;//总票数
	private boolean flag = true; 
	
	@Override
	public void run() {
		while(flag){
			//买票
			buyTikect();
		}//while end
	}

	private void buyTikect() {
		//买票前,先加锁
		synchronized(lock){
			if (tikects > 0) {
				tikects--;
				System.out.println("【"+Thread.currentThread().getName()+"】购票成功!剩余票量:"+tikects);
			}else{
				flag=false;//票卖光了,就结束买票
			}
			//模拟买票操作延迟
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}//sycn end
	}
	
}

单例模式案例,double checked

/**
 * 双重检测锁:synchronize+volatile
 * 创建单例模式
 * 1、保证原子性,用synchronize锁定class
 * 2、保证可见性和有序性,用volatile直接访问主存对象
 * 3、最外层的判空是为了提升访问效率
 * 
 * @author jeadong
 *
 */
public class DubbleCheckedTest {

	public static void main(String[] args) {
		//启动子线程触发第一次访问
		new Thread(() -> {
			System.out.println(Singleton.getInstance());
		}).start();
		//主线程触发第二次访问
		System.out.println(Singleton.getInstance());
		
	}

}

class Singleton {
	//volatile必须加,为了防止指令重排,第一次创建时,内存还没完成创建,就把对象指针暴露出去了
	private static volatile Singleton instance;
	
	private Singleton(){
	}

	public static Singleton getInstance(){
		//第一层检测,为了第二次访问之后提升效率,不要再次加锁
		if (null == instance) {
			//锁定class,让其他线程无法创建对象,保证只会执行一次New操作
			//如果没有加锁,就能模拟出创建两个对象
			synchronized(Singleton.class){
				//第二层检测,保证第一次访问顺利完成对象创建
				if(null == instance){
					//模拟创建对象比较耗时
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					//创建对象
					instance = new Singleton();
				}
			}
		}
		
		return instance;
	}
	
}

你可能感兴趣的:(技术仓库)