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();
}
}
posted @   站着说话不腰疼  阅读(43)  评论(0编辑  收藏  举报
编辑推荐:
· 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 重磅开源!
点击右上角即可分享
微信分享提示