Java多线程(下)---附代码示例与理解
本文系统介绍了Java线程管理的核心知识点,主要包括:1.线程生命周期(6种状态及转换规则),重点区分sleep()和wait()的差异;2.等待唤醒机制(wait/notify),通过生产者-消费者案例演示同步控制;3.Lock锁实现(ReentrantLock)及其基本用法;4.Callable接口与FutureTask获取返回值;5.线程池的创建使用(Executors)及优势;6.定时器(
1线程生命周期
此篇就主要围绕线程之间状态及其转换进行展开
主要状态
- NEW(新建):
- 线程被创建但尚未启动。
- Runnable(可运行):
- 线程已启动并处于可运行状态,等待CPU调度执行。
- Blocked(锁阻塞):
- 线程试图获取锁但未成功,进入阻塞状态。
- 转换条件:没有抢到锁。
- Waiting(无限等待):
- 线程调用
wait()
方法进入无限等待状态,释放锁,等待其他线程调用notify()
或notifyAll()
唤醒。 - 转换条件:调用
wait()
。
- 线程调用
- Timed Waiting(计时等待):
- 线程调用
sleep(time)
或wait(time)
进入计时等待状态,等待指定时间后自动唤醒或被其他线程唤醒。 - 转换条件:
sleep(time)
、wait(time)
。
- 线程调用
- Terminated(被终止):
- 线程执行完毕、异常未捕获或调用
stop()
方法终止。
- 线程执行完毕、异常未捕获或调用
六种状态转换规则如图:
注意事项
1. sleep(time)
和 wait(time)
的区别
sleep(time)
:- 线程睡眠,睡眠过程中线程不会释放锁。
- 其他线程无法抢到锁。
- 设置的时间超时后,线程自动醒来并继续执行。
wait(time)
:- 线程等待,等待过程中会释放锁。
- 其他线程有机会抢到锁。
- 如果在等待过程中被唤醒或时间超时,会与其他线程重新抢锁,抢到则继续执行,抢不到则锁阻塞。
2. wait()
和 notify()
的关系
wait()
:- 线程进入无限等待状态,释放锁。
- 需要其他线程调用
notify()
(唤醒一条等待的线程,唤醒是随机的)或notifyAll()
(唤醒所有等待的线程)方法来唤醒。 - 被唤醒后,会与其他线程重新抢锁,抢到则继续执行,抢不到则锁阻塞。
notify()
:- 唤醒一条正在等待的线程。
- 一次只能唤醒一条等待的线程,如果有多个线程在等待,
notify()
会随机唤醒一条。
notifyAll()
:- 唤醒所有正在等待的线程。
3. wait
和 notify
的用法
- 锁对象调用:
- 两个方法都需要锁对象调用,因此需要在同步代码块或同步方法中使用。
- 调用必须是同一个锁对象,即使用同一个锁对象将多条线程分组,
notify
只唤醒同一组的等待线程。
2等待唤醒机制
主要方法
- void wait()
- 功能:使当前线程进入等待状态,释放锁,直到被其他线程通过
notify()
或notifyAll()
唤醒。 - 使用场景:当线程需要等待特定条件满足时调用。
- 功能:使当前线程进入等待状态,释放锁,直到被其他线程通过
- void notify()
- 功能:唤醒一个等待的线程(若多个线程等待,则随机选择一个)。
- 使用场景:当某个条件满足,需要唤醒一个等待线程时调用。
- void notifyAll()
- 功能:唤醒所有等待的线程。
- 使用场景:当某个条件满足,需要唤醒所有等待线程时调用。
注意事项
wait()
和notify()
方法必须在同步代码块中调用,并且使用同一个锁对象,以确保线程安全。- 生产者-消费者问题:通过
wait()
、notify()
和notifyAll()
方法,可以有效管理线程的生产与消费过程,避免资源竞争和死锁。 - 同步机制:这些方法依赖于 Java 的同步机制(如
synchronized
关键字),确保线程在访问共享资源时的互斥性和有序性。
代码案例分析:
要求
一个线程生产包子,一个线程消费包子,但是不能连续生产不能连续消费
问题1: 怎么表示生产包子,怎么表示消费包子?
a. 如果是生产包子:count++
b. 如果是消费包子:直接输出count
问题2: 怎么证明有没有包子?
a. 如果flag = true;证明有包子,就要消费包子
b. 如果flag = false;证明没有包子,就要生产包子
问题3: 如何防止生产到一半,CPU切换到消费线程去了?
加锁
问题4: 即使加锁,也不能保证生产一个就消费一个如何防止连续生产,连续消费?
wait和notify方法
包子铺
生产包子
- 如果flag = true,证明有包子,生产线程wait
- 否则证明没有包子,生产包子
- 将flag变成true,证明生产完了,有包子了
- 唤醒消费线程:notify
消费包子
- 如果flag = false;证明没有包子,消费线程wait
- 否则,证明有包子,消费包子
- 将flag变成false;证明消费完了,没有包子了
- 唤醒生产线程:notify
包子铺类
public class BaoZiPu {
private int count = 0;
private boolean flag = false;
public BaoZiPu() {}
public BaoZiPu(int count, boolean flag) {
// 初始化包子数量和标志位
this.count = count;
this.flag = flag;
}
public void getCount() {
System.out.println("消费了............第" + count + "个包子");
}
public void setCount() {
count++;
System.out.println("生产了....第" + count + "个包子");
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
生产者类
public class Product implements Runnable {
private BaoZiPu baoZiPu;
public Product(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
synchronized (baoZiPu) {
// 判断是否有包子(flag为true表示有包子)
if (baoZiPu.isFlag()) {
try {
// 有包子,生产者线程等待
baoZiPu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包子,开始生产
baoZiPu.setCount();
// 生产后,设置flag为true,表示有包子了
baoZiPu.setFlag(true);
// 唤醒消费者线程
baoZiPu.notify();
}
try {
// 生产者线程休眠100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者类
public class Consumer implements Runnable {
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while (true) {
try {
// 消费者线程休眠100毫秒
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (baoZiPu) {
// 判断是否有包子(flag为false表示没有包子)
if (baoZiPu.isFlag() == false) {
try {
// 没有包子,消费者线程等待
baoZiPu.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 有包子,开始消费
baoZiPu.getCount();
// 消费后,设置flag为false,表示没有包子了
baoZiPu.setFlag(false);
// 唤醒生产者线程
baoZiPu.notify();
}
}
}
}
测试类
public class Main {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
Thread t1 = new Thread(product);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
测试结果
wait把锁释放出去后,两个线程仍然都具有抢锁能力只不过会根据逻辑如果错误的线程抢到锁了,就会用wait把锁让出去,让给另一个,另一个程序执行的时候会在把这一个在wait中的线程进行唤醒,唤醒了两个线程又重新具有抢锁能力进行抢锁。
多等待多唤醒案例
我们如果把生成和消费都加多
public class Main {
public static void main(String[] args) {
BaoZiPu baoZiPu = new BaoZiPu();
Product product = new Product(baoZiPu);
Consumer consumer = new Consumer(baoZiPu);
new Thread(product).start();
new Thread(product).start();
new Thread(product).start();
new Thread(consumer).start();
new Thread(consumer).start();
new Thread(consumer).start();
}
}
测试结果
根本原因是进入wait的进程再次被唤醒时就会直接从被wait后进行执行不会再进行整体的逻辑判断
例如如图
六个线程五个在等待,我们进入生产线程生产完成后调用唤醒,如果唤醒到了消费那么还是交替执行,如果唤醒到生产的两个就会造成连续生产的问题,无法严格交替进行
我们把if改成循环循环判断改变这个局面
至于先唤醒所有案例是为了什么,假设我们唤醒一个那么只有这个能抢到锁,我们全部唤醒,增大消费抢到锁的概率。增进效率,如果我们只加循环,那么假设唤醒生成那么直接将生成继续等待,那么又唤醒一次,这样也可以,但是全唤醒了可能 直接就给生产抢到锁了,加快效率。
测试结果
3Lock锁
Lock对象的介绍和基本使用总结
- 概述:Lock 是一个接口,用于实现更灵活的锁定操作。
- 实现类:ReentrantLock 是 Lock 接口的一个常见实现类,支持可重入锁。
- 方法:
lock()
:获取锁。unlock()
:释放锁。
核心信息
- Lock 接口:提供比 synchronized 更加灵活的锁定机制。
- ReentrantLock 类:Lock 接口的主要实现,支持可重入特性。
- 基本方法:
lock()
用于获取锁,若锁已被其他线程持有,则当前线程会阻塞等待。unlock()
用于释放锁,确保其他等待的线程可以获取锁
代码示例
延展类
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyTicket implements Runnable {
// 定义100张票
private int ticket = 100;
// 创建Lock对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 获取锁
lock.lock();
try {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
} else {
break; // 票已售完,退出循环
}
} finally {
// 释放锁
lock.unlock();
}
}
}
}
测试类
class Main {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");
t1.start();
t2.start();
t3.start();
}
}
测试结果
4通过Callable实现线程
Callable 接口总结
- 概述:
Callable<V>
是一个接口,类似于Runnable
,用于设置线程任务。
- 方法:
V call()
:设置线程任务的方法,类似于Runnable
接口中的run
方法。
call
方法和run
方法的区别:- 相同点:都是设置线程任务的。
- 不同点:
call
方法有返回值,并且可以声明抛出异常(throws
)。run
方法没有返回值,并且不能声明抛出异常。
- 泛型
<V>
:<V>
称为泛型,用于指定操作的数据类型。- 泛型只能写引用数据类型,若不指定泛型,默认为
Object
类型。 - 实现
Callable
接口时,指定泛型类型,call
方法的返回值类型即为该泛型类型。
- 获取
call
方法返回值:- 通过
FutureTask<V>
实现:FutureTask<V>
实现了Future<V>
接口。FutureTask<V>
中的V get()
方法用于获取call
方法的返回值。
- 通过
代码示例
线程类
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "我和你的故事------";
}
}
测试类
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread t1 = new Thread(futureTask);
t1.start();
System.out.println(futureTask.get());
}
}
测试结果
5线程池
1. 线程池的基本概念
- 线程池:用于管理和复用线程,避免频繁创建和销毁线程导致的资源浪费。
- 工作原理:线程池中维护一定数量的线程,任务提交到线程池后,线程池会分配线程执行任务,任务执行完毕后,线程返回线程池等待下一个任务。
2. 线程池的创建与使用
- 创建线程池:使用
Executors
工具类。- 方法:
newFixedThreadPool(int nThreads)
。 - 参数:
nThreads
表示线程池中线程的最大数量。 - 返回值:
ExecutorService
接口的实现类,用于管理线程池。
- 方法:
- 执行线程任务:通过
ExecutorService
的submit
方法。submit(Runnable task)
:提交一个Runnable
任务。submit(Callable<T> task)
:提交一个Callable
任务,可返回结果。
submit
方法的返回值:Future
接口。- 作用:接收
run
或call
方法的返回值(run
无返回值,call
有返回值)。 Future
的get()
方法:获取call
方法的返回值。
- 作用:接收
- 关闭线程池:调用
ExecutorService
的shutdown()
方法。- 作用:启动有序关闭,已提交的任务会执行完毕,但不再接受新任务。
3. 线程池的优势
- 资源复用:线程可以被多次使用,减少创建和销毁线程的开销。
- 控制线程数量:避免线程过多导致的资源竞争和性能下降。
- 提高响应速度:任务提交后可立即执行,无需等待线程创建。
代码示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
// 创建线程池对象,固定大小为2个线程
ExecutorService es = Executors.newFixedThreadPool(2);
// 提交3个任务到线程池
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());
// 关闭线程池对象
es.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
// 简单的任务执行逻辑
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行任务");
}
}
测试结果
6定时器
定时器(Timer)概述
定时器(Timer)是一种用于在指定时间执行任务的工具,常用于需要定时操作的场景。
构造
- Timer():创建一个新的定时器。
方法
- void schedule(TimerTask task, Date firstTime, long period):
- task:抽象类,是Runnable的实现类,定义了要执行的任务。
- firstTime:指定任务开始执行的初始时间。
- period:任务执行的周期,单位为毫秒,表示每隔多长时间执行一次任务
代码示例
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Main {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("战斗把");
}
}, new Date(), 2000L);
}
}
测试结果
更多推荐
所有评论(0)