学习多线程
学习多线程
1.认识程序,进程,线程
程序
程序由指令和数据组成,是静态的
进程
启动一个程序,就启动了一个进程。
进程就是程序的一次执行过程。
进程是动态的。
线程
一个进程包含多个线程。例如:播放视频时,有动画,有声音,有弹幕……
2.如何创建线程
继承Thread类
三板斧:
- 继承Thread类
- 重写run方法
- 启动线程
实现Runnable接口
四板斧:
- 实现Runnable接口
- 重写run方法
- 将线程对象放入Thread对象
- 启动Thread对象的线程
实现Callable接口
3.静态代理模式
一个关于结婚的例子
You you = new You(); WeddingFirm wf = new WeddingFirm(you); wf.marry(); //You类,WeddingFirm类都实现了Marry接口,也就都实现了marry方法
代入到线程
MyThread mt = new MyThread(); Thread t = new Thread(mt); t.start(); //MyThread 类,Thread 类都实现了 Runnable 接口,也就都实现了 run 方法
4.lambda表达式
函数式接口
函数式接口
只包含一个抽象方法的接口。一个典型的函数式接口:Runnable接口
逐步简化
- 外部类
- 静态内部类
- 局部内部类
- 匿名内部类
- lambda表达式
- 去参数类型
- 去小括号
- 去大括号
lambda语法
(参数列表) -> {实现抽象方法的方法体}
参数列表
- 有一个参数,不需要小括号
- 没有参数,或有多个参数必须加小括号
- 参数类型要省略就全省略。为什么可以省略?Java会去检查运行类型(即函数式接口)的方法。
方法体
- 只有一条语句:无论有没有返回值都不需要大括号。如果写了return,就一定要加大括号。
- 有多条语句:一定要加大括号
举例:
Thread thread = new Thread(() -> System.out.println("Hello World!")).start();
完整例子
例一
package thread.lambda; /** * 学习并体验一步步将对象简化为lambda表达式的感觉 */ public class Lambda01 { public static void main(String[] args) { //3.局部内部类 class MyClass3 implements MyLambda { @Override public void studyLambda() { System.out.println("学习Lambda3,局部内部类"); } } //4.匿名内部类 MyLambda myLambda4 = new MyLambda() { @Override public void studyLambda() { System.out.println("学习Lambda4,匿名内部类"); } }; MyLambda myLambda1 = new MyClass(); myLambda1.studyLambda(); MyLambda myLambda2 = new MyClass1(); myLambda2.studyLambda(); MyLambda myLambda3 = new MyClass3(); myLambda3.studyLambda(); myLambda4.studyLambda(); //5.Lambda表达式 MyLambda myLambda5 = () -> { System.out.println("学习Lambda5,Lambda表达式"); }; myLambda5.studyLambda(); } //2.静态内部类 static class MyClass1 implements MyLambda { @Override public void studyLambda() { System.out.println("学习Lambda2,静态内部类"); } } } interface MyLambda { void studyLambda(); } //1.外部类 class MyClass implements MyLambda { @Override public void studyLambda() { System.out.println("学习Lambda1,外部类"); } }
例二
package thread.lambda; /** * 学习并感受带参数的lambda表达式,并逐步简化lambda表达式 */ public class Lambda02 { public static void main(String[] args) { //3.局部内部类 class ClassB3 implements LambdaB { @Override public void studyLambda(int a) { System.out.println("学习Lambda3,局部内部类,a = " + a); } } LambdaB b = new ClassB(); b.studyLambda(520); b = new ClassB2(); b.studyLambda(521); b = new ClassB3(); b.studyLambda(522); //4.匿名内部类 b = new LambdaB() { @Override public void studyLambda(int a) { System.out.println("学习Lambda4,匿名内部类,a = " + a); } }; b.studyLambda(250); //5.lambda b = (int a) -> { System.out.println("学习Lambda5,带参数类型的Lambda表达式,a = " + a); }; b.studyLambda(251); //6.简化参数列表,去掉参数类型。要去参数类型,就全部去掉。例如:(a,int b)是错的 b = (a) -> { System.out.println("学习Lambda6,去掉参数类型,a = " + a); }; b.studyLambda(252); //7.简化参数列表,去掉小括号。只有一个参数,才能去掉小括号 b = a -> { System.out.println("学习Lambda7,去掉小括号,a = " + a); }; b.studyLambda(253); //8.简化参数列表,去掉大括号 b = a -> System.out.println("学习Lambda8,去掉大括号,a = " + a); b.studyLambda(254); } //2.静态内部类 static class ClassB2 implements LambdaB { @Override public void studyLambda(int a) { System.out.println("学习Lambda2,静态内部类,a = " + a); } } } interface LambdaB { void studyLambda(int a); } //1.外部类 class ClassB implements LambdaB { @Override public void studyLambda(int a) { System.out.println("学习Lambda1,外部类,a = " + a); } }
new Thread(Runnable)
构造方法中可以放入一个对象,这个对象必须是实现了Runnable接口的对象
扩展:
new 代理类(实现了 任意函数式接口的 匿名内部类 对象)
5.线程状态
线程状态
线程方法
线程停止
推荐使用 设置标志位 的方式,如flag =true/false
线程休眠
线程礼让
线程插队
监测线程状态
线程优先级
守护线程
6.线程同步
同步机制
先了解一个名词:并发
并发
多个线程想要获得(或操作)某个对象(或者某个资源),这种情况就叫做并发。但是当他们同时操作这个对象时,会有异常发生,例如:抢票问题(某一张票),取钱问题(某一张银行卡)。
这时,就要想些办法,来避免这些异常发生,于是,线程同步机制就出现了。说白了,线程同步是一种办法(或机制),这种办法,就是为了解决并发的问题。
于是,解决并发的问题,就变成了如何实现线程同步的问题。
同时和同步的区别:
同时和同步在含义和用法上有明显的区别。
同时强调的是时间因素,指的是在同一时刻,两个或多个事件同时发生。例如,当我们说“同时出发”时,指的是在某一特定的时间点,所有人或物都开始行动。
同步则不同,它更多地强调的是事件之间的协调性和一致性,而非单纯的时间因素。例如,在舞蹈表演中,每个舞者需要按照同样的节拍和步伐来表演,这就是同步。也就是说,所有舞者需要在时间和动作上保持一致,才能达到同步的效果。
以上是同时和同步的主要区别,希望对你有所帮助。
上面的解释来自于百度文心一言。
队列和锁
光有队列,不一定安全。正在打饭的人得到一个锁,才能打饭,打完了饭,就释放掉锁,后面的人继续获取锁,继续打饭……
如果没有锁,前面的人还没打完饭,后面的人就迫不及待地也要打饭,于是,可能就打起来了……所以,队列加锁很有必要。
队列和锁解决了线程同步的安全性
同步锁
synchronized 同步锁
同步方法
同步方法弊端
同步块
同步代码块的优势就在于:想锁哪里就锁哪里,想锁谁就锁谁。不需要像方法那样,把一整个方法体都锁住。
死锁
释放死锁:
死锁概念:
避免死锁:
Lock锁
Lock锁的概念
自定义lock锁
synchronized与lock对比
7.线程通信
生产者与消费者模式
线程通信问题分析
解决线程通信的方法
模型1:
管程法代码实现
package thread.communication; /** * 测试生产者与消费者模式:利用缓冲区解决(管程法) * 需要生产者,消费者,产品,缓冲区 */ public class TestPC { public static void main(String[] args) { SynContainer synContainer = new SynContainer(); new Producer(synContainer).start(); new Consumer(synContainer).start(); } } //消费者 class Consumer extends Thread { SynContainer synContainer; public Consumer(SynContainer synContainer) { this.synContainer = synContainer; } @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println("消费了第" + synContainer.pop().id + "只鸡"); } } } //生产者,相当于后厨 class Producer extends Thread { SynContainer synContainer; public Producer(SynContainer synContainer) { this.synContainer = synContainer; } @Override public void run() { for (int i = 1; i <= 100; i++) { synContainer.push(new Chicken(i)); System.out.println("生产了第" + i + "只鸡"); } } } //容器,相当于前台 class SynContainer { //定义数组,存放产品 Chicken[] chickens = new Chicken[10]; //计数器 int cnt = 0; //生产者放入产品 public synchronized void push(Chicken chicken) { //如果满了,就等待消费者消费 if (10 == cnt) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没满,就放入产品 chickens[cnt++] = chicken; //通知消费者可以消费 this.notifyAll(); } //消费者取出产品 public synchronized Chicken pop() { //如果容器为空,就等待生产者生产 if (0 == cnt) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //通知生产者可以生产 this.notifyAll(); //如果没空,就取出产品 return chickens[--cnt]; } } class Chicken { public int id; public Chicken(int id) { this.id = id; } }
模型2:
信号灯法代码实现
package thread.communication; /** * 生产者消费者问题2:信号灯法,标志位解决 */ public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new Actor(tv).start(); new Watcher(tv).start(); } } //生产者:演员 class Actor extends Thread { TV tv = new TV(); public Actor(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i % 2 == 0) this.tv.play("快乐大本营"); else this.tv.play("抖音"); } } } //消费者:观众 class Watcher extends Thread { TV tv; public Watcher(TV tv) { this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品:节目 class TV { //演员表演,观众等待 //观众观看,演员等待 String voice;//节目 boolean flag = true;//false:观众正在观看,演员不需要表演;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; } }
注意点:线程间通信的前提是 线程同步,所以依然需要设置锁。
8.线程池
线程池概念
使用线程池
线程池代码实现
package thread.pool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 测试线程池 */ public class TestPool { public static void main(String[] args) { //1.创建服务,创建线程池 //参数为线程大小 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭链接 service.shutdown(); } } class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~