多线程详解
简介 | |
---|---|
任务 | 本质上在同一时间做了一件事情。吃饭玩手机,开车打电话。 |
进程(Process) | 在操作系统中运行的程序。是执行程序的一次执行过程,动态概念。 |
程序(Thread) | 指令和数据的有序集合,本身没有任何运行的含义,静态概念。 |
线程 | 一个进程中可以包含若干个线程,一个进程中至少有一个线程。 |
多线程 | 从软件或者硬件上实现多个线程并发执行的技术。王者荣耀开黑。 |
普通方法调用和多线程调用,如图:
2. 线程实现(重点)
1. 继承Thread class
// 步骤: // 1. 自定义线程类继承Thread类 // 2. 重写run()方法,编写线程执行体 // 3. 创建线程对象,调用start()方法启动
//注意:线程开启不一定不一定立即执行,由CPU调度执行
//创建线程方式一: 继承Thread类, 重写run()方法,调用Start()开启线程。(start()方法线程同时执行)
public class TestThread extends Thread {
@Override
public void run() { //run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----run----" + i);
}
}
public static void main(String[] args) { //main线程, 主线程
TestThread t1 = new TestThread();
t1.start(); //创建线程对象,调用start()方法开启线程
// new t1().start(); //创建线程对象,调用start()方法开启线程
// new t1().run(); //先执行run方法。
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程----main----" + i);
}
}
}
2. 实现 Runable 接口
// 步骤 // 1. 定义MyRunnable类实现Runnable接口 // 2. 实现run()方法,编写线程执行体 // 3. 创建线程对象,调用start()方法启动线
/创建线程方式2: 实现runnable接口, 重写run方法, 执行线程需要将runnable实现类对象丢入Thread中, 调用Start()方法。
//runnable里面没有start()方法, 所以需要开启线程的时候, 需要借用Thread类中的Start()方法。
public class TestRunnable implements Runnable {
@Override
public void run() { //run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----run----" + i);
}
}
public static void main(String[] args) { //main线程, 主线程
TestRunnable T3 = new TestRunnable();//创建runnable接口的实现类对象
// Thread thread = new Thread(T3); //创建线程对象,通过线程对象代理开启我们的线程。
// thread.start(); //创建线程对象,通过线程对象代理开启我们的线程。
new Thread(T3).start(); //创建线程对象,通过线程对象代理开启我们的线程。
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程----main----" + i);
}
}
}
扩展:网页下载器
class WebDownloader { //文件下载工具类
public void downloader(String url, String name) { //远程路径 下载名字
try {
//FileUtils: 文件工具,复制url到文件
//Commons IO: 针对开发IO流功能的工具库
FileUtils.copyURLToFile(new URL(url), new File(name)); //将网络上的一个网页地址变为一个文件
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法下载异常。");
}
}
}
-
继承Thread类,重写run方法
class TestThread extends Thread { //练习Thread, 实现多线程同步下载图片。
private String url; //网络图片地址
private String name; //保存的文件名
public TestThread(String url, String name){ //构造器
this.url=url;
this.name=name;
}
@Override
public void run() { //下载图片线程的执行体。
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名为:"+name);
}
}
-
创建类对象,实现多线程
public static void main(String[] args) { //main方法
TestThread2 T1 = new TestThread2("https://img3.doubanio.com/view/note/l/public/p83642840.webp","1.jpg");
TestThread2 T2 = new TestThread2("https://img3.doubanio.com/view/note/l/public/p83642840.webp","2.jpg");
TestThread2 T3 = new TestThread2("https://img3.doubanio.com/view/note/l/public/p83642840.webp","3.jpg");
T1.start();
T2.start();
T3.start();
}
完整代码
通过实现Thread类实现
class TestThread extends Thread { //练习Thread, 实现多线程同步下载图片。
private String url; //网络图片地址
private String name; //保存的文件名
public TestThread(String url, String name){ //构造器
this.url=url;
this.name=name;
}
@Override
public void run() { //下载图片线程的执行体。
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名为:"+name);
}
public static void main(String[] args) { //main方法
TestThread T1 = new TestThread("https://img3.doubanio.com/view/note/l/public/p83642840.webp","1.jpg");
TestThread T2 = new TestThread("https://img3.doubanio.com/view/note/l/public/p83642840.webp","2.jpg");
TestThread T3 = new TestThread("https://img3.doubanio.com/view/note/l/public/p83642840.webp","3.jpg");
T1.start();
T2.start();
T3.start();
}
}
通过实现Runnalbe接口实现
class Testrunnable implements Runnable { //练习Thread, 实现多线程同步下载图片。
private String url; //网络图片地址
private String name; //保存的文件名
public Testrunnable(String url, String name) { //构造器
this.url = url;
this.name = name;
}
@Override
public void run() { //下载图片线程的执行体。
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名为:"+name);
}
public static void main(String[] args) { //main方法
Testrunnable T4 = new Testrunnable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "4.jpg");
Testrunnable T5 = new Testrunnable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "5.jpg");
Testrunnable T6 = new Testrunnable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "6.jpg");
new Thread(T4).start();
new Thread(T5).start();
new Thread(T6).start();
}
}
扩展:龟兔赛跑
public class Race implements Runnable {
private static String winner; //胜利者
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟兔子休息200ms
if ((Thread.currentThread().getName().equals("兔子")) && (i % 10 == 0)) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
if (flag) {
// System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"米。");
break;
}
System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "米");
}
}
扩展:购买火车票
//多个线程同时操作一个对象
//发现问题: 多个线程操作同一个资源的情况下, 线程不安全, 数据紊乱。
//出现多个人抢到同一张票。
//Thread.currentThread().getName() 查看是哪个线程正在操作(当前运行线程的名字)
public class TestRunnable implements Runnable{
private int ticketNums = 10; //票数
@Override
public void run() {
while (true) {
if (ticketNums < 1) {
break;
}
try { //模拟延迟。
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---->>拿到了第"+(ticketNums--)+"票。");
}
}
public static void main(String[] args) {
TestRunnable testThread4 = new TestRunnable();
//开启3个线程处理同一资源
new Thread(testThread4,"小明").start();
new Thread(testThread4,"小红").start();
new Thread(testThread4,"黄牛").start();
}
}
初识并发问题
Thread-Runnable的小结:
继承Thread类 | 实现Runnable接口 |
---|---|
子类继承Thread类具备多线程能力 | 实现接口Runnable具有多线程能力 |
启动线程:子类对象.Start() | 传入目标对象+Thread对象.Start() |
不建议使用:避免OOP单继承局限性 | 推荐使用:避免OOP单继承局限性,灵活方便,方便同一个对象被多个线程使用。 |
3. 实现Callable(了解)
// 步骤 // 1. 实现Callable接口,需要返回值类型 // 2. 重写call方法,需要抛出异常 // 3. 创建目标对象 // 4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1); // 5. 提交执行:Future<Boolean> result1 = ser.submit(t1); // 6. 获取结果:boolean r1 = result1.get() // 7. 关闭服务:ser.shutdownNow()
//线程创建方式三: 实现Callable接口 public class TestCallable implements Callable<Boolean> {// 1. 实现Callable接口,需要返回值类型 private String url; //网络图片地址 private String name; //保存的文件名 public TestCallable(String url, String name) { //构造器 this.url = url; this.name = name; } @Override// 2. 重写call方法,需要抛出异常 public Boolean call() { //下载图片线程的执行体。 try { WebDownloader WebDownloader = new WebDownloader(); WebDownloader.downloader(url, name); System.out.println("下载的文件名为:" + name); } catch (Exception e) { e.printStackTrace(); } return true; } public static void main(String[] args) { // 3. 创建目标对象 TestCallable C1 = new TestCallable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "call01.jpg"); TestCallable C2 = new TestCallable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "call02.jpg"); TestCallable C3 = new TestCallable("https://img3.doubanio.com/view/note/l/public/p83642840.webp", "call03.jpg"); // 4. 创建执行服务,创建三个服务 ExecutorService ser = Executors.newFixedThreadPool(3); // 5. 提交执行, 将线程对象提交 Future<Boolean> result1 = ser.submit(C1); Future<Boolean> result2 = ser.submit(C2); Future<Boolean> result3 = ser.submit(C3); // 6. 获取结果 try { boolean r1 = result1.get(); boolean r2 = result2.get(); boolean r3 = result3.get(); System.out.println(r1); System.out.println(r2); System.out.println(r3); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
// 7. 关闭服务 ser.shutdownNow(); } }
3. 线程状态
线程方法
方法 | 说明 |
---|---|
SetPriority(int newPriority) | 更改线程的优先级 |
Static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待线程停止 |
Static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程(别用这种方式) |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
//测试stop
/**
* 1. 建议线程正常停止 --> 利用次数,不建议死循环
* 2. 建议使用标志位 --> 设置一个标志位
* 3. 不要使用 stop() 或者 destroy() 等过时或者 JDK 不建议使用的方法
*/
public class TestStop implements Runnable {
//1. 设置一个标志位
private boolean flag = true;
public void run() {
int i = 0;
while (flag) {
System.out.println("run.....Thread....." + i++);
}
}
//2. 设置一个公开的方法停止线程,转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
P11_TestStop stop = new P11_TestStop();
Thread thread = new Thread(stop);
thread.start();
for (int i = 0; i <= 100; i++) {
System.out.println("main..."+i);
if(i==88){
stop.stop(); //调用 stop() 方法, 改变标志位, 停止线程
System.out.println("该线程已停止!!!");
}
}
}
}
线程休眠
/**
* sleep (时间) 指定当前线程阻塞的毫秒数
* sleep存在异常InterruptedException
* sleep时间达到后线程进入就绪状态
* sleep可以模拟网络延时,倒计时等
* 每一个对象都有一个锁,sleep不会释放锁
*/
// 模拟网络延时: 放大问题的发生性
public class TestSleep_01 implements Runnable{
private int ticketNums = 10; //票数
public void run() {
while (true) {
if (ticketNums < 1) {
break;
}
try { //模拟延迟。
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---->>拿到了第"+(ticketNums--)+"票。");
}
}
public static void main(String[] args) {
P12_TestSleep_01 testThread4 = new P12_TestSleep_01();
//开启3个线程处理同一资源
new Thread(testThread4,"小明").start();
new Thread(testThread4,"小红").start();
new Thread(testThread4,"黄牛").start();
}
}
线程礼让
/**
* 礼让线程,停止执行的线程,但不阻塞。
* 运行状态 --> 就绪状态
* 由 CPU 重新调度, 礼让随机, 可能失败
*/
// 测试礼让线程。
// 礼让不一定成功,看 CPU 心情
public class TestYield {
public static void main(String[] args) {
MyYield p1 = new MyYield();
new Thread(p1, "a").start();
new Thread(p1, "b").start();
}
}
class MyYield implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + "......线程开始执行......");
Thread.yield(); // 线程礼让
System.out.println(Thread.currentThread().getName() + "......线程停止执行......");
}
}
强制执行 - join
//Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
public class TestJoin implements Runnable {
public void run() {
for (int i = 0; i <= 200; i++) {
System.out.println("线程 VIP 来了!!!" + i);
}
}
public static void main(String[] args) {
// 启动线程
P14_TestJoin Join = new P14_TestJoin();
Thread thread = new Thread(Join);
thread.start();
// 主线程
for (int i = 0; i <= 100; i++) {
if (i == 88) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main......" + i);
}
}
}
观测线程状态
/**
* JDK1.8 文档搜索Thread State
* new 尚未启动的线程处于该状态
* runnable 在 Java虚拟机 中执行的线程处于该状态
* blocked 被阻塞等待监听器锁定的线程处于该状态
* waiting 正在等待开一个线程执行特定动作的线程处于该状态
* timed_waiting 正在等待另一个线程执行动作达到指定等待时间的线程处于该状态
* terminated 已退出的线程处于该状态
*/
//观察线程的状态
//创建 就绪 运行 阻塞 死亡
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println("等待中........"+(i+1));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("////////");
});
//观察状态 thread.getState(), Alt+回车创建变量state
Thread.State state = thread.getState();
System.out.println(state); //NEW
//观察启动后
thread.start();
state = thread.getState();
System.out.println(state); //RUN
while (state != Thread.State.TERMINATED){ //只要线程不终止,就一直输出状态
try {
Thread.sleep(200);
state = thread.getState(); //更新线程状态
System.out.println(state); //输出状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程优先级
/**
* 优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了.这都是看CPU的调度
* 优先级的设定建议在start()调度前
*/
//测试线程优先级
public class TestPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println("主线程---" + Thread.currentThread().getName() + " ----> " + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
//先设置优先级,在启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY); //Thread.MAX_PRIORITY=10
t4.start();
t5.setPriority(8);
t5.start();
}
}
class MyPriority implements Runnable {
public void run() {
//Thread.currentThread().getName() 获取当前运行线程的名字
//Thread.currentThread().getPriority() 返回当前运行线程的优先级
System.out.println(Thread.currentThread().getName() + " ----> " + Thread.currentThread().getPriority());
}
}
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
注意:用户进程执行完毕之后守护进程结束。
//测试守护进程
//上帝守护你
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Me me = new Me();
Thread thread = new Thread(god);
//设置守护进程
thread.setDaemon(true); //默认是false表示是用户线程, 正常的线程都是用户线程......
thread.start(); //上帝守护进程启动......
new Thread(me).start(); //用户线程启动......
}
}
//上帝
class God implements Runnable{
public void run() {
while (true){
System.out.println("上帝保佑着你......");
}
}
}
//你
class Me implements Runnable {
public void run() {
for (int i = 0; i <= 36500; i++) {
System.out.println("你一生都开心的活着......");
}
System.out.println("---- goodbye! world! ----"); // hello world!
}
}
4. 线程同步(重点)
线程同步机制
并发:同一个对象被多个线程同时操作
线程同步:多个线程访问同一个对象,利用对象等待池(等待机制)形成队列,挨个使用。
形成条件:队列和锁
队列和锁:线程同步需要队列和锁
锁机制(synchronized):当一个线程获得对象的排它锁 , 独占资源 , 其他线程必须等待 , 使用后释放锁即可 . 存在以下问题 :
-
一个线程持有锁会导致其他所有需要此锁的线程挂起
-
在多线程竞争下 , 加锁 , 释放锁会导致比较多的上下文切换和调度延时,引起性能问题
-
如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒 置 , 引起性能问题
三大不安全问题
不安全取钱
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(1000, "理财基金");
Drawing you = new Drawing(account,200,"你");
Drawing girlFriend = new Drawing(account,900,"girlFriend");
you.start();
girlFriend.start();
}
}
class Account {
int money; // 余额
String name; // 卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread {
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney;//现在手里多少钱
public Drawing(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
public void run() {
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足......");
// System.out.println(this.getName()+"余额不足......");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney; //卡内余额 = 余额 - 你取的钱
nowMoney = drawingMoney + nowMoney; //你手里的钱
System.out.println(account.name+"余额为: "+account.money);
//等同: Thread.currentThread().getName() == this.getName()
System.out.println(this.getName()+"手里的钱为: "+nowMoney);
}
}
不安全买票
//不安全的买票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "张三").start();
new Thread(buyTicket, "李四").start();
new Thread(buyTicket, "黄牛").start();
}
}
class BuyTicket implements Runnable {
private int ticketNums = 10; //票
boolean flag = true; //外部停止方式
public void run() { //买票
while (flag) { //模拟延时
buy();
}
}
private void buy() {
if (ticketNums <= 0) {//判断是否有票
flag = false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums-- + "张票...");
}
}
不安全集合
import java.util.ArrayList;
import java.util.List;
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
同步方法及同步块(synchronized)
同步方法
通过 private 关键字来保证数据对象只能被方法访问
同步方法(synchronized) 包括两种用法 : synchronized 方法 和synchronized 块
synchronized方法控制对 “对象” 的访问 , 每个对象对应一把锁 , 每个 synchronized方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 , 方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行。
缺陷: 若将大的一个方法申明为 synchronized 将会影响效率
安全买票-同步方法
//安全的买票
//利用同步块处理安全买票事件在【个人练习】
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket2 buyTicket = new BuyTicket2();
new Thread(buyTicket, "张三").start();
new Thread(buyTicket, "李四").start();
new Thread(buyTicket, "黄牛").start();
}
}
class BuyTicket2 implements Runnable {
private int ticketNums = 10; //票
boolean flag = true; //外部停止方式
//synchronized 默认锁的是 this
public synchronized void run() { //同步方法
while (flag) { //模拟延时
try {
buy();
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if (ticketNums <= 0) {//判断是否有票
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "拿到了" + ticketNums-- + "张票...");
}
}
同步块
同步块:synchronized(Obg){ }
Obj 称为同步监视器
1. obj 可以是任何对象,但是使用共享资源作为同步监视器
1. 同步方法中无需指定同步监视器 , 因为同步方法的同步监视器就是this , 就是这个对象本身 , 或者是 class [ 反射中讲解 ]
同步监视器的执行过程
-
第一个线程访问 , 锁定同步监视器 , 执行其中代码
-
第二个线程访问 , 发现同步监视器被锁定 , 无法访问
-
第一个线程访问完毕 , 解锁同步监视器
-
第二个线程访问, 发现同步监视器没有锁 , 然后锁定并访问
安全取钱-同步块
//安全的取钱
//两个人去银行取钱,账户
//利用同步方法处理安全取钱事件在【个人练习】
public class UnsafeBank {
public static void main(String[] args) {
Account2 account = new Account2(100, "理财基金");
Drawing2 you = new Drawing2(account, 50, "你");
Drawing2 girlFriend = new Drawing2(account, 100, "girlFriend");
you.start();
girlFriend.start();
}
}
class Account2 {
int money; // 余额
String name; // 卡名
public Account2(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing2 extends Thread {
Account2 account;//账户 包含了里面的余额和卡名称
int drawingMoney;//取了多少钱
int nowMoney;//现在手里多少钱
public Drawing2(Account2 account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
//synchronized 默认锁的是 this
public void run() {
synchronized (account) {
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足......");
// System.out.println(this.getName()+"余额不足......");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney; //卡内余额 = 余额 - 你取的钱
nowMoney = drawingMoney + nowMoney; //你手里的钱
System.out.println(account.name + "余额为: " + account.money);
// Thread.currentThread().getName() == this.getName()
System.out.println(this.getName() + "手里的钱为: " + nowMoney);
}
}
}
同步方法和同步代码块中的锁对象是什么?
注意:这里的锁处理的是同一个对象,是可以进行增删改的对象。
-
对于普通同步方法,锁是当前实例对象,如果有多个实例,锁对象必然不同,无法实现同步
-
对于静态同步方法,锁是当前类的 class 对象,有多个实例,但是锁对象是相同的,可以实现同步
个人练习
安全取钱-同步方法
//安全的取钱
//两个人去银行取钱,账户
class P20_MyTest02 {
public static void main(String[] args) {
Test01 test01 = new Test01();
test01.setAccount(1000,600);
new Thread(test01, "小猫").start();
new Thread(test01, "小狗").start();
}
}
//银行:模拟取款
class Test01 implements Runnable {
private int account;//账户 包含了里面的余额和卡名称
int drawingMoney;//取了多少钱
int nowMoney;//现在手里多少钱
public void setAccount(int account, int drawingMoney) {
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
//synchronized 默认锁的是 this
public synchronized void run() {
if (account - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足......");
// System.out.println(this.getName()+"余额不足......");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account = account - drawingMoney; //卡内余额 = 余额 - 你取的钱
nowMoney = drawingMoney + nowMoney; //你手里的钱
System.out.println(account + "余额为: " + account);
// Thread.currentThread().getName() == this.getName()
System.out.println(Thread.currentThread().getName() + "手里的钱为: " + nowMoney);
}
}
安全买票-同步代码块
//利用同步代码块解决安全买票问题。
//1. 创建共享资源为同步监听器。(锁是当前平台类的 class 对象)
//2. 执行同步代码块。
//3. 执行完毕,解锁同步监视器
//4. 第二个线程访问,发现无锁,后锁定访问.
public class P20_MyTest {
public static void main(String[] args) {
xiecheng xc = new xiecheng(100, "火车"); //第一步
money money = new money(xc,80 ,"黄牛" );
money money02 = new money(xc,100 ,"大米" );
Thread t1 = new Thread(money,"张三");
Thread t2 = new Thread(money02,"李四");
t1.start();
t2.start();
}
}
//平台
class xiecheng {
static int nums; //票数
String car; //汽车票
public xiecheng(int num, String car) {
this.nums = num;
this.car = car;
}
}
class money implements Runnable { //模拟买票
xiecheng xc; //车票名字
int go_; //买了多少
int tu_; //剩余多少
String name;
public money(xiecheng xc, int go_, String name) {
this.xc = xc;
this.go_ = go_;
this.name = name;
}
public void run() {
synchronized (xc) { //第二步
if (xc.nums - go_ < 0) {
System.out.println(Thread.currentThread().getName() + xc.car+"票需要: "+go_);
System.out.println(xc.car+"票余额不足......");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
xc.nums = xc.nums - go_; //卡内余额 = 余额 - 你取的钱
tu_ = go_ + tu_; //你手里的钱
System.out.println(xc.car + "票余额为: " + xc.nums);
// Thread.currentThread().getName() == this.getName()
System.out.println(Thread.currentThread().getName() + "手里的票为: " + tu_);
}
}
}
死锁
产生死锁的四个必要条件:
-
互斥条件:一个资源每次只能被一个进程使用
-
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
-
不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺
-
循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系
处理死锁的方法: 破其中的任意一个或多个条件就可以避免死锁发生
//死锁: 多个线程相互占用对方需要的资源,然后形成僵持
public class P22_DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
//化妆
class Makeup extends Thread {
//需要的资源只有一份,用 static 修饰
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String girlName; //使用化妆品的人
Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
//化妆
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//死锁情况
// //化妆, 相互占用对方的资源, 拿到对方的资源
// private void makeup() throws InterruptedException {
// if (choice == 0) {
// synchronized (lipstick) { //获得口红锁
// System.out.println(this.girlName + "获得口红锁...");
// Thread.sleep(1000);
// synchronized (mirror) { // 1秒钟后获得镜子锁
// System.out.println(this.girlName + "获得镜子锁...");
// }
// }
// } else {
// synchronized (mirror) { //获得镜子锁
// System.out.println(this.girlName + "获得镜子锁...");
// Thread.sleep(2000);
// synchronized (lipstick) { // 2秒钟后获得镜子锁
// System.out.println(this.girlName + "获得口红锁...");
// }
// }
// }
// }
//解决死锁
//化妆, 相互占用对方的资源, 拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) { //获得口红锁
System.out.println(this.girlName + "获得口红锁...");
Thread.sleep(1000);
}
synchronized (mirror) { // 1秒钟后获得镜子锁
System.out.println(this.girlName + "获得镜子锁...");
}
} else {
synchronized (mirror) { //获得镜子锁
System.out.println(this.girlName + "获得镜子锁...");
Thread.sleep(2000);
}
synchronized (lipstick) { // 2秒钟后获得口红锁
System.out.println(this.girlName + "获得口红锁...");
}
}
}
}
Lock(锁)
-
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock(可重入锁) 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
测试Lock锁
import java.util.concurrent.locks.ReentrantLock;
//测试Lock锁
public class P23_TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
int tickNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
public void run() {
while (true) {
lock.lock(); // 加锁
try {
if (tickNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余 "+tickNums--);
}else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}
}
}
}
syuchronized 与 Lock 的对比
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁 u 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序: Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)
5. 线程通信问题
生产者消费者问题
线程同步问题:生产者和消费者共享一个资源,互为依赖,互为条件。
-
对于生产者 , 没有生产产品之前 , 要通知消费者等待,而生产了产品之后 , 又需要马上通知消费者消费
-
对于消费者 , 在消费之后 , 要通知生产者已经结束消费 , 需要生产新的产品以供消费
在生产者消费者问题中 , 仅有synchronized是不够的
-
synchronized 可阻止并发更新同一个共享资源 , 实现了同步
-
synchronized 不能用来实现不同线程之间的消息传递 (通信)
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待 , 直到其他线程通知 , 与sleep不同 , 会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程 , 优先级别高的线程优先调度 |
注意:均是Object类的方法 , 都只能在同步方法或者同步代码块中使用, 否则会抛出异常IllegalMonitorStateException
解决方式1---管称法
并发协作模型 “ 生产者 / 消费者模式 ” --->管程法
生产者 : 负责生产数据的模块 (可能是方法 , 对象 , 线程 , 进程)
消费者 : 负责处理数据的模块 (可能是方法 , 对象 , 线程 , 进程)
缓冲区 : 消费者不能直接使用生产者的数据 , 他们之间有个 “ 缓冲区 生产者将生产好的数据放入缓冲区 , 消费者从缓冲区拿出
//测试生产者消费者模式 ---> 利用缓冲区解决: 管称法
//生产者 消费者 产品 缓冲区
public class P25_TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer(); //缓冲区
new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}
//生产者
class Productor extends Thread {
SynContainer synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
public void run() {
for (int i = 0; i < 100; i++) {
synContainer.push(new Chicken(i));
System.out.println("生产了" + i + "个产品...");
}
}
}
//消费者
class Consumer extends Thread {
SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了 ---> " + (synContainer.pop().id) + "个产品...");
}
}
}
//产品
class Chicken {
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer {
Chicken[] chickens = new Chicken[10]; //需要一个容器大小
int count = 0; //容器计数
//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果容器满了,就需要等待消费者消费
/*如果是if的话,假如消费者1消费了最后一个,这是index变成0此时释放锁被消费者2拿到而不是生产者拿到,这时消费者的wait是在if里所以它就直接去消费index-1下标越界,如果是while就会再去判断一下index得值是不是变成0了*/
while (count == chickens.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,就需要放入产品
chickens[count] = chicken;
count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop() {
while (count <= 0) {
//不能消费时(实际数为0时) 消费者等待 生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
this.notifyAll();
return chicken;
}
}
解决方式2---信号灯法
//测试生产者消费者问题2: 信号灯法,标志位解决
public class P26_TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者--> 演员
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv=tv;
}
public void run() {
for (int i = 1; i <= 20; i++) {
if(i%2==0){
this.tv.play("快乐大本营播放中...."+i);
}else {
this.tv.play("抖音: 记录美好生活...."+i);
}
}
}
}
//消费者--> 观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
public void run() {
for (int i = 1; i <= 20; i++) {
tv.watch();
}
}
}
//产品--> 节目
class TV{
//演员表演,观众等待
//观众观看,演员等待
String voice; //表演的节目
boolean flag = 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;
}
}
线程池
背景经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。 可以避免频繁创建销毁、实现重复利用
类似生活中的公共交通工具。
使用线程池
-
JDK 5.0起提供了线程池相关API:ExecutorService(线程池) 和 Executors
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
//创建一个大小为10的一个线程池
ExecutorService service = Executors.newFixedThreadPool(10); -
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
-
void shutdown() :关闭连接池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class P27_TestPool {
public static void main(String[] args) {
// 1. 创建服务, 创建线程池
// newFixedThreadPool 参数为线程池大小 线程池大小为10
ExecutorService service = Executors.newFixedThreadPool(10);
// 2. 执行命令
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 3. 关闭线程池
service.shutdown();
}
}
class MyThread implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
好处
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理(....)1. corePoolSize:核心池的大小 2. maximumPoolSize:最大线程数 3. keepAliveTime:线程没有任务时最多保持多长时间后会终止
6. 高级主题
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class P27_ThreadNew {
public static void main(String[] args) {
new Thread(new MyThread01(),"Thread").start();
new Thread(new MyThread02(),"Runnable").start();
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread03());
new Thread(futureTask,"Callable").start();
try {
Integer integer = futureTask.get();
System.out.println(Thread.currentThread().getName()+"---"+integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread01 extends Thread{
public void run() {
System.out.println(Thread.currentThread().getName()+"---MyThread01");
}
}
class MyThread02 implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"---MyThread02");
}
}
class MyThread03 implements Callable <Integer>{
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"---MyThread03");
return 100;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现