线程的进阶

线程的进阶

一、今日内容

1.课程回顾

2.线程的调度方法

3.线程方法汇总

4.线程安全

5.死锁

6.单例模式改造

二、课程回顾
2.1 反射

反射:动态的获取类的信息,并执行类中的属性或方法

Class

1.获取属性、方法等

2.调用方法、属性

3.访问到私有的方法或属性

2.2 多线程基础

1.进程和线程

2.Java中线程的 创建方式

​ 1.继承Thread类 2.实现Runable接口 3.实现Callable接口

3.线程的生命周期

​ 新建、就绪、运行、阻塞、销毁

4.线程的类型

​ 用户线程和守护线程

5.线程的优先级

​ 1-10之间 1最小,10最高 优先级 获取cpu的概率

三、线程调度
3.1 线程调度

线程调度:是指凡是可以影响线程的状态的方法,就成为调度方法

线程调度方法:

start 新建—>就绪

sleep 运行—>阻塞 会抛出编译异常,需要捕获

join 加入、合并 运行---->阻塞

yield 礼让 运行---->就绪

wait 等待 运行—>阻塞 明天课程讲解

notify/notifyall 唤醒 阻塞---->就绪 明天课程讲解

线程的五大状态,其实就是 就绪---->运行---->阻塞

3.2 代码演示

1.sleep

静态方法,Thread.sleep() 单位 毫秒

	public static void main(String[] args) {
		//需求:明天的这个时候,输出:全体员工开会
		try {
			Thread.sleep(24*60*60*1000);
			System.err.println("全体员工开会!");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

2.join

join:实例方法,加入、合并、插队

在当前线程中,执行另一个线程的join方法,然后当前线程就会阻塞,等待插入的线程执行完毕之后,才会从阻塞状态进入到就绪状态,重新参与CPU抢夺!

实际生活中的插队的现场。

就绪状态的线程的抢占发生在任意时期

示例代码:

public static void main(String[] args) {
		// 演示 线程的join 加入
		//2、第二个线程
		Thread td2=new Thread(()->{
			for(char c='a';c<='h';c++) {
				System.err.println("插入线程:"+c);
			}
		});
		//让另一个线程 加入进来
		td2.setPriority(3);
		td2.start();
		//1、第一个线程
		Thread td1=new Thread(()->{
			for(int i=1;i<10;i++) {
				if(i==3) {
					
					try {
						//将td2线程合并到td1线程中,会将td1阻塞
						td2.join();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.err.println("第一个线程:"+i);
			}
		});
		//启动线程
		td1.start();
	}

在这里插入图片描述

3.yield

yield: 实例方法,礼让 让线程的状态从运行状态 改变为就绪状态

可以让当前正在运行的线程暂停,但是不会让当前线程阻塞,而且让当前的线程进入到就绪状态。

实际上:线程执行yield之后,只有比当前线程的优先级更高或者相同的才有机会参与抢夺CPU,而且当前线程也会参与抢夺!

qq群发红包,发红包的人就是当前运行的线程,发红包的人是否可以参与红包的抢夺。调用yield方法,其实就是发红包的人也参与抢夺。

让CPU稍微空闲,把机会让给其他线程。

示例代码:

	public static void main(String[] args) {
		//演示 yield 礼让
		//1、第一个线程
		Thread th1=new Thread(()->{
			for(int i=1;i<20;i++) {
				if(i%3==0) {
					//被3整除,就礼让一次
					Thread.yield();
				}
				System.err.println("第一个线程:"+i);
			}
		});
		th1.start();
		//第二个线程 
		Thread th2=new Thread(()->{
			for(int i=100;i<120;i++) {
				System.err.println("第二个线程:"+i);
			}
		});
		th2.start();	
	}

在这里插入图片描述

四、线程方法汇总
4.1 方法汇总
  1. currentThread 获取当前的线程对象
  2. getName 获取线程的名称
  3. setName 设置线程的名称
  4. getId 获取线程的ID 唯一性 系统自动分配
  5. setDaemon 设置是否为后台线程
  6. isDaemon 线程是否为后台线程
  7. setPriority 设置优先级
  8. getPriority 获取优先级
  9. start 启动线程。注意线程的启动必须使用start方法
  10. isAlive 校验线程是否处于活动状态
  11. isInterrupted 线程是否已经中断
  12. join 加入、合并
  13. yield 礼让
  14. interrupt 中断线程
  15. sleep 睡眠

敲黑板:线程启动为什么要用start,而不是run?

start方法之后,线程的状态会切换为就绪状态,可以参与cpu的分配了。

而run方法,手动调用,并不是线程机制,而是普通的对象方法调用,这时子线程没有工作。

4.2 线程的停止

stop 自带安全隐患,已经过时

那么线程如何停止呢?

1.通过控制run方法,达到控制线程终止的方式

public class MyThread extends Thread {
	//线程是否允许的标记位 
	private boolean isrun;

	public boolean isIsrun() {
		return isrun;
	}

	public void setIsrun(boolean isrun) {
		this.isrun = isrun;
	}
	
	@Override
	public void run() {
		while (isrun) {
			try {
				Thread.sleep(400);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.err.println("子线程运行:"+System.currentTimeMillis());
			
		}
	}
}
public class Thread_Stop {
	public static void main(String[] args) throws InterruptedException {
		MyThread mtd=new MyThread();
		mtd.setIsrun(true);
		mtd.start();
		Thread.sleep(5000);
		mtd.setIsrun(false);
	}
}

2.通过线程的interrupt

	public static void main(String[] args) throws InterruptedException {
		//1.准备一个线程
		Thread td=new Thread(()->{
			while(true) {
				try {
					Thread.sleep(500);
					/*
					 *isInterrupted 线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来。 */
					System.err.println(Thread.interrupted());
					boolean b=Thread.currentThread().isInterrupted();
					System.err.println("子线程运行中……"+b);
				} catch (InterruptedException e) {
					System.err.println("线程中断");
					return;
				}
			}
		});
		//2、启动线程
		td.start();
		//3、休眠3秒
		Thread.sleep(3000);
		//4、执行线程的中断
		td.interrupt();
	}

其实就是抛出异常的形式 控制方法的执行

3.粗暴的使用stop

stop 方法已经过时,但是还是可以起到现在停止的。

五、线程安全
5.1 卖票的问题

需求:要求代码实现,模拟窗口卖票。模拟2个窗口,同时在卖100张火车票,每张票间隔 50毫秒

在这里插入图片描述

public class Ticket {

	private int no;//票号
	
	private String car;//车次

	public int getNo() {
		return no;
	}

	public void setNo(int no) {
		this.no = no;
	}

	public String getCar() {
		return car;
	}

	public void setCar(String car) {
		this.car = car;
	}
	
	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return "车次:"+car+"---->票号:"+no;
	}
}
public class TicRunnable implements Runnable{
	private int max=100;//最大的票号
	private int curr=0;//当前的票号
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(curr<max) {
			Ticket t=new Ticket();
			t.setCar("G301");
			curr++;
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			t.setNo(curr);
			System.err.println(Thread.currentThread().getName()+"--->"+t);
		}
	}
}
public class Thread_Main {

	public static void main(String[] args) {
			TicRunnable tr=new TicRunnable();
			
			//一个窗口 卖票
			Thread td1=new Thread(tr);
			td1.setName("窗口1-小姐姐");
			td1.start();
			
			//一个窗口 卖票
			Thread td2=new Thread(tr);
			td2.setName("窗口2-小哥哥");
			td2.start();
	}
}
5.2 出现问题的原因

线程的抢占发生在任意时期

当多个线程操作同一个数据源的时候吗,并且都涉及到了修改,那么就有可能发生数据重复的问题,这种现象就成为线程安全的问题。

1.多个线程

2.临界资源 共享资源

5.3 如何解决线程安全的问题

锁:一个线程在访问临界资源的时候,加上锁,这时其他线程,访问临界资源,就会发现已经上锁,就说明有其他线程在占用,这时就会在等待锁的释放。

Java中提供的锁,主要有2种:

1、Synchronized 同步锁、互斥锁

2、Lock 可重入锁

5.4 Synchronized 的应用

synchronized Java中的关键字,用来解决线程安全

有三种用法:

1.同步代码块

​ 语法格式:方法内部

​ synchronized(引用类型对象){

​ 要保护的代码

​ }

2.同步方法

​ 语法格式:修饰方法

​ 访问修饰符 synchronized 返回值 方法名(参数){

​ 要保护的内容

​ }

3.同步静态方法

​ 语法格式:修饰静态方法

​ 访问修饰符 static synchronized 返回值 方法名(参数){

​ 要保护的内容

​ }

1.锁修饰的内容只能是引用类型对象,一般都是使用this

2.锁的位置很关键,一般都是锁定是引起线程安全的部分

3.锁会自动释放,内部代码执行完毕之后。

示例代码:同步代码块

public class Thread_Sync01 {

	public static void main(String[] args) {
		//演示 同步锁 修饰代码块
		Runnable r=new Runnable() {
			private int no=1;
			private Object obj=new Object();
			@Override
			public void run() {
				while(no<100) {
						//1、修饰代码块 用在方法的内部
					synchronized (obj) {
						if(no<100) {
							no++;
							System.err.println(Thread.currentThread().getName()+
									"--->"+no);
						}
					}
				}
			}
		};
		
		//第一个线程
		Thread td1=new Thread(r);
		td1.start();
		//第二个线程
		Thread td2=new Thread(r);
		td2.start();
		//第三个线程
		Thread td3=new Thread(r);
		td3.start();
	}
}

示例代码:同步方法

public class Thread_Sync02 {

	public static void main(String[] args) {
		//准备接口对象
		Runnable r=new Runnable() {
			private int no=0;
			//卖票 同步方法
			//synchronized 2种用法 修饰方法
			private synchronized void sale() {
				if(no<1000) {
					no++;
					System.err.println(Thread.currentThread().getName()+
							"--->"+no);
				}
			}
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (no<1000) {
					sale();
				}
			}
		};
		
		//第一个线程
		Thread td1=new Thread(r);
		td1.setName("磊磊");
		td1.start();
		//第二个线程
		Thread td2=new Thread(r);
		td2.setName("抠脚磊磊");
		td2.start();
		//第三个线程
		Thread td3=new Thread(r);
		td3.setName("羊排磊磊");
		td3.start();
		
	}
}

示例方法:同步静态方法

public class SyncRunnable implements Runnable{

	private static int no=0;
	
	public static synchronized void sale() {
		if(no<1000) {
			no++;
			System.err.println(Thread.currentThread().getId()+"---->"+no);
		}
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(no<1000) {
			sale();
		}
	}
}
public class Thread_Sync03 {

	public static void main(String[] args) {
		SyncRunnable r=new SyncRunnable();
		//第一个线程
		Thread td1=new Thread(r);
		td1.start();
		//第二个线程
		Thread td2=new Thread(r);
		td2.start();
		//第三个线程
		Thread td3=new Thread(r);
		td3.start();
	}
}
5.5 Synchronized 用法的区别

1.同步代码块 锁的粒度可以很小

锁的粒度来讲:同步代码块 < 同步方法 < 同步静态方法

粒度 也就是锁的范围 粒度越小越好

2.同步代码块 锁的对象可以是:引用类型属性、this、字节码对象

同步方法 锁的对象只能是当前类的实例 this

同步静态方法 锁的对象是 当前类的字节码对象

5.6 Lock的应用

Lock锁的性能要比synchronized高,但是lock要求必须手动释放锁

语法格式:

try{

​ xxx.lock();

​ 要保护的代码

}finally{

​ xxx.unlock();

}

Lock是个接口,常用的是实现类:ReentrantLock

注意:无论是用哪种锁,要想起到保护作用,需要确保线程共有一把锁!

示例代码:lock的应用

public class Thread_lock01 {

	public static void main(String[] args) {
		Runnable r=new Runnable() {
			private int id=0;
			//实例化 可重入锁
			private Lock l=new ReentrantLock();
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while(id<100) {
					try {
						//加锁
						l.lock();
						if(id<100) {
						id++;
						System.err.println(Thread.currentThread().getName()+
							"--->"+id);
						}
					}finally { //最终会执行的
						//释放锁
						l.unlock();
					}
				}
			}
		};
		//第一个线程
		Thread td1=new Thread(r);
		td1.start();
		//第二个线程
		Thread td2=new Thread(r);
		td2.start();
		//第三个线程
		Thread td3=new Thread(r);
		td3.start();
	}
}
5.7 Synchronized和lock的区别

Synchronized:

​ 1.底层实现:指令码 控制锁,映射为字节码:monitorenter 和 monitorexit

​ 2.关键字

​ 3.自动释放锁 ,中途不能打断

Lock:

1.底层实现:基于CAS乐观锁 CLH队列实现

2.Java中的接口

3.手动释放锁,中途还可以打断

思考:如何解决卖票的线程安全问题?

六、死锁
6.1 哲学家进餐的问题

在这里插入图片描述

哲学家进餐问题:
五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。

死锁,多个线程,每个线程都拥有对方需要的资源,程序运行,但是每个线程都进入到了阻塞,无法继续。而产生的的一种现象。

开锁的故事

6.2 死锁产生的原因

死锁:每个人线程拥有其他人需要的资源,同时有需要其他线程的资源。在每个线程没有获取到自己资源的时候,都不会放弃已有的资源。这种现象就是死锁

多个线程

多个资源

多个锁

死锁产生的条件:

1.2个以上的线程

2.2个以上的锁

3.同步代码块嵌套同步代码块

6.3 代码实现死锁

需求:使用多线程的思想,模拟开锁的问题

在这里插入图片描述
示例代码:

public class MyLock {
	//模拟 开锁资源
	public static Object obj1=new Object();
	//模拟 房本资源
	public static Object obj2=new Object();
}
public class DeadLock_Main {

	public static void main(String[] args) {
		//模拟 开锁 角色:1.开锁人 2.房屋所有人 2种资源:开锁、房本
		//第一个线程:开锁人
		Thread td1=new Thread(()->{
			while(true) {
				//拥有开锁资源
				synchronized (MyLock.obj1) {
					System.err.println("开锁匠------>拥有开锁的技术");
					//拿到房本
					synchronized (MyLock.obj2) {
						System.err.println("开锁匠----开锁了……");
					}
				}
			}
		});
		td1.start();

		//第二个线程:房屋所有者
		Thread td2=new Thread(()->{
			while(true) {
				//拥有房本
				synchronized (MyLock.obj2) {
				System.err.println("房屋所有者------>拥有房本……");
					//需要开锁
					synchronized (MyLock.obj1) {
								System.err.println("房屋所有者----门开了……");
					}
				}
			}
		});
		td2.start();
	}
}

敲黑板:锁不要乱用,注意分寸

七、单例模式的线程安全
7.1 设计模式

设计模式:面向对象的最佳实践。目前主流的23种设计模式

根据模式分为三大类:

1、创建模型

2、结构型模式

3、行为型模式

创建模型:用来创建类的实例的设计模式 5种

简单工厂模式

抽象工厂模式

单例模式

建造者模式

原型模式

结构型模型:结合之后产生新的功能 7种

装饰者模式

适配器模式

过滤器模式

代理模式

外观模式

享元模式

组合模式

行为模型:实例之间的通信 11种

命令模式

责任链模式

迭代器模式

中介模式

观察者模式

模板模式

策略模式

访问者模式

解释器模式

状态模式

备忘录模式

7.2 单例模式

单例模式:最简单、最广泛的设计模式

可以实现类的唯一实例的创建。

目的:保证类的唯一实例

原因:频繁创建与销毁的类,可以有效保证性能

核心:私有的构造函数

单例模式的实现方案:

1.懒汉式

2.饿汉式

3.双重锁式

7.3 懒汉式单例模式

线程不安全

示例代码:懒汉式

public class SingleDog01 {
	//1.私有构造函数
	private SingleDog01() {}
	//2.私有的静态实例对象
	private static SingleDog01 dog;
	//3、公有静态方法 实现实例获取
	public static SingleDog01 instance() {
		if(dog==null) {
			dog=new SingleDog01();
		}
		return dog;
	}
}
7.4 饿汉式单例模式

线程安全 占用资源大

示例代码:饿汉式

public class SingleDog02 {
	//1.私有构造函数
	private SingleDog02() {}
	//2.私有的静态实例对象,并完成实例化
	private static SingleDog02 dog=new SingleDog02();
	//3、公有静态方法 实现实例获取
	public static SingleDog02 instance() {
		return dog;
	}
}
7.5 双锁式单例模式

目前号称 做好用的单例模式

public class SingleDog03 {
	//1.私有构造函数
	private SingleDog03() {}
	//2.私有的静态实例对象,并完成实例化
	//volatile 内存可见性 保证安全性
	private static volatile SingleDog03 dog;
	//3、公有静态方法 实现实例获取
	public static SingleDog03 instance() {
		if(dog==null) {
			synchronized (SingleDog03.class) {
				if(dog==null) {
					dog=new SingleDog03();
				}
			}
		}
		return dog;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值