在深入探讨模块18之前,让我们回顾一下【day17】中的关键内容:
Thread类: Thread。run方法,设置线程任务。start方法,开启线程,JVM自动执行run方法。Runnable接口: Runnable。run方法,设置线程任务。Thread对象中。start方法,开启线程,JVM自动调用run方法。new Thread(new Runnable(){...}).start();Thread中的方法:
start():开启线程,JVM自动调用run方法。getName():获取线程名字。setName(String name):设置线程名字。currentThread():获取当前正在执行的线程对象。sleep(long time):线程睡眠。setPriority(int n):设置线程优先级。getPriority():获取线程优先级。setDaemon(true):设置为守护线程。yield():礼让线程。join():插队线程。synchronized(锁对象){...}synchronized关键字。 this。class对象。本模块将深入探讨多线程的高级应用,包括:
wait和notify两个方法。Lock锁对象。Callable接口实现多线程。在生产者-消费者模型中,我们需要协调线程之间的通信,确保生产和消费的同步进行。这就需要用到等待唤醒机制。
wait():线程等待,等待的过程中线程会释放锁,需要被其他线程调用notify方法将其唤醒,重新抢锁执行。notify():线程唤醒,一次唤醒一个等待线程;如果有多条线程等待,则随机唤醒一条等待线程。notifyAll():唤醒所有等待线程。wait和notify方法需要锁对象调用,所以需要用到同步代码块中,而且必须是同一个锁对象。

以下是生产者-消费者模型的实现代码:
/*
count和flag可以定义成包装类
但是要记得给count和flag手动赋值
不然对于本案例来说,容易出现空指针异常
*/
public class BaoZiPu {
//代表包子的count
private int count;
//代表是否有包子的flag
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/*
getCount 改造成消费包子方法
直接输出count
*/
public void getCount() {
System.out.println("消费了..............第"+count+"个包子");
}
/*
setCount 改造成生产包子
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){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (baoZiPu){
//1.判断flag是否为true,如果是true,证明有包子,生产线程等待
if (baoZiPu.isFlag()==true){
try {
baoZiPu.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//2.如果flag为false,证明没有包子,开始生产
baoZiPu.setCount();
//3.改变flag状态,为true,证明生产完了,有包子了
baoZiPu.setFlag(true);
//4.唤醒消费线程
baoZiPu.notify();
}
}
}
}public class Consumer implements Runnable{
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (baoZiPu){
//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待
if (baoZiPu.isFlag()==false){
try {
baoZiPu.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//2.如果flag为true,证明有包子,开始消费
baoZiPu.getCount();
//3.改变flag状态,为false,证明消费完了,没 有包子了
baoZiPu.setFlag(false);
//4.唤醒生产线程
baoZiPu.notify();
}
}
}
}public class Test01 {
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();
}
}
在多生产者和多消费者的情况下,我们需要使用while循环来替代if判断,并将notify改为notifyAll,以确保所有等待的线程都能被唤醒。
public class Test01 {
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();
}
}/*
count和flag可以定义成包装类
但是要记得给count和flag手动赋值
不然对于本案例来说,容易出现空指针异常
*/
public class BaoZiPu {
//代表包子的count
private int count;
//代表是否有包子的flag
private boolean flag;
public BaoZiPu() {
}
public BaoZiPu(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
/*
getCount 改造成消费包子方法
直接输出count
*/
public synchronized void getCount() {
//1.判断flag是否为false,如果是false,证明没有包子,消费线程等待
while (this.flag == false) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//2.如果flag为true,证明有包子,开始消费
System.out.println("消费了..............第" + count + "个包子");
//3.改变flag状态,为false,证明消费完了,没 有包子了
this.flag = false;
//4.唤醒所有等待线程
this.notifyAll();
}
/*
setCount 改造成生产包子
count++
*/
public synchronized void setCount() {
//1.判断flag是否为true,如果是true,证明有包子,生产线程等待
while (this.flag == true) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//2.如果flag为false,证明没有包子,开始生产
count++;
System.out.println("生产了...第" + count + "个包子");
//3.改变flag状态,为true,证明生产完了,有包子了
this.flag = true;
//4.唤醒所有等待线程
this.notifyAll();
}
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){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.setCount();
}
}
}public class Consumer implements Runnable{
private BaoZiPu baoZiPu;
public Consumer(BaoZiPu baoZiPu) {
this.baoZiPu = baoZiPu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
baoZiPu.getCount();
}
}
}Lock是一个接口,其实现类为ReentrantLock,提供了比synchronized更灵活的锁控制。
lock():获取锁。unlock():释放锁。public class MyTicket implements Runnable {
//定义100张票
int ticket = 100;
//创建Lock对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100L);
//获取锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
ticket--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//释放锁
lock.unlock();
}
}
}
}public class Test01 {
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();
}
}synchronized: 不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放锁对象
Lock: 是通过两个方法控制需要被同步的代码,更灵活
Callable接口是实现多线程的另一种方式,它允许任务返回值。
call():设置线程任务的方法,类似于run方法,但可以返回值和抛出异常。public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "涛哥和金莲...的故事";
}
}public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
/*
FutureTask(Callable<V> callable)
*/
FutureTask<String> futureTask = new FutureTask<>(myCallable);
//创建Thread对象-> Thread(Runnable target)
Thread t1 = new Thread(futureTask);
t1.start();
//调用get方法获取call方法返回值
System.out.println(futureTask.get());
}
}
问题:之前来一个线程任务,就需要创建一个线程对象去执行,用完还要销毁线程对象,如果线程任务多了,就需要频繁创建线程对象和销毁线程对象,这样会耗费内存资源,所以我们就想线程对象能不能循环利用,用的时候直接拿线程对象,用完还回去
线程池是管理线程对象的一种机制,可以循环利用线程对象,减少创建和销毁线程对象的开销。
Executors.newFixedThreadPool(int nThreads):创建固定大小的线程池。submit(Runnable task)和submit(Callable<T> task):提交任务到线程池执行。Future.get():获取任务的返回值。public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"...执行了");
}
}public class Test01 {
public static void main(String[] args) {
//创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());
//es.shutdown();//关闭线程池对象
}
}public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1;
}
}public class Test02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> future = es.submit(new MyCallable());
System.out.println(future.get());
}
}需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串
public class MyString implements Callable<String> {
@Override
public String call() throws Exception {
return "那一夜,你没有拒绝我,那一夜,你伤害了我";
}
}public class MySum implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;
}
}public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
Future<String> f1 = es.submit(new MyString());
Future<Integer> f2 = es.submit(new MySum());
System.out.println(f1.get());
System.out.println(f2.get());
}
}Timer类用于定时执行任务。
schedule(TimerTask task, Date firstTime, long period):安排任务在指定时间开始执行,每隔一定时间重复执行。public class Demo01Timer {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("金莲对涛哥说:涛哥,快起床了~~~");
}
}, new Date(), 2000L);
}
}通过今天的学习,希望可以帮助你掌握多线程的高级应用,包括等待唤醒机制、Lock锁、Callable接口、线程池以及定时器的使用,这些都是Java并发编程中非常重要的知识点。