1.课程回顾
2.线程的调度方法
3.线程方法汇总
4.线程安全
5.死锁
6.单例模式改造
反射:动态的获取类的信息,并执行类中的属性或方法
Class
1.获取属性、方法等
2.调用方法、属性
3.访问到私有的方法或属性
1.进程和线程
2.Java中线程的 创建方式
1.继承Thread类 2.实现Runable接口 3.实现Callable接口
3.线程的生命周期
新建、就绪、运行、阻塞、销毁
4.线程的类型
用户线程和守护线程
5.线程的优先级
1-10之间 1最小,10最高 优先级 获取cpu的概率
线程调度:是指凡是可以影响线程的状态的方法,就成为调度方法
线程调度方法:
start 新建—>就绪
sleep 运行—>阻塞 会抛出编译异常,需要捕获
join 加入、合并 运行---->阻塞
yield 礼让 运行---->就绪
wait 等待 运行—>阻塞 明天课程讲解
notify/notifyall 唤醒 阻塞---->就绪 明天课程讲解
线程的五大状态,其实就是 就绪---->运行---->阻塞
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();
}
- currentThread 获取当前的线程对象
- getName 获取线程的名称
- setName 设置线程的名称
- getId 获取线程的ID 唯一性 系统自动分配
- setDaemon 设置是否为后台线程
- isDaemon 线程是否为后台线程
- setPriority 设置优先级
- getPriority 获取优先级
- start 启动线程。注意线程的启动必须使用start方法
- isAlive 校验线程是否处于活动状态
- isInterrupted 线程是否已经中断
- join 加入、合并
- yield 礼让
- interrupt 中断线程
- sleep 睡眠
敲黑板:线程启动为什么要用start,而不是run?
start方法之后,线程的状态会切换为就绪状态,可以参与cpu的分配了。
而run方法,手动调用,并不是线程机制,而是普通的对象方法调用,这时子线程没有工作。
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 方法已经过时,但是还是可以起到现在停止的。
需求:要求代码实现,模拟窗口卖票。模拟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();
}
}
线程的抢占发生在任意时期
当多个线程操作同一个数据源的时候吗,并且都涉及到了修改,那么就有可能发生数据重复的问题,这种现象就成为线程安全的问题。
1.多个线程
2.临界资源 共享资源
锁:一个线程在访问临界资源的时候,加上锁,这时其他线程,访问临界资源,就会发现已经上锁,就说明有其他线程在占用,这时就会在等待锁的释放。
Java中提供的锁,主要有2种:
1、Synchronized 同步锁、互斥锁
2、Lock 可重入锁
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();
}
}
1.同步代码块 锁的粒度可以很小
锁的粒度来讲:同步代码块 < 同步方法 < 同步静态方法
粒度 也就是锁的范围 粒度越小越好
2.同步代码块 锁的对象可以是:引用类型属性、this、字节码对象
同步方法 锁的对象只能是当前类的实例 this
同步静态方法 锁的对象是 当前类的字节码对象
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();
}
}
Synchronized:
1.底层实现:指令码 控制锁,映射为字节码:monitorenter 和 monitorexit
2.关键字
3.自动释放锁 ,中途不能打断
Lock:
1.底层实现:基于CAS乐观锁 CLH队列实现
2.Java中的接口
3.手动释放锁,中途还可以打断
思考:如何解决卖票的线程安全问题?
哲学家进餐问题:
五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。
死锁,多个线程,每个线程都拥有对方需要的资源,程序运行,但是每个线程都进入到了阻塞,无法继续。而产生的的一种现象。
开锁的故事
死锁:每个人线程拥有其他人需要的资源,同时有需要其他线程的资源。在每个线程没有获取到自己资源的时候,都不会放弃已有的资源。这种现象就是死锁
多个线程
多个资源
多个锁
死锁产生的条件:
1.2个以上的线程
2.2个以上的锁
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();
}
}
敲黑板:锁不要乱用,注意分寸
设计模式:面向对象的最佳实践。目前主流的23种设计模式
根据模式分为三大类:
1、创建模型
2、结构型模式
3、行为型模式
创建模型:用来创建类的实例的设计模式 5种
简单工厂模式
抽象工厂模式
单例模式
建造者模式
原型模式
结构型模型:结合之后产生新的功能 7种
装饰者模式
适配器模式
过滤器模式
代理模式
外观模式
享元模式
组合模式
行为模型:实例之间的通信 11种
命令模式
责任链模式
迭代器模式
中介模式
观察者模式
模板模式
策略模式
访问者模式
解释器模式
状态模式
备忘录模式
单例模式:最简单、最广泛的设计模式
可以实现类的唯一实例的创建。
目的:保证类的唯一实例
原因:频繁创建与销毁的类,可以有效保证性能
核心:私有的构造函数
单例模式的实现方案:
1.懒汉式
2.饿汉式
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;
}
}
线程安全 占用资源大
示例代码:饿汉式
public class SingleDog02 {
//1.私有构造函数
private SingleDog02() {
}
//2.私有的静态实例对象,并完成实例化
private static SingleDog02 dog=new SingleDog02();
//3、公有静态方法 实现实例获取
public static SingleDog02 instance() {
return dog;
}
}
目前号称 做好用的单例模式
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;
}
}