23.5.07总专注时长02:02:07;
多线程的学习Java文件位置
番茄钟启动:开始学习
相关详细资料
![[Pasted image 20230508002340.png|200]]
多线程
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包 含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
为什么需要多线程
众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:
- CPU 增加了缓存,以均衡与内存的速度差异;// 导致
可见性
问题 - 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致
原子性
问题 - 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致
有序性
问题
线程的生命周期
![[Pasted image 20230508003512.png]]
新建状态(New)
创建后尚未启动
可运行(Runnable)(就绪状态)
当线程对象调用了Thread.start方法后进入就绪状态,等待JVM的调动
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
1、等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
2、同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
3、 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
无限期等待(Waiting):
等待其他线程使用notify方法唤醒否则不会被分配资源
限期等待(TimeWaiting):
在一定时间过后自动被系统唤醒sleep()方法
死亡状态(Terminated):
可能是线程结束后任务自己结束,或者产生了异常导致结束
多线程的启动方式
第一种:自己定义一个类继承 Thread类本身
1、定义类继承Thread类
2、重新定义run方法
3、创建子类对象并启动线程
public class Mythread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"Hello");
}
}
}
public class threadDemo1 {
public static void main(String[] args) {
/*
多线程的第一种启动方式
1、自己定义一个类继承thread
2、重写run方法
3、创建子类的对象,并启动线程
*/
Mythread t1= new Mythread();
Mythread t2= new Mythread();
//给线程取名
t1.setName("线程一");
t2.setName("线程二");
//开启线程
t1.start();
t2.start();
}
}
第二种:定义一个类实现Runnable接口
1、定义类实现Runnable接口
2、重写run方法
3、创建自己定义的类的对象
4、创建Thread类的对象,并启动线程
public class MyRunnable implements Runnable {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start(); }
多线程的第三种实现方式:
特点:可以获取到多线程运行的结果
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
1、创建一个类MyCallable实现Callable接口
2、重写call(有返回值,表示多线程运行的结果)
3、创建MyCallable的对象(表示多线程要执行的任务)
4、创建FutureTask的对象 用于管理多线程运行的结果
5、创建Thread类的对象,并启动(表示线程)
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <=100 ; i++) {
sum=sum+i;
}
return sum;
}
}
public class threadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyCallable对象 表示多线程要执行的任务
MyCallable mc=new MyCallable();
//创建FutureTask的对象 用于管理多线程运行的结果
FutureTask<Integer> ft=new FutureTask<>(mc);
Thread t=new Thread(ft);
t.start();
Integer result = ft.get();
System.out.println(result);
}
}
实现接口 VS 继承 Thread
实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
线程的相关方法
String getName() 返回此线程的名称
void setName(String name) 设置该线程的名称
细节:
1、如果没有给线程设置名字,线程的默认名字为
Thread-x (从0开始)
2、给线程设置名字可以用set方法也可以用构造方法
static Thread currentThread() 获取当前线程的对象
//哪条线程执行到这个方法,此时获取的就是该线程的名称
Thread t=Thread.currentThread();
细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
它的作用就是去调用main方法并执行里面的代码
在以前,我们写的所有的代码,其实都是运行在main线程中
static void sleep(long time) 让线程休眠指定的时间
细节
1、哪条线程执行到这个方法,哪条线程就会在这里停留相应的时间
2、方法的参数:表示睡眠的时间,单位毫秒
1秒=1000毫秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的代码
setPriority(int newPriority)设置线程的优先级
final int getPriority() 获取线程的优先级
线程的优先级0-10 线程具有随机的特点
Daemon
守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main() 属于非守护线程。
使用 setDaemon() 方法将一个线程设置为守护线程。
细节:当其他的非守护线程执行完毕之后,守护线程会陆续结束(不一定会执行完
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}
public static void yield() 出让线程/礼让线程
//出让当前cpu的执行权
public final void join() 插入线程/插队线程
表示把线程插入到当前线程之前
线程安全(锁和相关机制)
同步代码块
把操作共享数据的代码锁起来
格式
synchronized(锁){
操作共享数据的代码
}
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开。
主类
public class threadsafeDemo {
public static void main(String[] args) {
/*
需求:三个窗口卖一共一百张票
*/
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
public class MyThread extends Thread{
//表示这个类所有的对象,都共享ticket数据
static int ticket=0;
//锁对象,一定是唯一的
// static Object obj = new Object();
@Override
public void run() {
while (true){
//同步代码块
synchronized (MyThread.class){
if (ticket<100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票!!!");
}else{
break;
}
}
}
}
}
同步方法
就是把synchronized关键字加到方法上
public class MyRunnable implements Runnable{
int ticket=0;
@Override
public void run() {
while (true){
//同步代码块
if (method()) break;
}
}
//非静态时锁对象 this
private synchronized boolean method() {
if (ticket==100) {
return true;
}else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!!");
}
return false;
}
}
Lock锁
jdk5下的Lock
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里 加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获 得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock(): 创建一个ReentrantLock的实例
public class MyThread extends Thread{
//表示这个类所有的对象,都共享ticket数据
static int ticket=0;
//jdk5的lock
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//同步代码块
//synchronized (MyThread.class){
lock.lock();
try {
if (ticket==100){
break;
}else{
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票!!!");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
}
线程池
为什么要有线程池
线程池能够对线程进行统一分配,调优和监控:
- 降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
- 提高响应速度(无须创建线程)
- 提高线程的可管理性
简单创建一个线程池
public class MyThreadPoolDemo1 {
public static void main(String[] args) {
/*
ExecutorService newCachedThreadPool() 创建一个没有上线的线程池
ExecutorService newFixedThreadPool(int nThreads)创建有上限的新线程池
*/
//获取线程池对象
ExecutorService pool1= Executors.newCachedThreadPool();
//提交线程任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//销毁线程池
pool1.shutdown();
}
}
等待和唤醒机制(生产者与消费者)
实现线程轮流交换执行的效果
厨师类 生产者
public class Cook extends Thread{
@Override
public void run() {
/*
1、循环
2、同步代码块
3、判断共享数据是否到了末尾(到了
4、判断共享数据是否到了末尾(没到 ->执行核心逻辑)
*/ while(true){
synchronized (Desk.lock){
if (Desk.count==0){
break;
}else {
//1、判断桌子上是否有食物
//有就等待
if (Desk.foodFlag==1){
try {
Desk.lock.wait();//将线程与所绑定 方便之后唤醒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//2、没有就制作
System.out.println("厨子做了一碗面");
//制作完了修改食物状态
Desk.foodFlag=1;
//叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
桌子 控制生产者和消费者的执行
public class Desk {
/*
控制生产者和消费者的执行
*/
//是否有面条:0:没有面条 1:有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
消费者
public class Foodie extends Thread{
@Override
public void run() {
/*
1、循环
2、同步代码块
3、判断共享数据是否到了末尾(到了
4、判断共享数据是否到了末尾(没到 ->执行核心逻辑)
*/
while(true){
synchronized (Desk.lock){
if (Desk.count==0){
break;
}else {
//先判断桌子上是否有面条
//如果没有就等待
if (Desk.foodFlag==0){
try {
Desk.lock.wait();//让当前线程跟锁进行绑定
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
//有就开吃
//把剩下的面减一
Desk.count--;
System.out.println("正在吃面,还能吃"+Desk.count+"碗");
//吃完后唤醒厨师继续做面
Desk.lock.notifyAll();
//修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
主类
public class ThreadDemo {
public static void main(String[] args) {
/*
生产者和消费者(等待和唤醒机制)
实现线程轮流交替执行的效果
*/ Cook c= new Cook();
Foodie f=new Foodie();
c.setName("厨师");
f.setName("吃货");
c.start();
f.start();
}
}
自定义线程池![[Pasted image 20230507230156.png]]
自定义线程池小结:
1、创建一个空的池子
2、有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程
不断的提交任务,会有以下三个临界点:
当核心线程满时,再提交任务就会排队
当核心线程满,队伍满时,会创建临时线程
当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略