写在前面
啦啦啦,我又来了,第三天打卡成功!
今日学习内容
多线程
创建方式
- 继承
Thread
类
步骤:- 创建
Thread
子类 - 重写
run()
方法,设置线程任务 - 创建
Thread
子类对象 - 调用
start()
方法开启线程
- 创建
- 实现
Runnable
接口- 步骤:
- 创建
Runnable
接口的实现类 - 重写
run()
方法,设置线程任务 - 创建实现类对象
- 创建
Thread
对象,构造方法中传递Runnable
实现类对象 - 调用
start()
方法开启线程
- 创建
- 好处
- 避免单继承的局限性:实现
Runnable
接口还能继承其他类,实现其他接口; - 增强程序扩展性:解耦,把线程任务和开启线程进行了分离(
Thread
构造方法中传递什么实现类对象,就执行什么线程任务)
- 避免单继承的局限性:实现
- 步骤:
- 匿名内部类
格式:new 父类/Runnable(){ //重写run方法 };
方法
java.lang.Thread
的方法:- 获取线程名称:
String getName();
- 设置线程名称:
void setName(String name);
- 使当前正在执行的线程以指定的毫秒数暂停,毫秒数结束之后,继续执行
static void sleep(long millis);
- 获取当前正在执行的线程:
static Thread currentThread()
- 创建
Thread
对象:Thread();
Thread(String name);
Thread(Runnable target);
Thread(Runnable target, String name);
- 获取线程名称:
java.lang.Object
的方法:- 唤醒此对象监视器的单个线程,继续执行
wait()
之后的代码,,若锁对象有多个等待线程,则随机唤醒:void notify();
- 唤醒此对象监视器的所有线程:
void notifyAll();
- 当前线程等待:
void wait();
- 当前线程以指定时间等待:
void wait(long millis);
- 唤醒此对象监视器的单个线程,继续执行
线程安全
多个线程访问共同的数据,会引发线程安全问题
解决方案:线程在访问共享数据时,无论是否失去了CPU
的执行权,让其他线程只能等待,等当前线程结束,其他线程再执行。
线程同步
- 同步代码块
- 格式
synchronized(锁对象){ //可能会引发线程安全的代码 }
- 注意
- 锁对象可以是任意对象(锁对象的作用:把此段代码锁住,只让一个线程执行此段代码块)
- 必须保证多个线程使用同一个锁对象,以保证同步【在
Thread
子类/Runnable
实现类中声明成员变量“锁对象”】
- 格式
- 同步方法
- 步骤
- 将访问共享数据的代码抽取出来,放到一个方法中
- 方法上加
synchronized
关键字
- 格式
注意:前一种方法,锁对象为修饰符 synchronized 返回值类型 方法名(){ //共享数据的代码块 } 修饰符 static synchronized 返回值类型 方法名(){ //共享数据的代码块 }
this
;后一种锁对象为类名.class
- 步骤
Lock
锁(java.util.concurrent.locks.ReentrantLock
)- 方法
- 加锁:
void lock();
- 解锁:
void unlock();
- 加锁:
- 使用步骤
- 在
Thread
子类/Runnable
实现类的成员变量常创建ReentrantLock
对象 - 在可能会出现线程安全的代码前调用
lock()
加锁 - 在可能会出现线程安全的代码执行完成之后调用
unlock()
解锁(最好将unlock()
放入finally
,无论代码是否发生异常,都会把锁释放->提高程序效率)
- 在
- 方法
线程状态
- 新建状态
- 可运行状态(线程可能正在运行也可能没有运行)
抢占式调度系统给每个可运行线程一个时间片来执行任务 - 阻塞状态
暂时是不活动的,消耗最少资源。
当一个线程试图获得一个内部的对象锁,而这个所目前正在被另一线程占用,该线程就会被阻塞。 - 计时等待状态
- 等待状态
暂时是不活动的,消耗最少资源 - 终止状态
线程通信–等待唤醒机制
多个线程在处理同一资源,并且在任务不同时,需要线程通信来帮助解决线程之间同一资源的操作,
注意:
- 线程必须用同步代码块包裹起来,保证等待和环唤醒只有一个执行
- 同步使用的锁对象必须唯一
- 只有锁对象可以调用
wait()
和notify()
??
调用唤醒方法之后,若没有获得锁,则进入阻塞状态;否则,进入可运行状态:被通知的线程不能立即恢复执行,因为它当初中断的地方是在同步代码块中,而此时它已不持有锁,所以需要再次获取,成功获得锁之后才可以获取继续在wait()
之后执行的权力
线程池
可容纳多个线程的容器
好处
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
创建
public static ExecutorService ewFixedThreadPool(int nThread)
返回Executors
实现类对象ExecutorService
使用步骤
1. 使用线程池的工厂类java.util.concurrent.Executors
的静态方法newFixedThreadPool(int nThread)
生成一个指定线程数量的线程池
2. 创建一个Runnable
实现类,重写run()
方法,设置线程任务
3. 调用Future submit(Runnable task)
传递线程任务
案例分析
需求:顾客告知包子铺要买包子,包子铺在没有包子的情况下花费一定时间做包子,做好包子后,通知顾客可以吃包子。
分析:
for
顾客
1. 有包子:吃包子->修改包子状态“false
”->告知包子铺做包子
2. 无包子:等待
for
包子铺
1. 无包子:做包子->修改包子状态”true
“->通知顾客吃包子
2. 有包子:等待
实现:
- 顾客线程类
任务:判断是否有包子,没有包子,等待;否则,吃包子,设置包子状态,通知包子铺 - 包子铺线程类:
任务:判断是否有包子,有包子,等待;否则,花时间做包子,然后通知顾客可以吃包子了 - 包子类(共享数据)
包子当前的状态:true
有,false
无 - 测试类
- 创建包子类,作为锁对象,传递到两个线程中
- 创建线程对象,构造函数传递锁对象参数
- 调用
start()
开启线程
注意:
- 线程任务包裹在同步代码块中,保证顾客线程和包子铺线程只有一个可以操作包子资源
- 两个线程使用同一个锁对象,以保证同步
- 调用
notify()
唤醒另一线程后,继续执行wait()
后代码,因而,先wait()
,后实现线程通信的notify()
写在最后
多线程这部分内容还是不是很理解,之后还要花时间再学习。