1、线程
Thread 源码面试–大全
java面试–线程
java基础多线程
1.1线程与进程
进程
- 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
- 进程是系统进行资源分配和调度的一个独立单位.
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分 成若干个线程
- 进程中负责程序执行的执行单元。一个进程中至少有一个线程
1.2线程调度
分时调度
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用的时间
抢占式调度
- 优先让优先级高的线程使用CUP,如果线程级相同,那么会随机选择一个。java使用的是抢占式调度,(默认为5,共有1–10级)
- cpu使用抢占式调度模式在多个线程间进行着高速的切换。其实,多线程 程序并不能提高程序的运行速度,但能提高程序的运行效率,让cpu的使用率更高
1.3同步与异步
同步:排队执行,效率低但是高
异步:同步执行,效率高但是数据不安全
例如:a线程判断出一个变量时10,准备拿来运行的时,b线程此时将变量改为了20,此时线程就会出错
1.4并发与并行
并发:指两个或多个事件在同一事件段内发生
- 通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时
并行:指两个或多个事件在同一时刻(同时发生)
- 多核电脑。多个cpu实例或者多台机器同时执行一段处理逻辑
1.5线程的基本状态
1.5.1状态说明
- NEW 表示线程创建成功,但还没有运行,在 new Thread 后,没有 start 前,线程的状态都是 NEW;
- 当调用start(),进入RUNNABLE,当前线程sleep()结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入RUNNABLE
- 当线程运行完成、被打断、被中止,状态都会从 RUNNABLE 变成 TERMINATED
- 如果线程正好在等待获得 monitor lock 锁,比如在等待进入 synchronized 修饰的代码块或方法时,会从 RUNNABLE 转至 BLOCKED
- WAITING 和 TIMED_WAITING 类似,都表示在遇到 Object#wait、Thread#join、LockSupport#park 这些方法时,线程就会等待另一个线程执行完特定的动作之后,才能结束等待,只不过 TIMED_WAITING 是带有等待时间的
1.5.2 线程的优先级
优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行,在 Java 源码中,优先级从低到高分别是 1 到 10,线程默认 new 出来的优先级都是 5。
分别为最低1,普通(默认优先级)5,最大优先级10
![](https://img-blog.csdnimg.cn/img_convert/317d687c96ca869151e496217eeeab5e.webp?x-oss-process=image/format,png#clientId=ub7dae5ca-816a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u0ee8cc15&margin=[object Object]&originHeight=792&originWidth=1304&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ud605b995-df29-4d43-89a0-a2f3dc3f053&title=)
使用以下方式获取或改变优先级
- getPriority() ,setPriority(int xxx)
package com.xxx.state;
/**
* 测试线程优先级:getPriority()
*/
public class TestPriority {
public static void main(String[] args) {
// 主线程优先级 main(Thread-0 --> 5)
System.out.println(Thread.currentThread().getName() +
" --> " + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
// 先设置优先级,再启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
// MAX_PRIORITY=10 最大优先级
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
// t5.setPriority(-1); 报错
// t5.start();
// t6.setPriority(11); 报错
// t6.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" --> " + Thread.currentThread().getPriority());
}
}
2、线程的实现
线程的实现有三种方式
2.1继承Thread类(重要)
不建议使用:避免OOP单继承局限性
- 自定义线程继承继承Thread类;
- 重写run()方法,编写线程执行体;
- 创建线程对象,调用start( )方法启动线程
public class MyThread extends Thread{
//重写了thread 的 run 方法
// run方法就是线程要执行 的 任务方法
//重写run方法,开辟一条新的执行路径
//路径的执行 方式,调用thread的start()来启动任务
@Override
public void run (){
for (int i = 0; i < 20; i++) {
System.out.println("run 方法线程 -- " + i)
}
}
// main 线程:主线程
public static void main(String[] args) {
// 创建线程对象
MyThread MyThread = new MyThread();
// 调用 start() 开启线程
MyThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("main 主线程 -- " + i);
}
}
2.2实现Runnable接口
推荐使用runnable接口,因为Java、单继承的局限性
- 自定义线程类,实现 Runnable 接口;
- 实现 run() 方法,编写线程执行体;
- 创建线程对象,调用 start() 方法,启动对象
public class MyRunnable implements Runnable{
/**
* 用于给线程进行执行的任务
*/
@Override
public void run() {
//线程任务
for (int i=0; i<10; i++){
System.out.println("窗前明月光"+i);
}
}
}
public class Demo {
/**
* 多线程技术
*实现Runnable 与 继承Thread相比有如下优势:
* 1。通过创建任务,然后给线程分配的方式来实现的多线程。更适合多个线程同时执行相同的任务的情况
* 2.可以避免单继承所带来的局限性
* 3。任务与线程本身是分离的,提高了程序的健壮性
* 4.后续学习的线程池技术,接受Runnable类型的任务,不接受thread类型的线程
*/
public static void main(String[] args) {
//实现Runnable
//1. 创建一个任务对象
MyRunnable r = new MyRunnable();
//2. 创建一个线程,,并为其分配一个任务
Thread t = new Thread(r);
//3. 执行这个线程
t.start();
for (int i=0; i<10; i++){
System.out.println("疑是地上霜"+i);
}
}
}
总结两种方式对比
实现Runnable 与 继承Thread相比有如下优势:
1.通过创建任务,然后给线程分配的方式来实现的多线程。更适合多个线程同时执行相同的任务的情况
2.可以避免单继承所带来的局限性
3.任务与线程本身是分离的,提高了程序的健壮性
4.后续学习的线程池技术,接受Runnable类型的任务,不接受thread类型的线程
接口定义
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
2.3Callable使用步骤
- 实现 Callable 接口,需要返回值类型;
- 重写 call 方法,需要抛出异常;
- 创建目标对象;
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future<> result1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get();
- 关闭服务:ser.shutdownNow();
Callable 优缺点:
- 优点:可以定义返回值、可以抛出异常;
- 缺点:实现方式比较复杂
1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
3. 通过Thread,启动线程
new Thread(future).start();
3.3.2Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thead.start()启动线程
3.3.3Runnable 与 Callable的不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
3.3.4Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。
2.4守护线程
- 线程分为 用户线程 和 守护线程;
- 虚拟机 必须确保用户线程 执行完毕;
- 虚拟机 不用等待守护线程 执行完毕;
- 守护线程,如:后台记录操作日志,监控内存垃圾回收等待,工具类……
创建守护进程 thread.setDaemon(true)
package com.xxx.state;
/**
* 守护线程
*/
public class TestDaemon {
public static void main(String[] args) {
Guard guard = new Guard();
You you = new You();
Thread thread = new Thread(guard);
// 默认 false:表示是用户线程,正常线程都是用户线程
thread.setDaemon(true);
// 守护线程启动
thread.start();
// 用户线程启动
new Thread(you).start();
}
}
// 守护线程
class Guard implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("守护线程一直运行");
}
}
}
// 用户线程
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("用户线程 " + i);
}
System.out.println("------用户线程结束------");
}
}
3、线程的基本方法
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等。
3.1线程等待wait
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调 用 wait()方法后,会释放对象的锁。因此,_wait 方法一般用在同步方法或同步代码块中。 _
3.2线程睡眠 sleep
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有锁,sleep(long)会导致线程进 入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态
public static void main(String[] args) throws InterruptedException {
//线程的休眠 sleep
for (int i = 0; i < 10; i++) {
System.out.println(i);
Thread.sleep(1000);
}
}
3.3线程唤醒notify
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择随机唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有** notifyAll()** ,唤醒再次监视器上等待的所有线程。
3.4线程让步 yield
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并 不敏感。
3.5线程插队join
join( ) 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态, 回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。
( join()线程插队,例如在main线程中创建了一个子线程,子线程child.join(),那么main线程将会让出cpu等子线程结束,再重新回到cpu竞争中)
为什么要用 join()方法?
很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线 程结束后再结束,这时候就要用到 join() 方法。
System.out.println(Thread.currentThread().getName() + "线程运行开始!");
//currentThread()返回正在被调用的线程
Thread thread1 = new Thread();
thread1.setName("线程 B");
thread1.join();
System.out.println("这时 thread1 执行完毕之后才能执行主线程");
3.6线程中断 interrupt
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程 本身并不会因此而改变状态(如阻塞,终止等)。
- 调用 interrupt()方法并不会中断一个正在运行的线程。也就是说处于 Running 状态的线程并不会 因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
- 若调用 sleep()而使线程处于 TIMED-WATING 状态,这时调用 interrupt()方法,会抛出 InterruptedException,从而使线程提前结束 TIMED-WATING 状态。
- 许多声明抛出InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异 常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
- 中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程 thread的时候,可以调用thread.interrupt()方法,在线程的run方法内部可以根据 thread.isInterrupted()的值来优雅的终止线程。
4、线程同步
4.1介绍
- 多个线程操作同一个资源 ;
- 并发:同一个对象,被多个线程同时操作。
(10086多人同时抢票、两个地方同时取钱)
- 现实生活中,我们会遇到 同—个资源,多个人都想使用 的问题,比如:食堂排队打饭,每个人都想吃饭,最天然的解決办法就是:排队,一个个来。
- 处理多线程问题时,多个线程访问同一个对象,并且,某些线程还想修改这个对象,这时,就需要线程同步。
- 线程同步:其实就是一种等待机制,多个需要同时访问此对象的线程,进入这个 对象的等待池 形成队列,等待前面线程使用完毕,下一个线程再使用。
4.2同步的方法
- 由于可以通过 private 关键字,来保证数据对象,只能被方法访问,所以只需要针对方法,提岀一套机制,这套机制就是: synchronized 关键字,它包括两种用法: synchronized 方法 和 synchronized 块。
- 同步方法:public synchronized void method (int args) {}
- synchronized 方法,控制对 “对象” 的访问,每个对象对应一把锁,每个 synchronized 方法,都必须获得调用该方法的对象的锁,才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回,才释放锁,后面被阻塞的线程,才能获得这个锁,继续执行。
- 缺陷:若将一个大的方法,申明为 synchronized 将会影响效率。
package com.xxx.syn;
/**
* 安全买票
* 同步方法:synchronized
*/
public class SafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station, "用户1").start();
new Thread(station, "用户2").start();
new Thread(station, "用户3").start();
}
}
class BuyTicket implements Runnable {
// 票
private int ticketNums = 10;
// 外部停止标志
private Boolean flag = true;
@Override
public void run() {
// 买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 买票
// synchronized 同步方法,锁的是 this
private synchronized void buy() throws InterruptedException {
// 判断是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
// 模拟延时
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() +
"拿到了第 " + ticketNums-- + " 张票");
}
}
4.3同步块 synchronized
- 同步块:synchronized (Obj) {}
Obj 称之为同步监视器
- Obj 可以是任何对象,但是推荐使用共享资源,作为同步监视器。
- 同步方法中,无需指定同步监视器,因为同步方法的同步监视器,就是 this,就是这个对象本身,或者是 class。
同步监视器的执行过程:
- 第一个线程访问,锁定同步监视器,执行其中代码;
- 第二个线程访问,发现同步监视器被锁定,无法访问;
- 第一个线程访问完毕,解锁同步监视器;
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
- 同步块:锁的对象就是变化的量,需要增、删、改的对象
- 实例 2:同步块
package com.xxx.syn;
/**
* 安全取钱:两个人同时取一个账户的钱
* 同步块:synchronized (Obj) {}
* 锁的对象就是变化的量,需要增、删、改的对象
*/
public class SafeBank {
public static void main(String[] args) {
// 账户
Account account = new Account(1000, "个人账户");
Drawing youA = new Drawing(account, 50, "YouA");
Drawing youB = new Drawing(account, 100, "YouB");
youA.start();
youB.start();
}
}
//账户
class Account {
int money; // 余额
String cardName; // 卡名
public Account(int money, String cardName) {
this.money = money;
this.cardName = cardName;
}
}
// 银行:模拟取款
class Drawing extends Thread {
// 账户
Account account;
// 取钱
int drawingMoney;
// 手里的钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
// super(name) = 父类构造方法(name)
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
// 取钱
// synchronized 默认锁的是 this
@Override
public void run() {
// 同步块:锁的对象就是变化的量,需要增、删、改的对象
synchronized (account) {
// 判断有没有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + " 钱不够,取不了!");
return;
}
// 延时:可以放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额 = 余额 - 取的钱
account.money = account.money - drawingMoney;
// 手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.cardName + "余额为:" + account.money);
// Thread.currentThread().getName() = this.getName()
System.out.println(this.getName() + "手里的钱:" + nowMoney);
}
}
}
4.4Lock锁
- 从 JDK 5.0 开始,Java 提供了更强大的线程同步机制:通过显式定义同步锁对象,来实现同步。同步锁使用 Lock 对象充当。
- java.util.concurrent.locks.Lock 接口,是控制多个线程,对共享资源进行访问的工具。锁,提供了对共享资源的独占访问,每次只能有一个线程,对 Lock 对象加锁,线程开始访问共享资源之前,应先获得 Lock 对象。
- ReentrantLock 类,实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock,可以显式加锁、释放锁。
锁代码格式
-
// 加锁 lock.lock();
-
解锁 lock.unlock();
class A{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try{
// 保证线程安全的代码
}
finally{
lock.unlock();
// 如果同步代码有异常,要将unlock()写入finally语句块
}
}
}
实例
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试 Lock 锁
*/
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
// 票
private int ticketNums = 10;
// 定义 Lock 锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (ticketNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
} finally {
// 解锁
lock.unlock();
}
}
}
}
4.5 synchroized 与 Lock 对比
- Lock 是显式锁 (手动开启和关闭锁,别忘记关闭锁), synchronized 是隐式锁, 出了作用域自动释放;
- Lock 只有代码块锁,synchronized 有代码块锁和方法锁;
- 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性 (提供更多的子类);
优先使用顺序:
- Lock > 同步代码块 (已经进入了方法体,分配了相应资源) > 同步方法 (在方法体之外)
5、死锁
产生死锁的四个必要条件
- 互斥条件:一个资源毎次只能被一个进程使用;
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件∶进程已获得的资源,在末使用完之前,不能强行剥夺;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
上面列出了死锁的四个必要条件,只要想办法破其中的任意一个,或多个条件,就可以避免死锁发生
银行家算法,预防死锁、
6、进程通信
应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费;
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止;
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
- Java提供了几个方法解决线程之间的通信问题。
- 注意:均是 Object 类的方法,都只能在同步方法,或者同步代码块中使用,否则会抛出异常 IIIegalMonitorStateException。
- 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件:
- 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费;
- 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费;
- 在生产者消费者问题中,仅有 synchronized 是不够的:
- synchronized 可阻止并发更新同一个共享资源,实现了同步;
- synchronized 不能用来实现不同线程之间的消息传递(通信)。
管程法–缓冲区
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
package com.xxx.gaoji;
/**
* 测试:生产者消费者模型-->利用缓冲区解决:管程法
* 生产者、消费者、产品、缓冲区
*/
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
// 生产者
class Productor extends Thread {
// 缓冲区
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
// 生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Product(i));
System.out.println("生产了 " + i + " 件商品");
}
}
}
// 消费者
class Consumer extends Thread {
// 缓冲区
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
// 消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了--> " + container.pop().id + " 件商品");
}
}
}
// 产品
class Product {
// 产品编号
int id;
public Product(int id) {
this.id = id;
}
}
// 缓冲区
class SynContainer {
// 需要一个容器大小
Product[] products = new Product[10];
// 容器计数器
int count = 0;
// 生产者放入产品
public synchronized void push(Product product) {
// 如果容器满了,就需要等待消费者消费
/*
如果是 if 的话,假如消费者1 消费了最后一个,
这时 index 变成 0 此时释放锁,被消费者2 拿到,而不是生产者拿到,
这时消费者的 wait 是在 if 里,所以它就直接去消费 index-1 下标越界,
如果是 while 就会再去判断一下 index 得值是不是变成 0 了
*/
while (count == products.length) {
// 生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,就需要放入产品
products[count] = product;
count++;
// 通知消费者消费
this.notify();
}
// 消费者消费产品
public synchronized Product pop() {
// 判断能否消费
while (count <= 0) {
// 等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果可以消费
count--;
Product product = products[count];
// 消费完了,通知生产者生产
this.notify();
return product;
}
}
信号量
并发协作模型:生产者/消费者模式 —> 信号灯法
package com.xxx.gaoji;
/**
* 测试:生产者消费者模型 2 -->信号灯法,标志位解决
*/
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
// 生产者 --> 演员
class Player extends Thread {
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("节目--1");
} else {
this.tv.play("节目--2");
}
}
}
}
// 消费者 --> 观众
class Watcher extends Thread {
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.tv.watch();
}
}
}
// 产品 --> 节目
class TV {
// 演员表演,观众等待 T
// 观众观看,演员等待 F
// 表演的节目
String voice;
// 标志位
Boolean flag = true;
// 表演
public synchronized void play(String voice) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + voice);
// 通知观众观看
// 通知唤醒
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;
}
// 观看
public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:" + voice);
// 通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
![](https://img-blog.csdnimg.cn/img_convert/f08d5a537ff00989714583d2f026652a.webp?x-oss-process=image/format,png#clientId=ub7dae5ca-816a-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u46b0c7bb&margin=[object Object]&originHeight=218&originWidth=245&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ubf926e4b-cf21-463c-a925-3a0101dcf95&title=)
7、线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
线程池的好处
-
降低资源消耗。
-
提高响应速度。
-
提高线程的可管理性。
-
corePoolSize:核心池的大小;
-
maximumPoolSize:最大线程数;
-
keepAliveTime:线程没有任务时,最多保持多长时间后会终止
Java中的四种线程池
ExecutorService
- JDK 5.0 起提供了线程池相关 API:ExecutorService 和 Executors。
- ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor
- void execute( Runnable command):执行任务命令,没有返回值,一般用来执行 Runnable;
- Future submit( Callabletask):执行任务,有返回值,一般又来执行 Callable;
- void shutdown():关闭连接池。
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
1、缓存线程池 Executors.newCachedThreadPool()
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
2、定长线程池 newFixedThreadPool(count)
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:3. 单线程线程池
4. 周期性任务定长线程池
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
3、单线程池 newSingleThreadExecutor()
效果与定长线程池 创建时传入数值1 效果一致.
/**
* 单线程线程池.
* 执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 4. 不空闲,则等待 池中的单个线程空闲后 使用
*/
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
4、周期性任务定长线程池
public static void main(String[] args) {
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务 .*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
/*service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,TimeUnit.SECONDS);
*/
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2,TimeUnit.SECONDS);
}