首页 >> 大全

Day015--java中的多线程

2023-12-29 大全 26 作者:考证青年

目录

线程的概述

线程的生命周期

线程的创建

案例:

接口

案例:

线程的控制

线程的挂起

join方法

状态检查

线程的优先

线程的守护

线程的同步机制

使用同步代码块进行上锁

​编辑使用同步方法上锁

水池排注水问题

多线程的概述

我们之前所写的代码(程序)都是单线程的,但是很多时候我们程序往往处理的任务不止一个,而是两个及两个以上。就好比一个司机正在高速公路上开着车,突然,他的手机响了,没有一点点交通安全常识(或总认为自己是独一无二的那个,不会出事情)就拿出来了他的手机接听,于是在这个时候他的大脑就不得不同时做两件事情,开车和接听电话。众所周知,边开车边接电话是不安全的,因为在这个时候人的注意力是在车子和电话之间进行转换的,有些时候如果接电话入迷了,没有注意道路情况就会不可避免的发生交通事故。这样子是不是很危险?是的,这样无疑是很危险的,因此不要去做。----此案例中的司机就是一个进程,而他所做的事情(执行的任务)就称为线程,我们可以看到他在同一时间做的不是一件事(个任务)而是两件事(个任务)---并发,因此他是一个多线程的并发进程。

在java中是支持多线程机制的,而什么是线程呢?这得从程序讲起。

值得注意的是:java中的线程并不是同时进行的,只是cpu在利用不同的时间片段去执行每个线程。因为线程之间的执行控制权转换很快,就造成了“同时运行的错觉”。

就这么光理论上的说说可能有点枯燥,那么我们先使用代码来看看线程的大概运行流程:

(这里涉及的代码就是让大家对多线程有个大概的轮廓)

我们先通过实现接口,然后每隔100毫秒打印输出能够被10整除的数

大家看到上面的问题和实现是不是会有一种疑问,我们明明可以直接在main方法中写一个判断输出语句即可,为什么还要多此一举的实现接口,并且还要使用start方法来启动它,以及使用sleep方法来让它休眠?大家想到的是不是下面这种方法,直接就是一个循环判断语句块?

但是相信大家也发现了,这样子的输出是很快的,并且我们在控制台是看不到最开始的数字的。如果我们使用线程,不仅可以让输出减缓,而且也可以编写另外的好几条循环语句,来让他们并发执行。如果我们还是之前的单纯类,没有继承或者是实现的话,就只能是执行单线程的任务,不能多个语句块同时执行。

接下来我们在线程里面实现我们的任务:为了方便使用start()可以直接让我们的类继承类。

在循环输出能够被10整除的数时,我们再创建一个线程用于循环输出质数

Day015--java中的多线程__Day015--java中的多线程

因为我们的系统资源足够,线程优先级没有体现出来。与之类似的还有yield()---礼让。从上面的运行效果我们可以看到两个线程的运行是交替着来的。

public class Control245 extends Thread{//在控制台输出可以被10整除的数,每输出一个数字后,实现线程休眠100毫秒int startNum=1;public void run() {while(true) {if(startNum==0) {System.out.println(startNum);}startNum++;try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {
//       new Thread(new  Control245()).start();    //使用实现Runnable接口的方式在创建对象时,外面再套个Thread的外套之后才能使用start方法new Zhi(6).start();new Control245().start(); System.out.println("Control245的优先级:"+new Control245().getPriority());System.out.println("Zhi的优先级:"+new Zhi(6).getPriority());}}
class Zhi extends Thread{public Zhi(int priority) {setPriority(priority);    
/*给线程设定优先级只是为了在系统资源紧张的情况下,
JVM会根据线程的优先级决定线程的执行顺序让优先级比较高的线程先执行。
*/}int  startNum=1;boolean flag=true;int i;public void run() {while(true) {for(int i=2;i<100;i++) {boolean flag=true;for(int j=2;j<=i/2;j++) {if(i%j==0) {flag=false;break;}}if(flag) {System.out.println(i+"是质数");try {Thread.sleep(100);    //线程每输出一句语句就休眠100毫秒} catch (InterruptedException e) {e.printStackTrace();}}}}}}

线程的生命周期

对于线程的生命周期有几个,众说纷纭,但是说得最多的是六个或者是七个。一般而言我们的线程主要是有七个:

出生:用户在创建线程时处于的的状态,在使用start方法之前,线程都处于出生状态就绪:用户调用start方法后,线程处于就绪状态(可执行状态)运行:线程得到系统资源后进入运行状态等待:处于运行状态的线程调用了类中的wait方法,可使用方法唤醒休眠:处于运行状态的线程调用了类中的sleep方法阻塞:如果一个线程在运行状态下发出输入/输出请求,输入/输出结束时,线程进入就绪状态。死亡:对于阻塞的线程来说,即使系统资源空闲,线程依旧不能回到运行状态,当线程的run方法执行完毕时,线程就进入了死亡状态。

线程的创建

java中有两种方式创建线程,一种是继承类,另外一种是实现接口。

我们完成线程真正功能的代码是放在类的run方法中的,当我们的类继承类后,就可以在该类中覆盖run方法,将实现该线程功能的代码写入run方法中,然后同时调用类中的start方法执行线程,也就是调用覆盖后的run方法。

案例:

创建两个线程,一个用来打印100到1000的水仙花数,另外一个用来打印2000到2022的数

之后我们到主方法中去启动创建的两个线程

public class DoTask  {//创建两个线程,一个用来打印100到1000的水仙花数,另外一个用来打印2000到2022的数public static void main(String[] args) {new Thread1().start();    //启动线程new Thread2().start();   //启动线程}}
class Thread1 extends Thread{//打印100到1000的水仙花数public  void run (){//一般来说线程的run方法里面会放置while死循环(无限循环),使线程一直运行下去,这里我们不需要循环输出for(int i=100;i<1000;i++) {int hundred=i/100;int ten=i0/10;int single=i0;if(Math.pow(hundred, 3)+Math.pow(ten, 3)+Math.pow(single, 3)==i) {System.out.println(i+"是水仙花数:");}}}}class Thread2 extends Thread{//打印2000到2022的数public  void run (){for(int i=2000;i<=2022;i++) {if(i==2022) {System.out.println("现在是"+i+"年");continue;}System.out.println(i+"年");}}
}

接口

前面我们也提到过:当一个类已经继承了其他类时,就不能再去继承类了,在java中提供了一种的接口,继承了其他父类的类可以通过实现接口来创建线程。实现了接口后该类还有一个极大的特点(优点):很适合多个线程共享一个资源的情况(虽然还是会出现资源调用错误,但是解决这个问题只需要后期加锁即可)。多线程共享一个资源最典型的案例就是火车站卖票问题

案例:

三个窗口售卖100张票(有且只有100张票),一旦剩余票数为0输出“票额不足”终止线程

在上面的效果中我们可以看到卖出的票数是不正确的出现了超卖的现象,如果大家在尝试的时候出现了超卖的话不要惊讶,因为这是正常情况。后期我们可以使用同步机制对其进行完善。

public class SaleTickts {public static void main(String[] args) {Worker w1=new Worker();   //启动线程new Thread(w1).start();;new Thread(w1).start();;new Thread(w1).start();;}}
class Worker implements Runnable{//三个窗口售卖100张票,每卖出一张休息一秒。一旦剩余票数为0输出“票额不足”终止线程static int tickts=100;int count=0;public void run() {while(true) {if(tickts==0) {System.out.println(Thread.currentThread().getName()+"票额不足");break;}System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票,还剩"+(--tickts));count++;try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(count);}
}

线程的控制

线程的控制包括线程的启动,挂起,状态检查,以及如何正确的结束线程,由于在程序中使用多线程,因此需要合理安排线程的执行顺序。

线程的挂起

当一个程序进入“非可执行状态”(即为挂起状态)时,必然存在某种原因使其不能继续运行,这些原因可能有如下几种情况:

join方法

join方法可以让一个线程实现插队的效果。当一个线程插队成功,则肯定先执行完插入的线程所有的任务才会去执行下一个线程的任务。

例如我们上面的的两个打印线程,如果我们想要先执行打印输出年份的线程那么我们可以使用join方法进行插队,如下:

public class DoTask  {//创建两个线程,一个用来打印100到1000的水仙花数,另外一个用来打印2000到2022的数public static void main(String[] args) {Thread2 t2= new Thread2();Thread1 t1= new Thread1(t2);t1.start();t2.start();}}
class Thread1 extends Thread{private Thread2 t2;public Thread1(Thread2 t2) {this.t2=t2;}//打印100到1000的水仙花数public  void run (){try {System.out.println("t2开始插队运行");t2.join();System.out.println("t2结束插队退出");} catch (InterruptedException e) {e.printStackTrace();}//一般来说线程的run方法里面会放置while死循环(无限循环),使线程一直运行下去,这里我们不需要循环输出for(int i=100;i<1000;i++) {int hundred=i/100;int ten=i0/10;int single=i0;if(Math.pow(hundred, 3)+Math.pow(ten, 3)+Math.pow(single, 3)==i) {System.out.println(i+"是水仙花数:");}}}}class Thread2 extends Thread{//打印2000到2022的数public  void run (){for(int i=2010;i<=2022;i++) {if(i==2022) {  //当i为2022时,修改输出语句System.out.println("现在是"+i+"年");continue;}System.out.println(i+"年");}}
}

状态检查

我们可以使用.().()方法获取到当前线程的状态

线程的优先级

线程的执行顺序其实跟它的优先级关系也不是很大,一般是在资源紧张的时候,系统才会先去执行优先级比较高的线程。java中的线程的范围可以是整型也可以是变量整型的范围为1~10,如果我们不去设定线程的优先级的话,默认的优先级为5。在类中有3个成员变量分别对应相应的优先级:

线程的守护

线程有两种:一种是用户进程,另外一种是守护进程。

创建两个线程,一个循环打印0到10的数字,一个循环打印100到150的数字,将循环打印100到150的数字的线程设置为守护线程

public class TestDeamon {//创建两个线程,一个只打印0到10的数字,一个循环打印100到120的数字,//将循环打印100到120的数字的线程设置为守护线程public static void main(String[] args) {Workers worker=new Workers();Daemoners daemoner=new Daemoners();daemoner.setDaemon(true);     //使用setDaemon方法将线程设置为守护进程daemoner.start();worker.start();}}
class Workers extends Thread{public void run() {for(int i=0;i<=10;i++) {System.out.println(i);}}
}class Daemoners extends Thread{public void run() {while(true) {for(int i=100;i<=150;i++) {System.out.println(i);}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}

线程的同步机制

如果程序是单线程的,我们在执行的时候是不用担心会有其他的线程来打扰我们的线程,但是如果我们使用的是多线程的话,就会出现共享资源上的竞争。就好比有两个人都吃坏了肚子,窜稀的那种,特别急切的想要上厕所,但是,很不巧只有一个厕所,这个时候就出现了共享资源发生冲突的情况,到底谁先上呢?此时就需要进行控制,否则容易阻塞(如果一直阻塞,线程获取锁失败可能会直接导致程序死亡),于是有了厕所门上的锁。在java中也有锁的概念,我们只需要将会发生冲突的共享资源上锁即可。刚刚的那两个人中,会是谁来给厕所上锁呢?毫无疑问是跑得最快的那个。在我们的java中也一样,率先访问到资源的第一个线程来为资源上锁,其他线程如果想要使用这个资源就需要等到锁解除,第二个线程获取锁成功后就会为该资源上锁。

java中的上锁可以使用同步机制()来完成。

注释:同步机制---指两个线程同时作用在一个对象时,应该保持对象数据的统一性和整体性

共享资源一般是文件,输入/输出端口,或者是打印机。

通常将操作共享资源的代码放入定义的区域内,这样当其他线程也获取到这个锁时,必须等待锁别释放时,才能进入该区域。java中有两种方式使用同步机制进行上锁:

案例依旧是之前我们的火车站卖票,现在我们对其使用同步机制,给需要同步的代码上锁

使用同步代码块进行上锁

使用同步方法上锁

public class SaleTickts {public static void main(String[] args) {Worker w1=new Worker();   //启动线程new Thread(w1).start();;new Thread(w1).start();;new Thread(w1).start();;}}
class Worker implements Runnable{//三个窗口售卖100张票,每卖出一张休息一秒。一旦剩余票数为0输出“票额不足”终止线程static int tickts=100;int count=0;boolean flag=false;public synchronized void sell() {    //使用同步方法if(tickts==0) {System.out.println(Thread.currentThread().getName()+"票额不足");//break;     //不能使用breakflag=true;    //标志变量return;      //可以使用return来返回,但是后期需要进行处理}System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票,还剩"+(--tickts));count++;try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count);}public void run() {while(true) {sell();if(flag) {     //判断语句,如果旗帜立起来时代表票额不足,退出循环break;}
}}}

水池排注水问题

public class Pool {       				 //创建水池类 static Object obj=new Object();      //在水池类里面创建超类,方便其他类来访问水池类static int totalWater=10;            //假设水池可以容纳10升的水static int haveWater=2;              //因为连续一个月的下雨,水池中现在有2升的水static int outWater=0;               //没有人去排水  public static void main(String[] args) {new Thread(new OutWater()).start();new Thread(new InWater()).start();}}class OutWater implements Runnable{         				//创建排水类public void outWaterMethod()  {     				//创建排水的方法synchronized(Pool.obj) {					//因为会有两个线程对水池的totalWater进行访问,因此我们上锁System.out.println("水池没水→"+isEmpty());if(isEmpty()) {try {Pool.obj.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("水池为空,不用排水,您可以休息。");}else {System.out.println("水池有水,请及时排掉。");Pool.outWater++;    //让水池开始排水System.out.println(Thread.currentThread().getName()+"已近排掉了"+Pool.outWater+"还剩"+(Pool.haveWater-Pool.outWater));}}}public boolean isEmpty() {						       //创建判断水池是否为空的方法return Pool.haveWater==Pool.outWater?true:false;   //如果水池中有多少水就排多少水,证明我们的水池是空的状态}public void run() {    									//线程的功能实现while(Pool.haveWater

如果大家对于上面的代码有什么更好的意见或者是更优解的话欢迎在评论区留言哈

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了