14、多线程
多线程
程序:Program,是一个指令的集合
进程:Process,(正在执行中的程序)
进程是程序的一次动态执行过程,占用特定的地址空间。
每个进程都是独立的,由三部分组成cpu,data,code
缺点:内存的浪费,cpu的负担
线程:是进程中一个“单一的连续控制流程”(a singles Thread,equential flow of control)/执行路径
1.线程又被称为轻量级进程(lightweight process)
2.Thread run at the same time, independently of one another
3.一个进程中的线程共享相同的内存单元/内存地址空间->可以访问相同的变量和对象,而且他们从同一堆中分配对象->通信. 数据交换 同步操作
4.由于线程间是在同一地址空间上进行的,所以不需要额外的通信机制,这使得通信更加简便而且信息传递的速度也更快。
线程与进程
创建线程的两种方式
通过继承Thread类来创建
缺点:Java是单继承的,如果一个类继承了Thread类,那么该类就不法继承其他的类,所以不建议使用这种方式。并且成员变量都是特有的不具备共享。
/*在 Java 中负责线程的这个功能的是 Java.lang.Thread 这个类 可以通过创建 Thread 的实例来创建新的线程。 每个线程都是通过某个特定 Thread 对象所对应的方法 run( )来完成其操作的,方法 run( )称为线程体。 通过调用 Thead 类的 start()方法来启动一个线程。 */ public class TestThread extends Thread { public void run() { for(int i=0;i<100;i++){ System.out.println(this.getName()+":"+i); } } public static void main(String[] args) { TestThread thread1 = new TestThread(); thread1.start(); TestThread thread2 = new TestThread(); thread2.start(); } }
实现Runnable接口
Runnable接口里面有一个方法。run();
实现Runnable接口的实现类不具备创建线程的能力,所以创建线程需要用Thread的构造方法,里面可以传进一个Runnable接口的实现类。再调用start();
优点:避免了单继承的问题,接口是可以实现多继承的,并且多个线程还可以共享资源。
缺点:写一个线程需要写大量的代码。
package thread; public class MyThread02 implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("thread02:"+i); } } } /////////////////////////////////////// package thread; public class Test02 { public static void main(String[] args) { MyThread02 mt02 = new MyThread02(); Thread th = new Thread(mt02); th.start(); for (int i = 0; i < 50; i++) { System.out.println("main:"+i); } } }
比如说网上购票系统。票数是固定的,多个窗口出售的只能是这么多票,而不是每个窗口都有这么多张票。
但是资源共享的情况下,一张票也可能会被多个抢到,所以这里涉及到了线程安全的问题。
代理设计模式
什么称为代理设计模式?
当你需要做某种东西,而你又做不了,那你又不得不去做,所以需要去找一个会做这个东西的人或物来帮你做。
而具有某种能力的东西,我们在代码里面就是接口,接口里面的方法就是一种能力。
所以将接口定义成自己的成员变量,而帮你做事情的这个人则必须实现该接口才能帮你去做这个事情,达成协议后,当你需要做这件事的时候,你只需要通过成员变量调用接口里的方法即可。这种设计模式称为代理设计模式。
//接口 package driver; public interface DrivingInterface { void driving(); } //需要找代理 package driver; public class Person { private DrivingInterface dr;//将他变为成员变量 public void goHome() { dr.driving();//调用方法来找到代理 } public Person() { super(); // TODO Auto-generated constructor stub } public Person(DrivingInterface dr) { super(); this.dr = dr; } public DrivingInterface getDr() { return dr; } public void setDr(DrivingInterface dr) { this.dr = dr; } } //受代理 package driver; public class Driver implements DrivingInterface{ @Override public void driving() { System.out.println("代驾开车了......"); } } //测试 package driver; public class Test { public static void main(String[] args) { Person wangcai = new Person(); Driver dr = new Driver(); wangcai.setDr(dr); wangcai.goHome(); } }
多线程的生命周期
新生状态:当线程被new出来的时候。(无资格无资源)
就绪状态:当线程调用start()方法后就进入就绪状态。(有资格无资源)
运行状态:当调到cpu的资源后,进入的运行状态,当run()还没执行完就结束了,就会回到就绪状态。(有资源有资格)
阻塞状态:导致阻塞线程的事件,比如线程的休眠sleep方法(无资格让资源)
死亡状态:run()正在的执行完毕,抛出未捕获的异常。
常用的方法:
currentThread();获得当前运行的线程。
getName();获取当前线程的名字。
setPriority();设置优先级。最高为10.
sleep();让线程进入休眠。
join();调用的该线程要执行完才能执行其他的线程。
yield();让出一次cpu的资源,然后再与其他线程一起抢。
interrupter();中断休眠。
package practect; class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+"-------"+i); // try { // Thread.sleep(5000); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } } } } ///////////////////////////////////////////////////////////////////////// public class Text { public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); //设置线程优先级 // mt.setPriority(10); // StackTraceElement[] st = mt.getStackTrace(); // for (StackTraceElement str : st) { // System.out.println(str); // } // Thread.currentThread().setPriority(10); for (int i = 0; i < 50; i++) { //currentThread()获取正在运行的线程 getName()获取线程的名字 System.out.println(Thread.currentThread().getName()+"****"+i); //System.out.println(mt.getState()); // if(i==49) { //判断线程是否存活,从线程调用start()方法开始,就绪状态到死亡状态为true // System.out.println(mt.isAlive()); // // try { //sleep()让线程进入休眠 // Thread.sleep(1000); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // //中断正在休眠的线程 // mt.interrupt(); // try { // //执行到这里,其他线程会进入阻塞状态, // //等被执行的线程完成执行完再重新回到join方法后执行其他的 // mt.join(); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } //// } //让出cpu的资源,然后再重新一起抢夺 if(i==20) { Thread.yield(); } } } }
同步锁synchronized
当我们线程需要处理数据的时候,会导致数据的不一致,此时会涉及到线程的安全问题,比如说同一张票多个人抢,当一个线程判断还有一张票,cpu资源就用完了。只能能下一次调用到下一次的cpu完成下面的操作,此时票依然还是有一张,而其他线程得到cpu的资源有开始判断,而if仍然成立,这就会造成两个或者多个人抢到的是同一张票,这一样就会造成线程的安全问题。
为了解决这一问题,引入了同步锁的概念。当有了同步锁,当执行锁定的数据时,这个数据没有用完,锁就不解,而其他的线程只能处于等待状态,等什么时候锁解了,其他线程才会去抢占这个资源,而抢到的这个线程又会将这个数据锁住,这样就解决了数据不一致的问题。
缺点:会浪费大部分的时间来等待资源。
实现同步有两种方式:
一是同步代码:synchronized(this){//需要同步的代码}
二是同步方法:public synchronized void
(){//需要同步的代码}//在方法返回值前面加synchronized修饰符
package demo; public class Account implements Runnable{ private int money = 1000; private boolean flag = false; @Override public void run() { for (int i = 0; i < 5; i++) { =======》 synchronized(this) { if(money>=0) { System.out.println(Thread.currentThread().getName()+",准备取款"); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(money-200>=0) { System.out.println("取出200元"); System.out.println("剩余:"+(money-=200)+"元"); System.out.println(Thread.currentThread().getName()+",完成取款"); }else { System.out.println("余额不足,"+money+"元"+Thread.currentThread().getName()+"的取款,不能完成支付"); } }else { System.out.println("余额不足,"+money+"元"+Thread.currentThread().getName()+"的取款,不能完成支付"); } } try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void qu(int money) { } }
死锁
双方都进入互相等待的逻辑时。
等待和唤醒
生产者/消费者 模式
我们都知道需要有生产者生产出来东西才能被消费者去使用,同时,生产多少,才能消费多少,而不是你没有生产我也可以去消费,也不能我消费的比生产的还有多,所以为了解决这问题,我们加上了等待和唤醒。、
wait();线程等待
notifyAll();线程唤醒
当我们消费者的线程执行到的时候,是要先进入到判断生产者是否生产出东西了,你才能使用,不然你就只能进入到等待状态。要有等待唤醒,或者设置等待的时间已过,否则将不能被是使用
方法名 | 作用 |
---|---|
final void wait() | 表示线程一直等待,直到其它线程通知 |
final void wait(long timeout) | 线程等待指定毫秒参数的时间 |
final void wait(long timeout,int nanos) | 线程等待指定毫秒、微妙的时间 |
final void notify() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行 |
//生产的商品 package demo03; public class Goods { /** * 当flag为false的时候,让生产者去生产,让消费者去等待 * 当flag为true的时候,让生产者等待,让消费者去消费 */ private boolean flag = false; private String name; private String brand; public Goods() { super(); // TODO Auto-generated constructor stub } public Goods(String name, String brand) { super(); this.name = name; this.brand = brand; } public synchronized void cuts() { //一开始生产者没有生产,所以flag为false,所以要取反 if(!flag) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("消费者消费:"+getBrand()+getName()); flag = false; notifyAll(); } public synchronized void prdc(int i) { //如果有商品就进入等待,所以不用取反 if(flag) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(i%2==0) { setBrand("旺仔"); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } setName("小馒头"); System.out.println("生产者生产:"+getBrand()+getName()); }else { setBrand("哇哈哈"); try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } setName("矿泉水"); System.out.println("生产者生产:"+getBrand()+getName()); } //表示生产好商品了 flag = true; notifyAll(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } } //生产者 package demo03; public class Prd extends Thread{ private Goods gs; @Override public void run() { for (int i = 0; i < 10; i++) { gs.prdc(i); } } public Prd(Goods gs) { super(); this.gs = gs; } public Prd() { } public Goods getGs() { return gs; } public void setGs(Goods gs) { this.gs = gs; } } //消费者 package demo03; public class Customer extends Thread{ private Goods gs; @Override public void run() { for (int i = 0; i < 10; i++) { gs.cuts(); } } public Customer(Goods gs) { super(); this.gs = gs; } public Customer() { } public Goods getGs() { return gs; } public void setGs(Goods gs) { this.gs = gs; } } //测试类 package demo03; public class Test { public static void main(String[] args) { Goods gs = new Goods(); new Prd(gs).start(); new Customer(gs).start(); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· Open-Sora 2.0 重磅开源!