一、Java 线程、进程、多线程概述
在 Java 中,进程(Process) 和 线程(Thread) 是操作系统进行资源管理和任务调度的重要概念,特别是在高并发和多任务处理场景下,多线程编程尤为重要。
1. 进程(Process)
1.1 进程的定义
进程是操作系统中运行的一个独立程序实例,每个进程都有自己独立的 地址空间、资源(如内存、文件、网络连接等),并且相互隔离(简单地讲就是一个正在运行的应用软件就是一个进程)。
1.2 进程的特点
- 独立性:一个进程的崩溃不会影响其他进程。
- 资源分配单位:每个进程都有自己的 内存空间、文件描述符 等资源。
- 通信复杂:进程间通信(IPC,如管道、消息队列、共享内存等)比线程间通信复杂。
- 系统开销大:创建或销毁进程的开销远高于线程。
2. 线程(Thread)
2.1 线程的定义
线程是 进程内部的执行单元,一个进程可以包含多个线程,这些线程共享进程的内存和资源,但每个线程有自己独立的栈空间和寄存器。(比如:快餐店就是一个进程,而店内的不同员工(点餐员、厨师、服务员)就相当于多个线程,它们可以同时进行不同的任务。)
2.2 线程的特点
- 轻量级:相比进程,线程创建和销毁的开销更小。
- 共享资源:同一进程的多个线程共享 内存、文件句柄等 资源,因此线程间通信更容易,但也可能引发线程安全问题。
- 并行执行:多个线程可以同时运行,提高程序的并发能力。
3. 多线程(Multithreading)
3.1 什么是多线程?
多线程指的是一个进程内同时运行多个线程,这些线程共享内存,但各自有独立的执行路径。
3.2 多线程的优点
- 提高程序效率:多个线程可以并发执行,提高 CPU 利用率。
- 减少资源消耗:多个线程共享内存,而多个进程需要单独的内存空间。
- 提升用户体验:如 UI 线程 + 后台线程,避免界面卡顿。
3.3 线程的生命周期
Java 线程的生命周期主要包括:
- 新建(New):
Thread t = new Thread();
- 就绪(Runnable):
t.start();
- 运行(Running):CPU 调度执行
run() 方法。
- 阻塞(Blocked/Waiting):
sleep(time):让线程休眠
join():等待另一个线程完成
wait():等待某个条件
- 终止(Terminated):线程执行完毕,或
stop()(不推荐)结束。
二、并发和并行
1. 并发
并发:在同一个时间段内,有多个指令在单个CPU上交替执行。
2. 并行
并行:在同一个时刻,有多个指令在多个CPU上同时执行。
三、多线程的实现方式
1. 继承Thread类的方式
步骤:
(1)自己定义一个类继承Thread类
(2)重写run()方法
(3)创建自定义的类对象,并调用**start()**方法来启动线程
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.ryan.thread;
public class ThreadDemo { public static void main(String[] args) { MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread();
myThread1.setName("线程1"); myThread2.setName("线程2");
myThread1.start(); myThread2.start();
} }
class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + " Hello"); } } }
|
2. 实现Runnable接口的方式
步骤:
(1)自己定义一个类实现Runnable接口
(2)重写run()方法
(3)创建自定义的类对象
(4)创建一个Thread类的对象,传入自定义的类对象后,开启线程
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.ryan.thread;
public class ThreadDemo2 { public static void main(String[] args) { MyRun mr = new MyRun();
Thread t1 = new Thread(mr); Thread t2 = new Thread(mr);
t1.setName("线程1"); t2.setName("线程2");
t1.start(); t2.start(); } }
class MyRun implements Runnable {
@Override public void run() { for (int i = 0; i < 100; i++) { Thread t = Thread.currentThread(); System.out.println(t.getName() + " Hello"); } } }
|
3. 利用Callable接口和Future接口方式
特点:可以获取到多线程运行的结果
步骤:
(1)创建一个类MyCallable实现Callable接口
(2)重写call()方法(是有返回值的,表示多线程运行的结果)
(3)创建MyCallable类的对象(表示多线程要执行的任务)
(4)创建FutureTask类的对象(FutureTask是Future接口的实现类)(作用是管理多线程运行的结果)
(5)创建Thread类的对象,并启动线程(表示线程)
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.ryan.thread;
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;
public class ThreadDemo3 { public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable mc = new MyCallable(); FutureTask<Integer> ft = new FutureTask<>(mc); Thread t1 = new Thread(ft); t1.start(); Integer result = ft.get(); System.out.println(result); } }
class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; } }
|
4. 总结
| 实现方式 |
优势 |
适用场景 |
备注 |
继承 Thread 类 |
结构简单,直接调用 start() 启动线程 |
适用于简单的多线程任务 |
受限于 Java 单继承,不推荐 |
实现 Runnable 接口 |
可以继承其他类,线程任务独立 |
适用于大多数多线程任务 |
推荐使用 |
实现 Callable 接口 |
可以返回结果,可以抛出异常 |
适用于需要返回结果的任务 |
需结合 FutureTask 使用 |
四、多线程中常用的成员方法
| 方法名称 |
说明 |
| String getName() |
返回此线程的名称 |
| void setName(String name) |
设置线程的名字(构造方法也可以设置名字) |
| static Thread currentThread() |
获取当前线程的对象 |
| static void sleep(long time) |
让线程休眠指定的时间,单位为毫秒 |
| setPriority(int newPriority) |
设置线程的优先级 |
| final int getPriority() |
获取线程的优先级 |
| final void setDaemon(boolen on) |
设置守护线程 |
| public static void yield() |
礼让线程/出让线程 |
| public static void join() |
插入线程/插队线程 |
第一部分
getName()和setName()细节:
- 如果没有给线程设置名字,线程是有默认的名字的。格式为:
Thread-X(X:表示序号,从0开始)。
- 如果要给线程设置名字,可以用setName()方法进行设置,也可以用构造方法进行设置(需要在自定义类中调用父类Thread类的构造方法)。
currentThread()细节:
- 哪个线程执行到这个方法,此时获取的就是哪条线程的对象。
- 当JVM启动之后,会自动的启动多条线程,其中有一条线程就叫做main线程,作用是调用main方法,并执行其中的代码。在以前,我们写的所有代码,其实都是运行在main线程中的。
sleep()细节:
- 哪条线程执行到这个方法,哪条线程就会在这里停留对应的时间。
- 方法的参数:表示睡眠时间,单位是毫秒(1s = 1000ms)
- 当休眠时间结束,线程会自动醒来,继续执行下面的其它代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.ryan.thread;
public class ThreadDemo4 { public static void main(String[] args) {
Thread t = Thread.currentThread(); String name = t.getName(); System.out.println(name); } }
class MyThreadTwo extends Thread { public MyThreadTwo() { }
public MyThreadTwo(String name) { super(name); }
@Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(this.getName() + "#" + i); } } }
|
第二部分 - 线程优先级
线程的调度
1. 抢占式调度(java使用的就是这个):表示多个线程在抢占CPU的执行权,CPU在什么时候执行哪个线程是不确定的,执行多长时间也是不确定的。体现了随机性。
2. 非抢占式调度:表示所有的线程轮流地执行,执行的时间也是差不多的。
3. 线程的优先级越大,抢到CPU的执行权的概率也就越大(不代表百分百能抢到)。
4. 在java中,线程优先级分为10档,最小的是1,最大的是10,默认为5。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.ryan.thread;
public class ThreadDemo5 { public static void main(String[] args) { MyThreadThree mt = new MyThreadThree(); Thread t1 = new Thread(mt, "飞机"); Thread t2 = new Thread(mt, "坦克");
System.out.println(t1.getPriority()); System.out.println(t2.getPriority()); System.out.println(Thread.currentThread().getPriority());
t1.setPriority(1); t2.setPriority(10); t1.start(); t2.start(); } } class MyThreadThree implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "#" + i); } } }
|
第三部分 - 守护线程
final void setDaemon(boolen on)细节:
- 当其它的非守护线程执行完毕后,守护线程会陆续结束。
- 通俗解释就是,当非守护线程执行结束后,守护线程就没有必要继续执行了,所以也会跟着结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.ryan.thread;
public class ThreaDemo6 { public static void main(String[] args) { Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t1.setName("女神"); t2.setName("备胎"); t2.setDaemon(true);
t1.start(); t2.start(); } } class Thread1 extends Thread { @Override public void run() { for (int i = 1; i <= 10; i++) { System.out.println(this.getName() + "#" + i); } } }
class Thread2 extends Thread { @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(this.getName() + "#" + i); } } }
|
第四部分 - 礼让线程和插队线程
public static void yield()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.ryan.thread;
public class ThreadDemo7 { public static void main(String[] args) { thread3 t1 = new thread3(); thread3 t2 = new thread3();
t1.setName("飞机"); t2.setName("坦克"); t1.start(); t2.start(); } }
class thread3 extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + "#" +i); } Thread.yield(); } }
|
public static void join()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.ryan.thread;
public class ThradDemo8 { public static void main(String[] args) throws InterruptedException { thread4 t = new thread4(); t.setName("土豆"); t.start();
t.join(); for (int i = 0; i < 10; i++) { System.out.println("main线程" + i); } } }
class thread4 extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + "#" +i); } } }
|
五、线程的生命周期
线程执行全过程图

六、线程安全的问题
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票。
不安全的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.ryan.thread;
public class ThreadDemo9 { public static void main(String[] args) { thread5 t1 = new thread5(); thread5 t2 = new thread5(); thread5 t3 = new thread5();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start();
} }
class thread5 extends Thread { static int ticket = 0; @Override public void run() { while(true) { if (ticket < 100) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } ticket++; System.out.println(this.getName() + "在卖第" + ticket + "张票"); } else { break; } } } }
|
出现的问题:
- 相同的票出现了多次。
- 出现了超出范围的票。
同步代码块 synchronized
作用:把操作共享数据的代码锁起来(当有线程在操作该代码块时,其它线程必须等待该线程执行完毕才能抢夺CPU执行权)。
格式:
1 2 3
| synchronized (锁) { 操作共享数据的代码 }
|
特点
- 锁默认是打开的,有一个线程进去后,锁自动关闭。
- 里面的代码全部执行完毕,线程出来,锁自动打开。
改进后的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package com.ryan.thread;
public class ThreadDemo9 { public static void main(String[] args) { thread5 t1 = new thread5(); thread5 t2 = new thread5(); thread5 t3 = new thread5();
t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3");
t1.start(); t2.start(); t3.start();
} }
class thread5 extends Thread { static int ticket = 0;
static Object obj = new Object();
@Override public void run() { while(true) { synchronized (obj) { if (ticket < 100) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticket++; System.out.println(this.getName() + "在卖第" + ticket + "张票"); } else { break; } } } } }
|
细节:
- 不能将
synchronized (锁对象)放在循环while (true)外面,否则就相当于线程1卖完所有票后,其它线程才开始卖票,但是票已经卖完了。
synchronized (锁对象)中的锁对象一定要是唯一的,所以一般都是将锁对象写成当前类的字节码文件。如上例,可更改为:sychronized (thread5.class)。
同步方法 synchronized
就是将synchronized关键字加到方法上。
格式
1
| 修饰符 synchronized 返回值类型 方法名(方法参数) {...}
|
特点
- 同步方法是锁住方法里面所有的代码
- 锁对象不能自己指定:
(1)若当前为非静态方法:则锁对象为:this。
(2)若当前为静态方法:则锁对象为:当前类的字节码文件对象*(当前类名.class)。
技巧
当不知道应该将哪些代码写入同步方法中时,可以先写同步代码块,之后再将同步代码块中的代码抽取成同步方法。
同步代码块版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package com.ryan.thread;
public class ThreadDemo10 { public static void main(String[] args) { MyRunnable1 mr = new MyRunnable1(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); Thread t3 = new Thread(mr); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
class MyRunnable1 implements Runnable {
int ticket = 0;
@Override public void run() {
while (true) { synchronized (MyRunnable1.class) { if (ticket == 100) { break; } else { ticket++;
System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票"); } } try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } }
} }
|
同步方法版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| package com.ryan.thread;
public class ThreadDemo10 { public static void main(String[] args) { MyRunnable1 mr = new MyRunnable1(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); Thread t3 = new Thread(mr); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
class MyRunnable1 implements Runnable { int ticket = 0;
@Override public void run() { while (true) { if (method()) { break; } try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } }
}
private synchronized boolean method() { if (ticket == 100) { return true; } else { ticket++; System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票"); } return false; } }
|
idea中将选中的代码定义为一个方法的快捷键:ctrl + alt + m
拓展 StringBuilder和StringBuffer
根据java API帮助文档,可以看出StringBuilder和StringBuffer的方法都是一样的。但是StringBuilder是线程不安全的,StringBuffer是线程安全的(在源码中StringBuilder的所有方放都是普通的方法,而StringBuffer的所有方法都加了synchronized关键字,都是同步方法)。
选择
当代码都是单线程的,不需要考虑多线程的情况时,选择StringBuilder。
如果是多线程环境下,需要考虑到线程安全问题,则选择StringBuffer。
Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁。为了更清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
Lock中提供了获得锁和释放锁的方法:
(1)void lock(); 获得锁
(2)void unLock(); 释放锁
手动上锁,手动释放锁。
- Lock是接口不能直接实例化,可以采用它的实现类
ReentrantLock来实例化
ReentrantLock的构造方法:
ReentrantLock():创建一个ReentrankLock的实例。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| package com.ryan.thread;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo10 { public static void main(String[] args) { MyRunnable1 mr = new MyRunnable1(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); Thread t3 = new Thread(mr); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
class MyRunnable1 implements Runnable { int ticket = 0; Lock lock = new ReentrantLock();
@Override public void run() { while (true) { lock.lock();
try { if (ticket == 100) { break; } else { ticket++; System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票"); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } }
|
七、死锁
1. 什么是死锁?
死锁是指两个或多个线程相互等待对方释放资源,导致程序永久阻塞的一种情况。发生死锁的典型场景是:
- 线程A持有资源X,想获取资源Y;
- 线程B持有资源Y,想获取资源X;
- 线程A和B都不释放自己已占有的资源,导致程序进入无限等待状态。
2. 死锁产生的四个必要条件
死锁的产生必须同时满足以下四个条件
- 互斥条件(Mutual Exclusion):
- 占有并等待(Hold and Wait):
- 线程已经持有了至少一个资源,并且正在等待获取其他被占用的资源。
- 非抢占条件(No Preemption):
- 线程已经持有的资源不能被其他线程强行抢占,只能由线程自己释放。
- 循环等待条件(Circular Wait):
- 存在一个线程等待的循环链,每个线程都在等待下一个线程所持有的资源。
3. 如何避免死锁
要避免死锁,需要破坏死锁产生的四个必要条件之一。以下是常见的避免死锁的方法:
- 破坏占有并等待条件
- 让线程一次性获取所有需要的资源,而不是分多次获取。
- 例如,使用一个全局锁来保护所有资源的获取。
- 破坏非抢占条件
- 允许线程释放已经持有的资源,如果它无法获取其他资源。
- 例如,使用
tryLock() 方法尝试获取锁,如果失败则释放已持有的锁。
- 破坏循环等待条件
- 对资源进行排序,要求线程按照固定的顺序获取资源。
- 例如,在上面的示例中,可以要求所有线程先获取
lock1,再获取 lock2。
- 使用超时机制
- 在获取锁时设置超时时间,如果超时则放弃并释放已持有的锁。
- 例如,使用
ReentrantLock 的 tryLock(long timeout, TimeUnit unit) 方法。
八、生产者和消费者(等待唤醒机制)
生产者-消费者模式(Producer-Consumer Pattern)是一个十分经典的多线程写作的模式。
1. 什么是生产者-消费者模式?
生产者-消费者模式是一种线程间协作(Inter-thread Cooperation)机制,主要用于解决多线程环境下的生产和消费同步问题。
- 生产者(Producer):负责生产数据,并将数据放入缓冲区(队列)。
- 消费者(Consumer):从缓冲区获取数据并进行处理。
- 缓冲区(共享队列):用于存储生产者生产的数据,消费者从中取数据。
2. 为什么要使用生产者-消费者模式?
- 解耦生产与消费:生产者和消费者不直接交互,而是通过缓冲区进行通信,使它们可以独立扩展。
- 提高系统吞吐量:多个生产者和消费者可以并行工作,提高整体处理能力。
- 解决线程同步问题:适用于多线程环境下的任务协调。
3. 生产者和消费者(常见方法)
| 方法名称 |
说明 |
| void wait() |
当前线程等待,直到被其它线程唤醒 |
| void notify() |
随机唤醒单个线程 |
| void notifyAll() |
唤醒所有线程 |
示例图

代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| package com.ryan.thread;
public class ThreadDemo11 { public static void main(String[] args) {
Cook c = new Cook(); Foodie f = new Foodie(); c.setName("厨师"); f.setName("吃货"); c.start(); f.start();
} }
class Cook extends Thread {
@Override public void run() {
while (true) { synchronized (Desk.lock) { if (Desk.count == 0) { break; } else { if (Desk.foodFlag == 1) { try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println("厨师做了一碗面条"); Desk.foodFlag = 1; Desk.lock.notifyAll(); } } } } } }
class Foodie extends Thread {
@Override public void run() {
while (true) { synchronized (Desk.lock) { if (Desk.count == 0) { break; } else { if (Desk.foodFlag == 0) { try { Desk.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { Desk.count--; System.out.println("吃货正在吃面条,还能再吃" + Desk.count + "碗"); Desk.lock.notifyAll(); Desk.foodFlag = 0; } } } }
} }
class Desk {
public static int foodFlag = 0;
public static int count = 10;
public static Object lock = new Object(); }
|
4. 等待唤醒机制(阻塞队列方式实现)
示例图

阻塞队列的继承结构
阻塞队列实现了四个接口:iterable, Collection, Queue, BlockingQueue。
两个实现类:
ArrayBlockingQueue:底层是数组,有界(创建对象的时候需要指定长度)。
LinkedBlockingQueue:底层是链表,无界但不是真正的无界,最大为int的最大值。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| package com.ryan.thread;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo12 { public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1); Cook1 c = new Cook1(queue); Foodie1 f = new Foodie1(queue); c.start(); f.start();
} }
class Cook1 extends Thread { ArrayBlockingQueue<String> queue;
public Cook1(ArrayBlockingQueue<String> queue) { this.queue = queue; }
@Override public void run() { while (true) { try { queue.put("面条"); System.out.println("厨师放了一碗面条"); } catch (InterruptedException e) { e.printStackTrace(); } } } }
class Foodie1 extends Thread { ArrayBlockingQueue<String> queue;
public Foodie1(ArrayBlockingQueue<String> queue) { this.queue = queue; }
@Override public void run() { while (true) { try { String food = queue.take(); System.out.println(food); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
九、多线程的6种状态
示例图

java中没有运行状态,因为在线程抢到CPU执行权后,线程就会交给操作系统进行处理,而JVM就不管了。
6种状态
新建状态(NEW) –> 创建线程对象
就绪状态(RUNNABLE)–> start方法
阻塞状态(BLOCKED)–> 无法获得锁对象
等待状态(WAITING)–> wait方法
计时等待(TIMED_WAITING)–> sleep方法
结束状态(TERMINATED)–> 全部代码运行完毕
十、线程池
我们使用之前多线程的写法的弊端:用到线程的时候就创建,用完后线程消失,这样会浪费系统资源。
线程池
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
代码实现步骤
- 创建线程池
- 提交任务(提交任务时,线程池底层会创建线程或者复用 已经存在的线程,这些代码不需要我们写,我们负责的就是提交任务)。
- 所有任务全部执行完毕,关闭线程池(实际开发中线程池是不会关闭的,因为服务器一般是24小时都在运行)。
线程池代码实现
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
| 方法名称 |
说明 |
| public static ExecutorService newCachedThreadPool() |
创建一个没有上限的线程池 |
| public static ExecutorService newFixedThreadPool(int nThreads) |
创建一个有上限的线程池 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.ryan.threadpool;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class MyThreadPoolDemo { public static void main(String[] args) {
ExecutorService pool1 = Executors.newCachedThreadPool();
ExecutorService pool2 = Executors.newFixedThreadPool(3);
pool1.submit(new MyRunnable());
pool2.submit(new MyRunnable()); pool2.submit(new MyRunnable()); pool2.submit(new MyRunnable()); pool2.submit(new MyRunnable());
} }
class MyRunnable implements Runnable { @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + "---" + i); } } }
|
十一、自定义线程池
Java 提供了 ThreadPoolExecutor 作为核心的线程池实现,但有时我们需要自定义线程池来满足特定需求,比如:
- 限制最大线程数,防止过载。
- 自定义拒绝策略,比如日志记录或降级处理。
- 监控线程池状态,提高可观测性。
使用ThreadPoolExecutor类
在ThreadPoolExecutor中有七个参数。
参数解析
| 参数 |
说明 |
| 核心线程数量 |
不能小于0 |
| 线程池中最大线程的数量 |
最大数量>=核心线程数量 |
| 空闲时间(值) |
不能小于0 |
| 空闲时间(单位) |
用TimeUnit指定 |
| 阻塞队列(任务队列) |
不能为null |
| 创建线程的方式(创建线程工厂) |
不能为null |
| 要执行的任务过多时的任务拒绝策略 |
不能为null |
任务拒绝策略
| 任务拒绝策略 |
说明 |
| ThreadPoolExecutor.AbortPolicy |
(默认策略):丢弃任务并抛出RejectedExecutionException异常 |
| ThreadPoolExecutor.DiscardPolicy |
丢弃任务,但是不抛出异常,这是不推荐的做法 |
| ThreadPoolExecutor.DiscardOldestPolicy |
抛弃队列中等待最久的任务,然后把当前任务加入队列中 |
| ThreadPoolExecutor.CallerRunsPolicy |
调用任务的run()方法绕过线程池直接执行 |
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.ryan.threadpool;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;
public class MyThreadPoolDemo1 { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor( 3, 6, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); } }
|
小结:
步骤
- 创建一个空的线程池
- 有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程
不断提交任务,会有以下三个临界点
- 当核心线程满时,再提交任务就会排队
- 当核心线程满,队伍满时,会创建临时线程
- 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略