千峰教育2218期线程

2023-10-22 23:11
文章标签 线程 教育 千峰 2218

本文主要是介绍千峰教育2218期线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天的内容

进程
线程【重点】
今天学习的目标是要入门线程。知道线程是干嘛的!!!!!

1. 进程

是独立的一个应用程序
比如咱们电脑上面,一个软件就是一个应用程序,就是一个进程,idea、 QQ
window 系统会给进程分配当前电脑中资源,可以分享当前的网络,网卡,内存,显卡等
进程有些特性:
独立性
各个进程之间是相互独立的互不影响!!!
互斥性
每个软件 windows 系统会给他们分配唯一的端口号,如果一个软件启动了电脑会给分配一个独立的端口号,如果其他软件启动,电脑再给分配另一个端口号,端口号是唯一的。
比如 QQ 端口号是 97 idea 端口号也是 97. 意味着 QQ 启动之后 idea 无法启动,会报错,端口被占用的错误

2. 线程

进程是可以运行的,在运行的时候,线程是进程中最小的运行单位。每一个进程至少有一个线程
如果一个进程没有线程,那么这个进程就没有生命力。
进程包含了线程,线程是组成进程的最小的基本单位
线程特性:
抢占式运行【很重要今天能理解线程】
一个进程在执行的时候,靠获取 CPU 时间片来执行的。单位时间片是抢占式执行的。
比如一个进程里面有是三个线程在执行,同时执行的 ? 不是 三个线程先抢占
假如线程1先抢到以后,线程 1 先执行 4ms, 然后是释放资源。三个线程再抢 线程 2 再执行。执行 4ms
如此往复。抢占间隔的时间是比较短的!!所以你感觉这个应用程序一致在执行的!!!
资源共享性
线程之间可以共享网卡和 CPU 的
Java 程序:
比如咱们写过的 Demo1 main 主函数 就是一个进程,就是一个应用程序。
Demo1 这个 Java 应用程序中有几个线程?
两个:
main 主线程
JVM 垃圾回收线程

3. 并行和并发

并行:真正意义上的同时执行,我一边做饭一边看电视
并发:同时发生轮流交替执行,我看一会电视做一会饭

4. 创建线程的两种方式【重点】

一个线程干一个活,比如咱们可以让一个线程去打印 99 乘法变,让另一个线程打印等腰直角三角形!!!

创建线程的第一种方式:
创建一个新的执行线程有两种方法。一个是将一个类声明为 Thread 的子类,这个子类应该重写 Run 类的方法 Thread 。然后可以分配并启动子类的实例。
package com.study.a_thread;/*** @author big God* @date 2023/1/2 10:23 * @version 1.0*/
// 1.自己新建一个类声明为 Thread 子类
class MyThread1 extends Thread {// 2. 重写 run 方法@Overridepublic void run() {// MyThread1 线程执行的任务for (int i = 0; i < 500; i++) {System.err.println("MyThread1 线程" + i);}}
}
class MyThread2 extends Thread {@Overridepublic void run() {for (int i = 0; i < 500; i++) {System.out.println("MyThread2 线程" + i);}}
}
public class Demo1 {public static void main(String[] args) {// 3. 实例化线程MyThread1 myThread1 = new MyThread1();// 4. 启动线程  使该线程开始执行;Java 虚拟机调用该线程的 run 方法。myThread1.start();new MyThread2().start();}
}

练习:新建两个线程一个线程打印九九乘法表一个线程打印直角三角形 15分钟能不能写完

package com.study.a_thread;/*** @author big God* @date 2023/1/2 10:42 * @version 1.0*/
class Multiplication extends Thread {@Overridepublic void run() {for (int i = 1; i < 10; i++) {for (int j = 1; j <= i; j++) {System.out.print(i + "*" + j + " = " + i * j + "\t");}System.out.println();}}
}
class Triangle extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {for (int j = 0; j < i + 1; j++) {System.out.print("* ");}System.out.println();}}
}
public class Demo2 {public static void main(String[] args) {new Multiplication().start();new Triangle().start();// 线程得到的东西不可预期,所以难!!! 每次执行的结果不一样}
}
创建线程的第二种方式
另一种方法来创建一个线程是声明实现类 Runnable 接口。那个类然后实现了 run 方法。然后可以分配类的实例,在创建 Thread 时作为参数传递,并启动。
package com.study.b_thread;/*** @author big God* @date 2023/1/2 11:16 * @version 1.0*/
// 1. 实现 Runnable 接口
class MyThread1 implements Runnable {@Override// 2. 重写 run 方法public void run() {for (int i = 0; i < 100; i++) {System.out.println("MyThread1 线程" + i);}}
}
class MyThread2 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("MyThread2 线程" + i);}}
}
public class Demo1 {public static void main(String[] args) {//3. 创建 MyThread1 的对象//4. 在创建 Thread 对象的时候将线程对象作为参数传递进来// Thread(Runnable target)//          分配新的 Thread 对象。// 5. 开启线程new Thread(new MyThread1()).start();// 匿名内部类实现 Runnable 接口new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.err.println("匿名内部类实现 Runnable接口 " + i);}}}).start();for (int i = 0; i < 100; i++) {System.out.println("主线程" + i);}// 启动 MyThread2 线程// 匿名对象的写法new Thread(new MyThread2()).start();}
}
练习:新建两个线程打印九九乘法表一个线程打印直角三角形!!
使用第二种方式来写!!! 【匿名内部类也可以写】
package com.study.b_thread;/*** @author big God* @date 2023/1/2 11:30 * @version 1.0*/
public class Demo2 {public static void main(String[] args) {// 匿名内部类实现 Runnable 接口 九九乘法表new Thread(new Runnable() {@Overridepublic void run() {for (int i = 1; i < 10; i++) {for (int j = 1; j < i + 1; j++) {System.out.print( i + "*" + j + "=" + i * j + "\t");}System.out.println();}}}).start();// 匿名内部类实现 Runnable 接口 直角三角形new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {for (int j = 0; j < i + 1; j++) {System.out.print("* ");}System.out.println();}}}).start();}
}

开发中使用第二种方式,因为多个接口

5. 线程下面几个基础方法

构造方法】
线程下面的方法
package com.study.c_thread;/*** @author big God* @date 2023/1/2 11:46 * @version 1.0*/
class MyThread1 implements  Runnable {@Overridepublic void run() {// currentThread() 获取当前线程的引用 当前 MyThread1 对象System.out.println("Line7:" + Thread.currentThread());// Thread-0 线程默认的名字System.out.println("Line8:" + Thread.currentThread().getName());}
}
public class Demo1 {public static void main(String[] args) {// 通过构造方法对线程起名字Thread thread = new Thread(new MyThread1(),"线程1");System.out.println("Line13:" + thread);// 覆盖构造方法所起的名字thread.setName("MyThread1 线程");thread.start();// 匿名内部类实现 Runnable 接口// 通过构造方法对线程起名字Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {// currentThread() 获取当前线程的引用 当前 MyThread1 对象// Thread-1 线程默认的名字System.out.println("Line9:" + Thread.currentThread());System.out.println("Line3:" + Thread.currentThread().getName());}}, "匿名内部类线程2");thread1.start();// 在 main 主函数中写 currentThread() 主线程 获取主线程的名字// main 是主线程的默认名字//Thread.currentThread().setName("主线程");System.out.println(Thread.currentThread().getName());// 发现咱们的线程 JVM 都会给一个默认的值,咱们呢过不能自己给线程起名字 ? 可以的}
}
package com.study.c_thread;/*** @author big God* @date 2023/1/2 12:18 * @version 1.0*/
class Mythread2 implements  Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("MyThread2 线程" + i);}}
}
public class Demo2 {public static void main(String[] args) {Thread myThread2 = new Thread(new Mythread2(), "MyThread2");myThread2.setPriority(10);Thread.currentThread().setPriority(1);int priority = myThread2.getPriority();// 线程优先级 默认是 5, 值可以设置为 1 ~ 10, 1 的优先级是最低的 10 的优先级最高的// 优先级并不是真正的优先,优先级越高就增加执行的概率System.out.println(priority); // 5myThread2.start();// main 主线程中优先级  5System.out.println(Thread.currentThread().getPriority());for (int i = 0; i < 100; i++) {System.err.println("主线程" + i);}}}
package com.study.c_thread;/*** @author big God* @date 2023/1/2 12:30 * @version 1.0*/
class MyThread3 implements Runnable {@Overridepublic void run() {// 让 Mythread3 睡一会儿 10000 毫秒// 1 秒 = 1000 毫秒try {// 为啥只能 try - catch 不能 throws/*** run 方法是重写的方法,重写非常严格* 在父类中 run 方法是没有 throws 的*  public abstract void run() throws Exception;*/Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}for (int i = 0; i < 100; i++) {System.err.println("MyThread3 线程:" + i);}}
}
public class Demo3 {public static void main(String[] args) {new Thread(new MyThread3()).start();for (int i = 0; i < 100; i++) {System.out.println("主线程:" + i);}}
}

上午的内容

1. 创建线程的两种方式继承 Thread 类,重写 run 方法实现 Runnable 接口,重写 run 方法
2. 线程下面的几个方法static void sleep(); 让线程睡一会儿static Thread currentThread(); 获取当前线程对象的setName() 设置线程名字的getName() 获取线程名字的

6. 线程的同步和锁

当时用多个线程来访问同一个数据时,将会导致数据不准确,相互之间产生冲突,非常容易出现安全问题,比如多个线程都在操作同一个数据,都打算修改商品库存,这样就会导致数据不一致的问题。
线程同步的真实意思,其实是"排队":几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
所以我们用同步机制来解决这些问题,加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
三个人(三个线程)同时对共享资源(一个坑位)进行操作(抢占)。导致数据不安全的效果。咋解决?
同步机制,加锁。一个人上厕所,锁住,其他的两个人在外面等待。等待你解决完以后释放掉这个锁以后。其他的线程再进来抢占这个资源。
package com.study.d_thread;/***卖票* 有两个线程卖票 火车票 总共有 192 张票* 一个线程 第 192 张票 另外一个线程 第 191 张票* @author big God* @date 2023/1/2 14:37 * @version 1.0*/
class SaleTicket implements Runnable {// ticket 共享资源 线程会操作这个共享的资源private static int ticket = 192;@Overridepublic void run() {while (true) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了" + ticket + "票");ticket--;} else {System.err.println("卖完了");break;}}}
}
public class Demo1 {public static void main(String[] args) {// 两个线程访问同一个数据SaleTicket saleTicket = new SaleTicket();new Thread(saleTicket,"线程1").start();new Thread(saleTicket,"线程2").start();}
}
线程1卖出了第192票
线程1卖出了第191票
线程1卖出了第190票
线程1卖出了第189票
线程1卖出了第188票
线程1卖出了第187票
线程1卖出了第186票
线程1卖出了第185票    问题出现了
线程2卖出了第185票    问题出现了  为啥同时卖出去了?
线程2卖出了第183票
线程2卖出了第182票

发现线程安全不安全?不安全!!!!要加锁!!!

解决方案:同步方法
使用关键字 synchronize 修饰的一个方法,因为 Java 对象都会有一个内置的锁对象,使用这个关键字的时候
修饰方法的时候,这个方法就会被锁保护起来,无论如何之后只保证一个线程再执行这个方法
语法格式:
public synchronize void 方法名字 () {}
package com.study.d_thread;/***卖票* 有两个线程卖票 火车票 总共有 192 张票* 一个线程 第 192 张票 另外一个线程 第 191 张票* @author big God* @date 2023/1/2 14:37 * @version 1.0*/
class SaleTicket implements Runnable {// ticket 共享资源 线程会操作这个共享的资源private static int ticket = 192;@Overridepublic synchronized void run() {while (true) {if (ticket > 0) {// 线程 1 和线程 2 同时进入到 if 语句了这个代码 有没有这种可能?// ticket = 186// 结果线程 1 打印了一下 "卖出了第 185 张票"// ticket -- 还没有自身减 1, 然后线程 2 把线程 1 执行权抢了过去// 此时线程 2 ticket = 185 也打印了一下 "卖出了第 185 张票"System.out.println(Thread.currentThread().getName() + "卖出了" + ticket + "票");ticket--;} else {System.err.println("卖完了");break;}}}
}
public class Demo1 {public static void main(String[] args) {// 两个线程访问同一个数据SaleTicket saleTicket1 = new SaleTicket(); // 你有一个 ticket 变量// SaleTicket saleTicket2 = new SaleTicket(); // 也有一个 ticket 变量// 就不是一个共享的资源了new Thread(saleTicket1,"线程1").start();new Thread(saleTicket1,"线程2").start();}
}

把锁加在了 run 方法上面不太合适,出现了一家独大的情况,咋办?

咱们有些同学说单独封装一个方法,将 if 语句加锁.

package com.study.d_thread;/*** @author big God* @date 2023/1/2 16:08 * @version 1.0*/
class SaleTickets implements Runnable {public static int ticket = 100;@Overridepublic void run() {while (ticket > 0) {test();if (ticket == 0)break;}}private synchronized void test() {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "票");ticket--;} else {System.err.println("售罄 ~ ~");}}
}
public class Demo2 {public static void main(String[] args) {SaleTickets saleTickets = new SaleTickets();new Thread(saleTickets,"猫眼").start();new Thread(saleTickets,"美团").start();}
}
只会打印一个 "售罄~ ~"
解决方案二:
最终的一种解决方案:
同步代码块:可以将一段代码放到 synchronize 括起来的地方,就会对这段代码加上锁
synchronized(this) { }
package com.study.d_thread;/***卖票* 有两个线程卖票 火车票 总共有 192 张票* 一个线程 第 192 张票 另外一个线程 第 191 张票* @author big God* @date 2023/1/2 14:37 * @version 1.0*/
class SaleTicket implements Runnable {// ticket 共享资源 线程会操作这个共享的资源private static int ticket = 192;@Overridepublic void run() {// 在这个地方加锁可以吗? 把 while 循环加锁可以不? 不可以 为啥?// 进去一个锁住出不来了 "一家独大"while (true) {synchronized (this) {if (ticket > 0) {// 线程 1 和线程 2 同时进入到 if 语句了这个代码 有没有这种可能?// ticket = 186// 结果线程 1 打印了一下 "卖出了第 185 张票"// ticket -- 还没有自身减 1, 然后线程 2 把线程 1 执行权抢了过去// 此时线程 2 ticket = 185 也打印了一下 "卖出了第 185 张票"System.out.println(Thread.currentThread().getName() + "卖出了" + ticket + "票");ticket--;} else {System.err.println("卖完了");break;}}}}
}
public class Demo1 {public static void main(String[] args) {// 两个线程访问同一个数据SaleTicket saleTicket1 = new SaleTicket(); // 你有一个 ticket 变量// SaleTicket saleTicket2 = new SaleTicket(); // 也有一个 ticket 变量// 就不是一个共享的资源了new Thread(saleTicket1,"线程1").start();new Thread(saleTicket1,"线程2").start();}
}

思路:先讲的线程同步,多个线程共享一个资源,结果发现有问题!!!

共享数据出现混乱

想办法解决,加同步锁,先讲了一个同步方法,发现一个线程进来以后,while 循环其他线程进不来了,不能出现一家独大的情况!!! 咱们同学想到了一种解决方案? 把 if 语句封装成一个方法,然后对这个方法加同步方法操作,也是可以的!!!

再换一种方法!使用同步代码块解决方法

6.1 Java 中 Lock 锁【非重点】

synchronize 在 Java 中被称为隐式锁,会自动释放锁。它是一个非公平的锁.
Lock 锁被称为显示锁,需要显示的获取和释放,可以设置为公平锁。
Lock 和 synchronize 是一样的,都可以实现线程同步。但是 synchronize 更加强大,更加力度化,更加灵活,一般开发使用 synchronize 或者以后会讲线程池。
一般开发都会用线程池
Lock 是一个接口,实现类是 ReentrantLock
有两个方法:
lock(); 加锁方法
unlock(); 解锁方法
package com.study.d_thread;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @author big God* @date 2023/1/2 16:32 * @version 1.0*/
class SaleTicket1 implements Runnable {private static int ticket = 100;// 换 Lock 的一种写法 Lock 是一个接口,private Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {// 开始上锁lock.lock();if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票!!");ticket--;} else {System.err.println("(๑′ᴗ‵๑)❤ 不好意思!售罄!!");break;}} catch (Exception e) {e.printStackTrace();} finally {// 无论上面有没有异常// 开始手动开锁lock.unlock();}}}
}
public class Demo3 {public static void main(String[] args) {SaleTicket1 saleTicket1 = new SaleTicket1();new Thread(saleTicket1,"美团").start();new Thread(saleTicket1,"支付宝").start();}
}

7. 守护线程【非重点】

守护线程是用来守护非守护线程的
每个线程都可以或不可以标记为一个守护程序。
非守护线程一旦结束,守护线程就会挂掉
守护线程依附于非守护线程。如果非守护线程消亡,那么守护线程没有执行完也要挂掉!!
帝王制度,皇帝【非守护线程】驾崩,妃子【守护线程】要陪葬
setDaemon
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 
该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。 参数:
on - 如果为 true,则将该线程标记为守护线程。 
package com.study.e_thread;/*** @author big God* @date 2023/1/2 17:51 * @version 1.0*/
class MyThread1 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("守护线程" + i);}}
}
class MyThread2 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.err.println("非守护线程" + i);}}
}
public class Demo1 {public static void main(String[] args) {/*** setDaemon* public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。* 该方法必须在启动线程前调用。** 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。*** 参数:* on - 如果为 true,则将该线程标记为守护线程。*/// Java 中默认的都是非守护线程MyThread1 myThread1 = new MyThread1();Thread thread = new Thread(myThread1);// myThread1 就变成守护线程了thread.setDaemon(true);thread.start();new Thread(new MyThread2()).start();}
}

8. 线程的生命周期

线程的创建:开启线程
可运行状态:线程在等待,线程在抢占
运行状态:线程等待,线程抢占,线程执行
阻塞状态:sleep,加锁
消亡状态:线程死亡

作业:

1.今天的案例理解为主,一定要敲!!!
2.(线程同步)设计四个线程对象对同一个数据进行操作,两个线程执行减操作,两个线程执行加操作
3.银行存钱的下面的这个代码,要一行一行搞懂
4.今天7点自习的时候组长开直播间,统计人数截屏发群里上自习!!!
今天不再布置其他作业了,理解以后赶紧休息,明天讲的东西比今天难的更多!!!
package com.study.f_homework;/*** 银行* @author big God* @date 2023/1/2 18:21 * @version 1.0*/
class Bank {private int account = 200;public int getAccount() {return account;}/*** 用同步方法实现* @param money*/public synchronized void save(int money) {account += money;}/*** 同步代码块实现* @param money*/public void save1(int money) {synchronized (this) {account += money;}}
}
class MyThread implements Runnable {private Bank bank;public MyThread(Bank bank) {this.bank = bank;}@Overridepublic void run() {for (int i = 0; i <10; i++) {// bank.save(10000);bank.save1(10000);System.out.println(Thread.currentThread().getName() + "账户余额为:" + bank.getAccount());}}
}
public class Demo1 {public static void main(String[] args) {Bank bank = new Bank();// 不同线程向一个账户充值MyThread myThread = new MyThread(bank);Thread thread1 = new Thread(myThread,"工商银行");Thread thread2 = new Thread(myThread,"农业银行");thread1.start();thread2.start();}
}

今天的内容

死锁
关于 Object 类下面和线程有关的方法【重点】
生产者消费者模式【难点重点】

1. 死锁

开发中禁止使用死锁
面试会问:
应用场景:并发场景,多个线程,线程之间互不相让。
线程加锁的目的就是为了安全,但是物极必反。
死锁是一种状态,当两个线程互相持有对方的资源的时候,却又不主动释放对方的资源,会导致死锁。代码不会正常的执行,这两个线程就会僵持住,那个线程都不能往下执行。
线程 1 有锁 1 线程 1 想要使用锁 2
线程 2 有锁 2 线程 2 想要使用锁 1
生活中
开锁公司去帮你开锁,开锁公司需要身份证,但是你的身份证在屋子里面。开锁公司需要先提供身份证,你需要开锁才能提供身份证。
线程 2 拿到了锁 2 对象
线程 1 拿到了锁 1 对象
等待锁 1 的释放
等待锁 2 的释放
package com.study1.a_thread;/*** @author big God* @date 2023/1/2 21:09 * @version 1.0*/
class DeadLock implements Runnable {private boolean flag; // 标记属性private Object obj1; // 对象1private Object obj2; // 对象2// 有参构造方法public DeadLock(boolean flag, Object obj1, Object obj2) {this.flag = flag;this.obj1 = obj1;this.obj2 = obj2;}@Overridepublic void run() {if (flag) { // 如果 flag == true, 让线程 1 执行 if 语句里面的代码synchronized (obj1) { // 线程 1 里面锁的是 obj 这个资源System.out.println(Thread.currentThread().getName() + "拿到了锁 1 对象");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// 想去使用 obj2 这个对象System.out.println("等待锁 2 的释放");// 出问题了// obj1 这个资源没有被释放
//                synchronized (obj2) { // 要用 obj2 这个资源
//                    System.out.println(Thread.currentThread().getName() + "拿到了锁 2 对象");
//
//                }}// synchronized 大括号里面的代码执行完以后,锁会自动释放掉 <obj1>,代码不会出问题}if (!flag) { // 如果 flag == false 让线程 2 执行 if 语句里面的代码synchronized (obj2) { // obj2 已经加了锁,资源System.out.println(Thread.currentThread().getName() + "拿到了锁 2 对象");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("等待锁 1 的释放");// 出问题了 obj2 资源没有释放/*** 线程 2 说:线程 1 你把 obj1 这个资源让我用一下呗* 线程 1 说:线程 2 你把 obj2 这个资源让我先用一下,我才能释放 obj1*/1.}}}}
}
public class Demo1 {public static void main(String[] args) {Object o1 = new Object();Object o2 = new Object();// 第一个线程DeadLock deadLock = new DeadLock(true, o1, o2);new Thread(deadLock,"线程 1 ").start();// 第二个线程DeadLock deadLock1 = new DeadLock(false, o1, o2);new Thread(deadLock1,"线程 2 ").start();}
}

2. 和线程线程相关的 Object 类方法

Object 类的方法
public final void wait(long timeout) throw InterruptedException
导致当前线程等待,直到另一个线程调用此对象的 notify() 方法或者 notifyAll() 方法,才能够唤醒当前等待线程!!!!
总结:两个线程,其中一个线程 对象.wait() 方法。那么这个线程就会阻塞。
什么叫阻塞??? 这个线程暂时不执行了
如何让这个线程继续往下执行?
再开另外一个线程。对象.notify() 去唤醒另外一个线程!

总结:

新建两个线程:一个是等待线程等待线程中代码从上往下执行,但是使用了 object.wait() 方法以后,咱们当前的线程就等待了,阻塞了到 wait 之后代码暂时不会往下执行了,需要借助另外一个线程进行唤醒。一个是唤醒线程唤醒线程使用 object.notify() 方法去将,等待线程中的正在等待的线程进行唤醒,让等待线程继续往下执行。好比:在大学谈了个对象,你给你对打电话晚上去吃饭,你对象说你来接我吧,到楼底下等着我,你就是等待线程,你在楼底下等着,你对象就是唤醒线程,你对象要化妆,需要两个小时,你就得站在楼底下两个小时,等你对象化妆结束以后,她来唤醒你,说,走吧,然后你被唤醒和她一起开开心心的去吃饭。咱们 wait 和 notify 方法,实现了线程之间的互通 通信问题!!!!我点了一份宫保鸡丁 我得等待 就是等待线程商家做好以后,唤醒我,别等了哥们。做好了吃吧 商家就是唤醒线程
package com.study1.b_object;/*** @author big God* @date 2023/1/2 21:56 * @version 1.0*//*** 为什么要写 Message 这个类?*  wait 需要 对象.wait() 先有类然后再创建对象 message.wait();*/
class Message {private String message; // 信息public Message(String message) {this.message = message;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "Message{" +"message='" + message + '\'' +'}';}
}
// 等待线程
class WaiterThread implements Runnable {private Message msg; // 要是用 msg 对象 msg.wait()public WaiterThread(Message msg) {this.msg = msg;}// 为啥写构造方法,WaiterThread 和 NotifierThread 里面,保证是同一个 msg 对象@Overridepublic void run() {// 等待线程中 获取线程的名字System.out.println(Thread.currentThread().getName() + "等待唤醒的时间:" + System.currentTimeMillis());/*** 等待应总是发生在循环中,如下面的示例:** synchronized (obj) {* while (<condition does not hold>)* obj.wait(timeout);* ... // Perform action appropriate to condition*      }*/synchronized (msg) {try {msg.wait(); // 当前线程会等待 不会往下执行。// 为什么要加 synchronized, 当调用 wait 方法的时候// 会自动释放锁,然后将 msg 对象放到对象的等待池中} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "被唤醒的时间" + System.currentTimeMillis());System.out.println(Thread.currentThread().getName() + "线程" + msg.getMessage());}}
}
// 唤醒线程
class NotifierThread implements Runnable {private Message msg; // 使用 msg 对象 msg.notify() 去唤醒等待线程public NotifierThread(Message msg) {this.msg = msg;}@Overridepublic void run() {try {Thread.sleep(5000); // 唤醒线程睡 10 秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "开始唤醒等待线程");/*** 此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:** 通过执行此对象的同步实例方法。* 通过执行在此对象上进行同步的 synchronized 语句的正文。* 对于 Class 类型的对象,可以通过执行该类的同步静态方法。* 一次只能有一个线程拥有对象的监视器。*/synchronized (msg) {msg.setMessage("我是修改后的 message 对象");// msg.notify(); // 去唤醒等待线程 让等待线程继续往下执行msg.notifyAll(); // 去唤醒所有的等待线程}}
}
public class Demo1 {public static void main(String[] args) {Message message = new Message("我是 message 对象");WaiterThread waiterThread = new WaiterThread(message);// 如果有两个等待线程呢?// 一次只能有一个线程拥有对象的监视器。 随机唤醒NotifierThread notifierThread = new NotifierThread(message);new Thread(waiterThread,"wait").start();new Thread(waiterThread,"wait1").start();new Thread(notifierThread,"notify").start();}
}

上午内容

1.死锁知道死锁的原理,代码可以不会写!!!2. wait() 方法,notify() notifyAll() 方法

3. join 方法 【开发不用】

为啥 join 方法放在 wait 方法之后,因为 join 方法的底层是 wait() 方法
作用: 让主线程等待,一直等待他的子线程执行完以后,才执行主线程
package com.study1.c_join;/*** @author big God* @date 2023/1/3 10:50 * @version 1.0*/
class FatherThread implements Runnable {@Overridepublic void run() {// 在 FatherThread 线程中去启动 SonThread 这个线程Thread thread = new Thread(new SonThread());// SonThread 是在 FatherThread 线程启动的,// 那么就意味着 SonThread 是 FatherThread 的一个子线程thread.start();try {thread.join(); // thread 的 父线程// 让主线程(父线程)等待,等到子线程执行完以后再执行父线程} catch (InterruptedException e) {e.printStackTrace();}for (int i = 0; i < 1000; i++) {System.out.println("父亲线程:" + i);}}
}
class SonThread implements Runnable {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.err.println("儿子线程:" + i);}}
}
public class Demo2 {public static void main(String[] args) {new Thread(new FatherThread()).start();}
}

面试题:

你如何控制两个线程执行顺序:
1. sleep 让另外一个线程先执行,睡完以后再执行其他线程
2. join 使用 join 让子线程先执行完
3. 线程池

4. 生产者消费者模式

生活中案例:
卖家:汽车厂商
买家:咱们班 40 位同学
XX 想买一个大奔,买车定完车以后需要等。
XX 告知汽车厂商我要买车,如果有,直接发货。如果没有?XX 会进入等待状态,等待汽车厂商生产好以后,唤醒 XX 的等待线程,然后 XX 来买车,如果没有人来买车,汽车厂商会进入到等待【阻塞】状态,如果有人买车,立马唤醒汽车厂商线程,让他赶紧造车
生产者:汽车厂商
消费者: XX
美团 饿了么 等都是这种模式
XX 【消费者】想吃饭? 咋办?
去饭店 然后喊老板【生产者】我要吃饭,我要吃麻辣毛蛋,老板说好的 你先等着。
XX 就进入了等待 wait 状态!!
饭做好了,老板喊消费者吃饭【唤醒 XX 消费者】, XX 在吃饭的时候,饭店在干嘛? 没有人再来吃饭,饭店阻塞状态,等待,等待再上人 唤醒生产者。
现在看第一个问题?为啥是消费者抢到这个执行权,等了 5 秒以后才执行消费这个线程!!!!我让消费者睡了 5 秒钟因为 goos 的初始值是 fasle 肯定生产者先抢到这个执行权,为啥 ? 因为消费者在睡觉但是没有执行生产者,为啥? 因为 goods 的初始值是 fasle 所以生产者在 wait 【有车】, 一旦 wait 等过了 5 秒以后消费者线程睡醒了,睡醒之后先执行消费者。true 之后又唤醒了生产者,让生产者去生产车消费者购买了:五菱价格为:76.0
生产者生产了:大众价值:3000.0
消费者购买了:大众价格为:3000.0
生产者生产了:兰博基尼价值:3.0E7
消费者购买了:兰博基尼价格为:3.0E7
生产者生产了:大众价值:3000.0
消费者购买了:大众价格为:3000.0
生产者生产了:兰博基尼价值:3.0E7
消费者购买了:兰博基尼价格为:3.0E7
生产者生产了:大众价值:3000.0
package com.study1.d_productor;/*** @author big God* @date 2023/1/3 11:31 * @version 1.0*/
// 为啥要写这个类? 是作为两个线程之间的桥梁的共享资源
class Goods {private String name; // 商品的名字private double price; // 商品的价格private boolean isProduct; // 是否需要生产? true 需要生产 false 不需要生产public Goods(String name, double price, boolean isProduct) {this.name = name;this.price = price;this.isProduct = isProduct;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public boolean isProduct() {return isProduct;}public void setProduct(boolean product) {isProduct = product;}
}
// 消费者线程
class Customer implements Runnable {private Goods goods;public Customer(Goods goods) {this.goods = goods;}@Overridepublic void run() {// 消费者消费try {Thread.sleep(5000); // 可有可无} catch (InterruptedException e) {e.printStackTrace();}// 生产者一直生产,消费者一直消费while (true) {synchronized (goods) {// 一直消费,判断商品是否有无,// true 需要生产(没有商品) false 不需要生产(有商品)if (!goods.isProduct()) {// 有商品的情况:消费者直接购买。购买完以后没有商品;了System.out.println("消费者购买了:" + goods.getName() + "价格为:" + goods.getPrice());// 购买完以后商品没了,去修改标记 flag ,然后唤醒生产者goods.setProduct(true);goods.notify();} else {// 没有商品的情况:消费者先唤醒生产者,然后消费者进入等待状态。// 生产者线程需要执行try {goods.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
// 生产者线程
class Producer implements Runnable {private Goods goods;public Producer(Goods goods) {this.goods = goods;}@Overridepublic void run() {int count = 0;// 生产者生产while (true) { // 死循环synchronized (goods) {if (goods.isProduct()) {// true 需要生产// 奇数 生产车 兰博基尼 偶数 生产车 大众if (count % 2 == 0) {// 偶数goods.setName("大众");goods.setPrice(3000);} else {// 奇数goods.setName("兰博基尼");goods.setPrice(30000000);}// 生产者生产后一定要把 flag  属性 设置为 falsegoods.setProduct(false); // false 有车了就不用生产了System.out.println("生产者生产了:" + goods.getName() + "价值:" + goods.getPrice());count++;// 生产完以后,消费者消费,在生产的时候,消费者在干嘛? 消费着在等待// 唤醒消费者goods.notify();} else {// 不需要生产车的时候,生产者在等待try {goods.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
public class Demo1 {public static void main(String[] args) {// false 是不需要生产的Goods goods = new Goods("大众",300,false);// 两个线程共享的是一个资源Customer customer = new Customer(goods);Producer producer = new Producer(goods);new Thread(customer).start();new Thread(producer).start();}
}

我没有听懂,影响后面写项目吗?不影响,咱们以后要学框架,生产者消费者是框架的东西,固定的按照人家的步骤一点一点的理解即可。为了让大家有一个简单的认知!!! 这个是咱们这阶段里面是最难的地方!!!

今天我会给大家多点时间去理解今天的东西。然后写一点作业即可。明天将线程池!!!开发要用但是比较简单。

练习:取快递 存快递

package com.study1.e_customer;/*** 生产者消费者模式练习* @author big God* @date 2023/1/3 22:06 * @version 1.0*/
// 快递对象,顾客和快递员都要通过该对象来进行阻塞和唤醒状态
class Express {// 快递的种类private String species;// 快递的重量private double weight;// flag  是否需要生产?// true 有快递 不需要生产 【生产者进入等待状态 消费者被唤醒】// false 需要生产 【消费者进入等待状态 生产者被唤醒】private boolean flag;public Express(String species, double weight, boolean flag) {this.species = species;this.weight = weight;this.flag = flag;}public String getSpecies() {return species;}public void setSpecies(String species) {this.species = species;}public double getWeight() {return weight;}public void setWeight(double weight) {this.weight = weight;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}
}
// 消费者一直消费
class PersonThread implements Runnable {private Express express;public PersonThread(Express express) {this.express = express;}@Overridepublic void run() {try {// 消费者先睡眠 生产者先放快递Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}while (true) {synchronized (express) {// true 需要生产【没有快递】 false 不需要生产 【有快递】if (express.isFlag()) {// 不知道为什么 换成 err 会产生线程抢占的情况 没有进入等待和唤醒状态 以后问问老师// System.err.println(express.getSpecies() + "被取走了" + "重量是:" + express.getWeight());System.out.println(express.getSpecies() + "被取走了" + "重量是:" + express.getWeight());express.setFlag(false);express.notify();} else {try {express.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
// 生产者一直生产
class Courier implements Runnable {private Express express;public Courier(Express express) {this.express = express;}@Overridepublic void run() {int count = 0;while (true) {synchronized (express) {if (!express.isFlag()) {// 需要存快递if (count % 2 == 0) {express.setSpecies("中通快递");express.setWeight(67);} else if (count % 3 == 0) {express.setSpecies("京东快递");express.setWeight(89);} else {express.setSpecies("顺丰快递");express.setWeight(100);}// true  就是有快递了 不需要放快递了express.setFlag(true);System.out.println("快递员存放了" + express.getSpecies() + "重量是:" + express.getWeight());count++;// 唤醒消费者express.notify();} else {try {// 不需要生产  有快递express.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
public class Demo1 {public static void main(String[] args) {// false 是不用生产的Express express = new Express("中通快递", 56, true);PersonThread personThread = new PersonThread(express);Courier courier = new Courier(express);new Thread(personThread).start();new Thread(courier).start();}
}

练习:小明取钱妈妈存钱

package com.study1.f_homework;import java.util.Scanner;/*** @author big God* @date 2023/1/4 10:24 * @version 1.0*/
class Bank {// 账户余额private double money;public Bank(double money) {this.money = money;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}
}
class Save implements Runnable {private Bank bank;public Save(Bank bank) {this.bank = bank;}@Overridepublic void run() {Scanner scanner = new Scanner(System.in);while (true) {synchronized (bank) {if (bank.getMoney() < 100) {System.out.println("请输入存款金额:");bank.setMoney(bank.getMoney() + scanner.nextInt());System.out.println(Thread.currentThread().getName() + "恭喜您!存款成功!余额为:" + bank.getMoney() + "元");// 存完钱以后告诉取钱线程bank.notify();} else {try {// 不小于 100 存钱线程等待bank.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
class Draw implements Runnable {private Bank bank;public Draw(Bank bank) {this.bank = bank;}@Overridepublic void run() {Scanner scanner = new Scanner(System.in);while (true) {synchronized (bank) {if (bank.getMoney() >= 100) {// 取钱System.out.println("请输入取款金额:");bank.setMoney(bank.getMoney() - scanner.nextInt());System.out.println(Thread.currentThread().getName() + "您好!取款成功! 余额为:" + bank.getMoney() + "元");bank.notify();} else {// 等待try {bank.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
public class Demo1 {public static void main(String[] args) {Bank bank = new Bank(0);Save save = new Save(bank);Draw draw = new Draw(bank);// 两个线程进行取钱,两个线程进行存钱new Thread(draw,"小张").start();new Thread(draw,"老张").start();new Thread(save,"老张").start();new Thread(save,"小张").start();}
}

自我总结:

生产者和消费者模式

  1. 先创建一个共享的资源,生产者和消费者都是对一个共有的资源进行等待和唤醒

  1. 创建一个生产者线程

  1. 声明一个 可操作的对象 也就是上面创建的共享资源

  1. 一般都是死循环 死循环之后开始上锁,锁住当前对象

  1. 进行判断,满足条件,进行操作,操作完毕唤醒消费者线程

  1. 进行判断,不满住条件,线程等待。

  1. 创建一个消费者线程

  1. 声明一个 可操作的对象 也就是上面创建的共享资源 【和生产者操作的是同一个对象】

  1. 一般都是在死循环中操作,循环中开始上锁【如果同时有好几个线程,保证一个线程操作完毕之后,其他线程才开始操作】,锁住当前对象

  1. 进行判断,满足条件,进行操作,操作完毕唤醒生产者线程

  1. 进行判断,不满足条件,线程等待。

  1. 实例化 共享的资源 【操作的对象】

  1. 实例化生产者和消费者线程,并开启线程。

线程池【重点】

线程池是一个容纳了多个线程的容器,其中的线程可以反复的使用。省去了频繁创建线程的对象的操作,无需反复创建线程而消耗更多的资源

工作原理:

![线程池的工作原理 ](D:\AQF\x64\note\note3\img\线程池的工作原理 .png)

在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用长江,总体来说线程池的创建可以分为以下两类:
通过 ThreadPoolExecutor 手动创建线程池
通过 Executors 执行器自动创建线程池

而以上两类创建线程池的方式,又有 7 种具体实现方法,这 7 种实现方法分别是:

  1. Executors.newCachedThreadPool : 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

  1. Executors.newSingleThreadExecutor : 创建单个线程数的线程池,它可以保证先进先出的执行顺序。

  1. Executors.newScheduledThreadpool : 创建一个可以执行延迟任务的线程池。

  1. Executors.newSingleThreadScheduleExecutor : 创建一个单线程的可以执行延迟任务的线程池。

  1. Executors.newWorkStealingPool : 创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK1.8 添加】。

  1. ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

接下来我们分别看看这 7 种线程池的具体使用。

1 FixedThreadPool

创建一个固定大小的线程池,可以控制并发线程数。使用 FixThreadPool 创建 3 个固定数量的线程池:

package com.study2.a_threadpool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author big God* @date 2023/1/4 11:38 * @version 1.0*/
public class Demo1 {public static void main(String[] args) {fixedThreadPool();}public static void fixedThreadPool () {// 创建固定数量的线程放到的线程池里面// 使用执行器自动创建 2 两个线程 都是两个线程// 1. 创建线程池,池子中有两个线程ExecutorService threadPool = Executors.newFixedThreadPool(3);// 2. 创建任务 task  使用 Runnable 接口创建一个任务// Runnable 是一个接口,可以使用匿名内部类的写法Runnable run  = new Runnable() {@Overridepublic void run() {// 放执行的任务System.out.println("任务被执行,线程:" + Thread.currentThread().getName());}};// 3. 线程池执行任务// 执行任务有两个方法: submit() 和 execute()threadPool.execute(run);threadPool.execute(run);threadPool.execute(run);threadPool.execute(run);
//      threadPool.submit(run); 有返回值/*** 总结:*      1.创建线程池*      2.创建任务  Runnable*      3.拿线程池里面的线程去执行任务*/}
}

2 CachedThreadPool

创建一个可缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才会被回收,如果线程数不够,则会新建线程。

package com.study2.b_threadpool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author big God* @date 2023/1/4 15:06 * @version 1.0*/
public class Demo1 {public static void main(String[] args) {cacheThreadPool();}public static void cacheThreadPool () {// 1. 创建线程池ExecutorService threadPool = Executors.newCachedThreadPool();// 2. 执行任务for (int i = 0; i < 10; i++) {// void execute (Runnable command); Runnable 接口// 方法的参数是一个接口,有想法没?threadPool.execute(new Runnable() {@Overridepublic void run() {// 任务System.out.println("任务被执行,线程:" + Thread.currentThread().getName());}});}}
}

线程池创建了 10 个线程来执行相应的任务。

使用场景

CachedThreadPool 是根据短时间的任务量来决定创建的线程数量的,所以它适合短时间内有突发大量任务的处理场景。

3. SingleThreadExecutor

创建单个线程的线程池,它可以保证先进先出的执行顺序。

package com.study2.b_threadpool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author big God* @date 2023/1/4 15:20 * @version 1.0*/
public class Demo2 {public static void main(String[] args) {singleThreadPool();}public static void singleThreadPool () {ExecutorService threadPool = Executors.newSingleThreadExecutor();// 执行任务for (int i = 0; i < 10; i++) {int index = i;threadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println(index +"任务被执行 线程:" + Thread.currentThread().getName());}});}}
}

单个线程的线程池有什么意义?

单个线程的线程池相比于线程来说,它的有点有以下 2 个:

  • 可以复用线程即使是单个线程池,也可以复用线程。

  • 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了以后,可以执行拒绝策略,这些都是线程不具备的。

4.ScheduleThreadPool

创建一个可以执行延迟任务的线程池。使用示例如下:

package com.study2.b_threadpool;import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** @author big God* @date 2023/1/4 15:43 * @version 1.0*/
public class Demo3 {public static void main(String[] args) {scheduleThreadPool();}public static void scheduleThreadPool () {ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);// 添加一个定时任务执行System.out.println("添加任务的时间:" + new Date());for (int i = 0; i < 10; i++) {threadPool.schedule(new Runnable() {@Overridepublic void run() {System.out.println("任务被执行的时间:" + new Date());}}, 3, TimeUnit.SECONDS);}}
}

上述结果可以看出,任务在 3 秒之后被执行力,实现了延迟 3s 再执行任务

5. SingleThreadScheduledExecutor

创建一个单线程的可以执行延迟任务的线程池,此线程可以看做是 ScheduleThreadPool 的单线程池版本。

public static void SingleThreadScheduledExecutor() {// 创建线程池ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();// 添加定时执行任务(2s 后执行)System.out.println("添加任务,时间:" + new Date());threadPool.schedule(() -> {System.out.println("任务被执行,时间:" + new Date());try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}}, 2, TimeUnit.SECONDS);
}

上述结果可以看出,任务在 2 秒之后被执行了

6. newWorkStealingPool

创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的版本才能使用。

package com.study2.b_threadpool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author big God* @date 2023/1/4 16:00 * @version 1.0*/
public class Demo4 {public static void main(String[] args) {workStealingPool();}private static void workStealingPool() {// 创建线程池ExecutorService threadPool = Executors.newWorkStealingPool();// 执行任务for (int i = 0; i < 100; i++) {final int index = i;threadPool.execute(() -> {System.out.println(index + "被执行的线程名: " + Thread.currentThread().getName());});}// 确保任务执行完成while (!threadPool.isTerminated()) {}}
}

从上述结果可以看出,任务的执行顺序是不确定的,因为他是抢占式执行的。

7. ThreadPoolExecutor 【以后开发要用的】

Executor //英*/ɪɡˈzekjətə(r)/*

ThreadPoolExecutor 是最原始,也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置。

package com.qfedu.a_threadpool;import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class Demo5 {public static void main(String[] args) {test();}public static void test () {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>());//执行任务for (int i = 0; i < 21; i++) {int index = i;threadPoolExecutor.execute(new Runnable() {@Overridepublic void run() {System.out.println(index + "被执行, 线程为:" + Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//threadPoolExecutor.shutdown();}}
}
public ThreadPoolExecutor(int corePoolSize, 核心线程数int maximumPoolSize, 最大线程数long keepAliveTime, 存活时间TimeUnit unit, 时间单位BlockingQueue<Runnable> workQueue, 阻塞队列ThreadFactory threadFactory, 创建新的线程RejectedExecutionHandler handler)  拒绝任务
ThreadPoolExecutor 的创建主要参数有 7 个,接下来将进行一一介绍
最大线程数(MaximumPoolSize) 和核心线程数(CorePoolSize)最大线程数(MaximumPoolSize):线程池运行的最大线程数量,受到属性 CAPACITY 的限制,最大为(2^29) -1 (约 5 						 	亿)核心线程数(CorePoolSize): 线程池中保持最小活动数的线程数量,并且不允许超时,除非调用 															allowCoreThreadTimeOut 方法,这个时候最小值是 0.当前线程池数量小于核心线程数时,一个新的任务请求被提交上来时,不管其他线程是否处于空闲状态,都会新建一个新的线程来处理这个请求如果在运行的线程数量超过核心线程数但是小于最大线程数,并且工作队列已满,将创建一个线程处理这个请求。默认情况下,当一个任务请求时,核心线程数才会被创建和启动,但是也可以通过 prestartCOreThread 启动一个核心线程或者 prestartAllCoreThread 启动所有核心线程。创建新的线程(ThreadFactory)ThreadFactory 用来创建线程。如果没有指定 ThreadFactory 的话,默认会使用 Executors # defaultThreadFactory 来创建线程,并且这些线程都是在同一个 ThreadGroup 并且都是非守护线程状态(non-daemon status) 并且拥有相同的优先级(NORM_PRIORITY)。 如果指定了 ThreadFactory 可以修改 ThreadGroup 和线程的名称、守护状态、优先级等。ThreadFactory 如果调用 newThread(Runnable r) 方法返回 null 则创建线程失败,线程池会继续运行但可能不会执行任何任务。线程应该拥有 "modifyThread" 权限,如果工作线程或者其他线程没有拥有这个权限,服务可能会降级,配置更改可能不会及时生效,关闭线程池可能保持在可能终止但未完成的状态。存活时间(Keep-alive times)存活时间(Keep-alive times): 空闲线程等待工作的超时时间 (以纳秒为单位) 如果当前线程池中的线程数超过了核心线程数,超出的部分线程如果空闲的时长大于存活时长,那么他们将会被终止运行。当线程池不被频繁使用的时候,这提供了一种减少资源消耗的方法。存活时间可以通过 setKeepAliveTime(long, TimeUnit) 进行修改,使用 setKeepAliveTime(Long.MAX_VALUE,NANOSECONDS)有效的禁止空闲线程在关闭之前终止。默认情况下,存活策略只适用于当前线程数超过核心线程数的情况下。但是使用方法 allowCoreThreadTimeOut(boolean()) 也可以将这个超时策略应用到核心线程,只要 KeepAliveTime 值不为 0.时间单位(TimeUnit)TimeUnit 是存活时间的单位。阻塞队列(BlockingQueue)任何实现了 BlockingQueue 接口的实现类都可以用来传输和保存提交的任务,阻塞队列的使用和线程池容量大小相关:如果运行的线程少于核心线程数, Executor 总是倾向于添加一个新线程而不是排队。如果核心线程数或更多线程正在运行(不超过最大线程数),Executor 总是倾向于排队请求,而不是添加一个新的线程。如果没有达到最大线程数并且队列未满,将创建新的线程执行任务,如果线程数大于最大线程数,任务将会被拒绝三种排队策略直接传递:工作队列的一个很好的默认选择是 SynchronousQueue, 它将任务交给线程而不用其他方式持有它们。一个新的任务尝试排队时,如果没有可供使用的线程运行它时将会创建一个新的线程。该策略避免了锁定处理可能具有内部依赖关系的请求集,直接传递通常需要无界的最大线程池来避免新的任务提交。这反过来又承认了当命令的平均到大速度快于它们的处理速度时,线程无限增长的可能性。无界队列:无界队列是一个没有预定义容量的队列,使用无界队列例如 LinBlockingQueue 将导致新任务一直在等待,当核心线程数的线程处于工作状态时。因此,不会有超出核心线程数的线程被创建也就是说最大线程数是不起作用的。当任务之间相互独立,互不影响的时候这个选择可能是挺合适的。例如,在 web 服务器中,这种队列在消除短暂的高并发方面很有作用,它允许无界队列增长的平均速度比处理的平均速度快。有界队列:例如 ArrayBlockingQueue, 它能在有限的最大线程数内防止资源耗尽,但是他也更难调整和控制。队列的大小和最大线程数可以相互替换:使用更大的队列数量和小的线程池数量能够最小化 CPU 的使用、系统资源和上下文切换的开销,但也人为的导致了低吞吐量。如果一个任务频繁的阻塞,例如频繁 I/O,系统更多的时间是在频繁的调度而不是运行任务。使用小的队列通常需要大的线程池数量,这会让 CPU 更能充分利用,但是也会遇到不可接受的调度开销,也会降低吞吐量。拒绝任务在调用 execute (Runnable) 提交任务时,在 Executor 已经关闭或者有界队列的最大线程数和队列满的情况下任务会被拒绝。不论在什么情况下,execute 方法调用 RejectedExecutionHandler # ejectedExecution(Runnable,ThreadPoolExecutor) 任务都会根据拒绝策略被拒绝。四种拒绝策略ThreadPoolExecutor 预定义了四中拒绝策略:ThreadPoolExecutor.AbortPolicy,默认的拒绝策略,简单粗暴,拒绝的时候直接抛出异常 RejectedExecutionExpectionThreadPoolExecutor.CallerRunsPolicy, 由调用者执行自身 execute 方法来运行提交进来的任务,从名字 CallerRuns (调用者运行) 中就可以看出。它会提供一个简单的反馈控制机制,这种策略将降低新任务被提交上来的速度。ThreadPoolExecutor.DiscardPolicy,也很简单粗暴,直接丢弃任务,不抛异常。ThreadPoolExecutor.DiscardOldestPolicy,DiscardOldest 丢弃最早的任务,在队列头部也就是最新进入队列的任务会被丢弃,然后尝试提交新任务,如果提交失败会继续重复以上步骤。也可以自己实现 RejectedExecutionHandler 接口,并重写 rejectedExecution 方法来自定义拒绝策略。通俗解释关于上面的参数我试着通俗的说一下,希望我说的你能明白。假如现在有一家外包公司(ThreadPoolExecutor),公司的核心开发(corePoolSize) 有 5 个人,公司最多容纳(maximumPoolSize) 10 个开发,现在公司接了一个项目,核心开发还忙的过来,就将这个项目给其中一个核心开发做,慢慢地销售人员接的项目越来越多, 5 个核心开发都在做项目没有时间再做新的项目,公司为了节省开支新来的项目只能先接过来暂时积压(LinkedBlockingQueue)起来,但是一直积压也不是个事情,客户也会一直催,(有界队列)公司最多只能积压 10 个,积压到 10 个之后公司也还能容纳 5 个开发,不得不再招人处理新的项目。当公司发展的越来越好,接的项目也越来越多这 10 个开发也忙不过来了,有新的项目再进来就只能通过各种方式(RejectedExecutionHandler)了。再后来因为疫情原因,公司能接到的项目也越来越少,开发人员很多(Thread)已经没事儿可做了,大概过了两周时间(KeepAliveTime),公司又为了节省开支就把这些空闲下来的非核心开发给开除了。当然,核心开发也不是说一定不能懂也是可以开的(allowCoreThreadTimeOut(true)),只不过肯定是优先考虑非核心人员。有人说了,项目多的时候为啥不扩大公司规模呢?首先,公司老板最多也就养着几个员工的能力,养的多了老板也吃不消,多招一个人可能也不会使工作效率提高,反而可能会拖累其他开发的进度,能养几个员工也是经过老板深思熟虑加以往的经验总结得出的结果。总结LinkedBlockingQueue: 最大线程数是不起作用 无界队列3 个任务 => 开 3 个线程		任务还会放到队列中排队吗?不会的队列是 0 个任务5 个任务 => 开 5 个线程		队列中 0 个6 个任务 => 开 5 个线程		队列中 1 个 只要线程执行完 5 个以后,最后一个任务的话 随机从 5 个线程中抽取一个然后执行最后的线程12 个任务 => 开 5 个线程		队列中有 7 个new ArrayBlockingQueue<>(10): 有界队列 只能放 10 个任务 最大线程数是 103 个任务 => 开 3 个线程		有界队列中 有没有任务? 没有的6 个任务 => 开 5 个核心线程		有界队列中存 1 个12 个任务 => 开 5 个核心线程		有界队列存 7 个 有界队列可以放 10 个16 个任务 => 开 6 个线程		(为啥会创建 6 个线程?) 有界队列存 10 个 已经放满了 先紧着有界队列放最大的任务书,如果核心线程还不够处理线程数的时候,最大线程开始上21 个任务 => RejectedExecutionException 最多会执行 20 个任务,第 21 个任务 会被拒绝!!!这个拒绝策略的前提是有界队列。真实开发的时候一般使用无界队列!!!

ThreadPoolExecutor 相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控,所以在阿里巴巴《Java 开发手册》是这样规定的:

【强制要求】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加,明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:1. FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VAlUE, 可能会堆积大量的请求,从而导致 OOM。2. CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM.

总结

线程池的创建方式总共有以下 7 种:

  1. Executors.newFixedThreadPool: 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

  1. Executors.newCachedThreadPool: 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

  1. Executors.newSingleThreadExecutor: 创建单个线程数的线程池,它可以保证先进先出的执行顺序。

  1. Executors.newScheduledThreadPool: 创建一个可以执行延迟任务的线程池。

  1. Executors.newSingleThreadScheduledExecutor: 创建一个单线程的可以执行延迟任务的线程池。

  1. Executors.newWorkStealingPool: 创建一个抢占式执行的线程池 ( 任务执行顺序不确定)【JDK 1.8 添加】

  1. ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数

而线程池的创建推荐使用最后一种 ThreadPoolExecutor 的方式来创建,因为使用它可以明确线程池的运行规则,规避资源耗尽的风险。

package com.qf.b_threadpool;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class Demo6 {public static void main(String[] args) {test();}public static void test () {ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));//核心线程数  5//最大线程数  10//执行任务  3个任务,只有3个线程//new LinkedBlockingeque<>//当任务多了线程执行不过来,然后先把任务放到队列中//后续再执行Runnable runnable = new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 9; i++) {for (int j = 1; j <= i; j++) {System.out.print(j + "*" + i + "=" + j * i +"\t");}System.out.println();}}};Runnable runnable1 = new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 9; i++) {for (int j = 1; j <= i; j++) {System.out.print("*");}System.out.println();}}};threadPool.execute(runnable);threadPool.execute(runnable1);}
}

这篇关于千峰教育2218期线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/264182

相关文章

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

Java子线程无法获取Attributes的解决方法(最新推荐)

《Java子线程无法获取Attributes的解决方法(最新推荐)》在Java多线程编程中,子线程无法直接获取主线程设置的Attributes是一个常见问题,本文探讨了这一问题的原因,并提供了两种解决... 目录一、问题原因二、解决方案1. 直接传递数据2. 使用ThreadLocal(适用于线程独立数据)

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api

java线程深度解析(六)——线程池技术

http://blog.csdn.net/Daybreak1209/article/details/51382604 一种最为简单的线程创建和回收的方法: [html]  view plain copy new Thread(new Runnable(){                @Override               public voi

java线程深度解析(五)——并发模型(生产者-消费者)

http://blog.csdn.net/Daybreak1209/article/details/51378055 三、生产者-消费者模式     在经典的多线程模式中,生产者-消费者为多线程间协作提供了良好的解决方案。基本原理是两类线程,即若干个生产者和若干个消费者,生产者负责提交用户请求任务(到内存缓冲区),消费者线程负责处理任务(从内存缓冲区中取任务进行处理),两类线程之

java线程深度解析(四)——并发模型(Master-Worker)

http://blog.csdn.net/daybreak1209/article/details/51372929 二、Master-worker ——分而治之      Master-worker常用的并行模式之一,核心思想是由两个进程协作工作,master负责接收和分配任务,worker负责处理任务,并把处理结果返回给Master进程,由Master进行汇总,返回给客

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的