3305 字
17 分钟
多线程

多线程#

概念#

  1. 多进程:window系统就是一个多进程的系统,同时运行多个应用程序。CPU交替执行这些进程,进程执行的间隔比较短(几毫秒)。CPU要执行哪些进程、执行多长时间,是由CPU决定的
  2. 多线程:线程是一段顺序代码或是一个任务(代码),进程中包含多个线程。也是由CPU交替执行这些线程。完成多线程的并发执行。

多线程的实现#

Thread方式实现#

Thread类的对象就是一个多线程,JVM允许多个Thread对象中的代码并发的运行。

定义一个子类,继承Thread类,重写run方法,在run方法中编写线程执行的代码

实现一个火车买票的例子:

public class MyThread extends Thread {
public MyThread(String name){
super(name);
}
@Override
public void run() {
//卖火车票 1~1000
for (int i = 1; i <= 1000; i++) {
System.out.println("窗口"+Thread.currentThread().getName()+"卖了"+i+"张票");
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread("A");
MyThread t2 = new MyThread("B");
MyThread t3 = new MyThread("C");
//不要调用run方法,否则还是顺序执行
//start方法两件事:1.启动多线程;2.在线程中执行run
t1.start();
t2.start();
t3.start();
}
}

Runnable接口方式#

单独定义线程要执行的任务,定义一个类实现Runnable接口,创建Thread类的对象出入Runnable对象

public class MyRunnable implements Runnable{
String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
//卖火车票 1~1000
for (int i = 1; i <= 1000; i++) {
System.out.println(name+"窗口"+Thread.currentThread().getName()+"卖了"+i+"张票");
}
}
}
public static void main(String[] args) {
MyRunnable r = new MyRunnable("哈西站");
Thread t1 = new Thread(r,"A");
Thread t2 = new Thread(r,"B");
MyRunnable r2 = new MyRunnable("哈尔滨站");
Thread t3 = new Thread(r2,"C");
//t1.start() 两件事: 1.启动多线程 ; 2. 调用r.run()
t1.start();
t2.start();
t3.start();
}

Runnable方式的好处:

  1. MyRunnable类还可以继承其他类
  2. 比较灵活:例如实现分组统计票数

线程的生命周期#

  1. new (新建状态)
  2. runnable(可执行状态) :start方法进入状态
  3. running(执行状态):CPU调用线程,yield方法进入到runnable状态 ; 进入阻塞状态
  4. blocking(阻塞状态) : 只能有running进入blocking ;只能blocking进入runnable
  5. dead(死亡状态):run方法执行结束或出现异常

image-001

Thread类的方法#

  1. Thread.yield方法:当前线程进入到runnable状态,让CPU结束当前线程的调度。但是我们不能保证CPU再次调度这个线程。
  2. 阻塞线程方法:

Thread.sleep方法: 当前线程休眠指定的毫秒数,进入blocking状态,当达到设定的时间,进入runnable

应用:启动一个服务器备份任务的多线程,在每天凌晨2点执行一段代码(可能用时10分钟),用sleep完成

public class BackRunnable implements Runnable{
@Override
public void run() {
while(true){
Calendar calendar = Calendar.getInstance();
if(calendar.get(Calendar.HOUR_OF_DAY)==2){
System.out.println("执行备份程序(10分钟)");
}
try {
Thread.sleep(60*60*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

join方法: 当A线程在B线程中执行了join方法 ,B线程要等待A线程执行完毕后,再执行join后面的代码。如果A线程没有执行完毕 ,B线程进入阻塞

public class Main {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r,"A");
Thread t2 = new Thread(r,"B");
Thread t3 = new Thread(r,"C");
//t1.start() 两件事: 1.启动多线程 ; 2. 调用r.run()
t1.start();
t2.start();
t3.start();
//判断t1线程是否执行完毕,如果没有执行完毕 当前线程要阻塞
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖完3000张票了,火车发车了-----------------");
}
}
  1. 守护线程 : 守护线程一般是为其他线程提供服务的一个线程,当其守护的线程结束后,自己自动结束。我们可以创建一个多线程,setDaemon()方法变为守护线程,是其他线程的守护线程
Thread t1 = new Thread(new PrinceRunnable());
Thread t2 = new Thread(new PrincessRunnable());
t2.setDaemon(true);
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
  1. 设置线程优先级方法:setPriority()方法: 低、普通、高 ,线程默认的优先级是普通

线程锁#

多个线程在使用同一个资源(对象、数据)时,只能保证让一个线程使用,否则将会出现数据问题。

当一个线程使用这个对象时,为对象加锁,另一个线程就不能使用这个对象,将等待,直到锁被释放,等待的线程才能使用这个对象

synchronized关键字#

  1. synchronized修饰方法: 当一个线程执行了这个方法,线程要获得这个对象的互斥锁(对象锁,每个对象都 有对象锁),如果获得了就能执行这个方法,如果获得不了(锁已被其他线程获得),则等待(runnable)。方法执行完毕后,锁被释放
public class Account {
int money = 20000;
public synchronized void add(){
money+=1000;
System.out.println("存1000余额是:"+money);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void sub(){
money-=1000;
System.out.println("取1000余额是:"+money);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Account account = new Account();
AddRunnable add = new AddRunnable(account);
SubRunnable sub = new SubRunnable(account);
new Thread(add).start();
new Thread(sub).start();
}
}
  1. synchronized语句块
public void add(){
//线程要获得哪个对象的互斥锁
synchronized (this) {
money += 1000;
System.out.println("存1000余额是:" + money);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sub(){
synchronized (this) {
money -= 1000;
System.out.println("取1000余额是:" + money);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

synchronized属于重量级锁,在获得锁、释放锁时效率低,

JVM将synchronized锁设置了四个级别:无锁>偏向锁>轻量锁>重量锁

CAS算法

死锁问题#

A线程等待B线程释放锁,B线程要等待A线程释放锁 ,程序并没有结束,但是程序什么也没有执行

public class TestLock implements Runnable{
boolean flag ;
static Object obj1 = new Object();
static Object obj2 = new Object();
public TestLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while(true) {
if (flag) {
synchronized (obj1) {
synchronized (obj2) {
System.out.println("obj1---obj2");
}
}
} else {
synchronized (obj2) {
synchronized (obj1) {
System.out.println("obj2---obj1");
}
}
}
}
}
public static void main(String[] args) {
TestLock lock1 = new TestLock(true);
TestLock lock2 = new TestLock(false);
new Thread(lock1).start();
new Thread(lock2).start();
}
}

如何避免死锁:

  1. 每个线程 加锁顺序尽量一致,最简单的但是业务需求可能是不允许的
  2. 服务生策略 , 创建一个守护线程,判断线程的状态是否是死锁状态,中断等策略,实现较容易
  3. 设置加锁时间,实现较难

线程八锁: 对象锁的八种情况

  1. 线程占用了对象的互斥锁时,其他线程依然可以访问这个对象没有synchronized修饰的方法
  2. 当线程执行到sleep时,不释放互斥锁
  3. 当线程访问对象synchronized非静态方法时,获得的是对象锁;当线程访问类的synchronized静态方法时,获得的是类锁

线程间的通信(wait和notify)#

  1. wait方法 :无参方法, 是Object类的方法,意味着所有对象都有wait方法。当调用一个对象的wait方法,当前获得这个对象互斥锁的线程将等待;如果调用对象的wait方法时,没有线程获得这个对象的互斥锁,将抛出异常。

wait方法:带毫秒值参数的方法

  1. notify方法:当调用了一个对象的notify方法时,在这个对象上等待的线程中随机唤醒一个线程。

notifyAll方法:唤醒当前对象所有等待的线程。

wait和sleep的区别

wait可以被主动唤醒(notify方法),会释放互斥锁,属于Object类的方法

sleep只能被动唤醒(达到指定时间),不释放互斥锁,属于Thread类的方法

使用wait和notify实现生产者和消费者的模型

有一个一定数量的资源,有很多生产者生产资源,很多消费者消费资源,实现一个模型让资源保持一定的数量。

public class Resource {
int count = 20;
public synchronized void add(){
notifyAll();
while(count>20){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println("生产了一个,剩余:"+count);
/*       try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
public synchronized void sub(){
notifyAll();
while(count<20){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println("消费了一个,剩余:"+count);
/*       try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
public static void main(String[] args) {
Resource resource = new Resource();
AddRunnable addRunnable = new AddRunnable(resource);
SubRunnable subRunnable = new SubRunnable(resource);
Thread p1 = new Thread(addRunnable);
Thread p2 = new Thread(addRunnable);
Thread p3 = new Thread(addRunnable);
Thread s1 = new Thread(subRunnable);
Thread s2 = new Thread(subRunnable);
Thread s3 = new Thread(subRunnable);
p1.start();
p2.start();
p3.start();
s1.start();
s2.start();
s3.start();
}
}

线程池#

多线程需求:

  1. 开发一个游戏,只用到3个线程,创建多线程完成需求,不需要使用线程池
  2. 有一个Web服务器(网站的服务器),例如公司的网站,每个用户访问网站发送一次请求响应就结束,服务器对请求响应是并发操作的处理,需要创建大量的多线程。new Thread方法就会耗费很多资源,服务器会变慢。 使用线程池管理多线程对象
  3. 有一个网站系统Web服务器(淘宝、支付宝),访问量非常巨大,会使用微服务架构。

线程池是用来管理多线程(创建、状态、销毁等)的一种缓冲池技术。

线程池的核心类ThreadPoolExecutor#

ThreadPoolExecutor构造器的核心参数:

  1. 核心线程数:如果核心线程数量的线程都忙且队列满了,再来任务时,就会创建新的线程;如果线程数大于核心线程数,且空闲线程的空闲时间大于了设定的存活时间,要销毁空闲的线程,直到线程数量达到了核心线程数。

  2. 最大线程数:线程池能创建的最多线程数量。

  3. 存活时间:空闲的时间

  4. 时间单位:存活时间的单位

  5. 队列:当达到了最大线程数,来任务时,将在队列中等待,有空闲线程则会消费队列中的任务。

  6. 拒绝策略:当线程都忙,队列也满了,采取设置的拒绝策略,包括:

    拒绝并抛出异常

    在线程池执行的线程中,执行这个任务。

    队列中最旧的任务放弃

    直接放弃,什么也不做

示例:

public class TestRunnable implements Runnable {
int i ;
public TestRunnable(int i){
this.i = i;
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行了"+i+"号任务");
}
}
public class Teset {
public static void main(String[] args) {
//组建10个任务
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new TestRunnable(i));
}
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,
5, TimeUnit.SECONDS, new LinkedBlockingDeque(2));
for (Runnable runnable:list) {
pool.execute(runnable);
}
}
}

线程池的工作原理:

刚创建线程池时,没有线程对象,当执行任务时,开始创建线程对象,当线程数等于核心线程数且都忙,来新任务时将其存入队列中;当核心线程数的线程都忙且队列也满了,开始创建新的线程执行新来的任务,线程数不能超过最大线程数。如果忙线程数达到最大且队列也满了,再来任务,采取拒绝策略。当空闲线程数大于核心线程数且超出了设定的存活时间,就线程进行销毁。最终的空闲线程数量等于核心线程数量。

Java中常用的线程池#

这些常用的线程池大部分封装了ThreadPoolExecutor ,在某些场景下我们使用指定的线程池会更方便。

  1. FixedThreadPool线程池:指定固定数量的线程数,不会销毁线程池中的线程,使用场景是:线程数量固定时效率最高的
//组建10个任务
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new TestRunnable(i));
}
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (Runnable runnable:list){
executorService.execute(runnable);
}
  1. CachedThreadPool线程数:最大线程数就是int的最大值,空闲线程存活1分钟,使用场景:适合任务量多执 行时间短的场景
//组建10个任务
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new TestRunnable(i));
}
ExecutorService executorService = Executors.newCachedThreadPool();
for (Runnable runnable:list){
executorService.execute(runnable);
}
executorService.shutdown();
  1. ScheduledThreadPool线程池:设置核心线程数,使用场景:执行大量定时任务线程
public static void main(String[] args) {
//组建10个任务
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(new TestRunnable(i));
}
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
for (int i = 0; i < list.size(); i++) {
executorService.schedule(list.get(i),i,TimeUnit.SECONDS);
}
}
  1. SingleThreadPool线程池 : 只有一个线程执行任务,使用场景:多个任务按顺序执行或多个任务同步需求
public static void main(String[] args) {
//组建10个任务
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(new TestRunnable(i));
}
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < list.size(); i++) {
executorService.execute(list.get(i));
}
}
  1. WorkStealingThreadPool线程池:先执行完的线程会分担其他线程的任务执行,使用场景:任务数量较固定 且任务执行时间较长。WorkStealingThreadPool中的线程是守护线程
public static void main(String[] args) {
//组建10个任务
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new TestRunnable(i));
}
ExecutorService executorService = Executors.newWorkStealingPool(5);
for (int i = 0; i < list.size(); i++) {
executorService.submit(list.get(i));
}
ExecutorService executor = Executors.newWorkStealingPool(2);
//由于上面的是守护线程,让主线程休眠100000毫秒
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
多线程
https://mizuki.mysqil.com/posts/13多线程/
作者
SuifengFlying
发布于
2026-07-28
许可协议
CC BY-NC-SA 4.0

Java SE

11 / 13

Related articles

部分信息可能已经过时

封面
示例歌曲
示例艺术家
封面
示例歌曲
示例艺术家
0:00 / 0:00