多线程_多线程技能

1.进行和多线程的概念以及线程的优点

  • 进程:进程就是一段程序的执行过程。
    1.进程是一个实体。每一个进程都有它自己的地址空间
    2.进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。

  • 线程:线程是进程的一个实体,是进程的一条执行路径。
    通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。

多线程优点:
1.单线程的特点是排队执行,即同步.CPU的利用率会大幅度降低
2.使用多线程后,CPU在不同线程中来回切换,因为切换速度快,看起来这些任务就是同时运行的,所以使用多线程技术,可以在同一时间内运行更多不同种类的任务

注意:
1.全部占有CPU,并不是全部利用啊!单线程全部占有了cpu,但是利用率不高,多线程时cpu在不同线程中切换,会提供利用率.
2.多线程并不一定比单线程的执行效率更高


2.使用多线程

  • 继承Thread类
  • 实现Runnable接口

这两种方式创建的线程在工作时性质是一样的,没有本质区别


2.1 继承Thread类

Thread也实现了Runnable接口,并重写了run方法
在这里插入图片描述

2.2 实现Runnable接口

如果欲创建的线程类已经有一个父类了,因为java不支持多继承,此时就能通过实现Runnable接口
在这里插入图片描述

2.3 线程调用的随机性

在使用多线程技术时,代码的运行结果和代码执行顺序或调用顺序是无关的.

Thread类中的start方法通知线程规划器,此线程已经准备就绪了,等待调用线程对象的run方法,然后系统安全一个时间来调用run方法,此时线程运行


2.4 变量实例和线程安全

(1)不共享数据的情况
每个线程,有各自的变量.多个线程不同享同一个变量实例
在这里插入图片描述
我们创建了三个线程,每个线程都有各自的count变量,自己减少自己的count变量的值,这样的情况就是变量不共享的,也就是说不存在多个线程访问同一个变量实例的情况.

(2)共享数据的情况
在这里插入图片描述
结果如下:
在这里插入图片描述
由此可见,线程A和线程B打印的count都是3,这是因为线程A执行了count–后,线程B也执行了count–,此时两个线程都还没来得急向控制台输出内容,而count的值已经是3了

扫描二维码关注公众号,回复: 6422172 查看本文章

在JVM中,i–的操作分为如下3步:
1.取得原有i值
2.计算i-1
3.对i重新赋值

在这三个步骤中,如果有多个线程同时访问,那么一定会出现非线程安全的问题

我们想要得到的结果是依次递减,那么如何做才行呢?
我们观察源代码,发现产生非线程安全问题的原因在于,多个线程在同一时间调用了线程类对象的run方法,而在run方法中,对共享数据既有读的操作,又有写的操作.那么我们只要保证多个线程调用的run方法是串行执行,就不会存在非线程安全问题了
在这里插入图片描述
非现场安全:
主要指多个线程对同一个对象中同一个实例变量进行操作时会出现值被更改,值不同步的情况,进而影响程序的执行流程
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
流程如下,a线程启动调用run方法,在run方法中,执行LoginServlet.doPost方法时,由于un=‘a’,a线程睡了5秒,在这5秒时间中,b线程也开始执行doPost方法,因为un是常量(共享),所以此时被重新赋值了,然后打印了un=b,pw=bb,时间一到,a线程继续往下执行,pw=aa,所以最终打印了un=b,pw=aa

那么如果解决这个先线程安全问题?
1.很简单,对doPost加锁(静态方法上的锁,锁的是class类)
2.将读和写的地方,放一块加锁,保证读写一致(当前线程读到东西,得是当前线程改的东西),写的地方是un=userName,而读的地方是syso
在这里插入图片描述

2.5 留意i–和System.out.println()的异常

我们发现println()方法加了锁,但是当pringln方法中参数–时,还是会发生非线程安全问题
在这里插入图片描述
结果如下:
在这里插入图片描述
出现非线程安全问题的原因在于:println()方法是同步的,但是i–的操作是在进入println之前发生的,只对读的操作加了锁,没对写的操作加锁,导致了非现场安全问题


3.currentThread()方法

currentThread()方法返回代码正在被哪个线程调用的信息,返回的是当前线程对象
在这里插入图片描述
Thread.currentThread()和this的区别:
代码演示:
在这里插入图片描述
执行结果:
在这里插入图片描述
总结:
1.this跟线程类对象有关,this代表线程类对象,this出现在构造函数,成员方法,run()方法中指的都是是线程类对象
2.Thread.currentThread()代表调用此方法的线程
3.在一个线程类对象中,所有方法中的出现的this都是同一个,即线程类对象,而Thread.currentThread()视哪个线程调用来决定


4.isAlive()方法

方法isAlive()的作用是测试线程是否处于活动状态,什么是活动状态?
活动状态即:线程已经启动,且尚未终止.线程处于正在运行或准备开始运行的状态,就认为线程是存活的

在这里插入图片描述
返回如下:
在这里插入图片描述
注意:
1.一般不会通过Thread.currentThread()来调用isAlive(),这样就表示调用当前方法的线程是否存活,必然是存活的,这样显得多此一举
2.上面end == true,但是此值其实是不确定的,此处打印true是因为mythread线程还未执行完毕,如果让主线程休眠一秒,给mythread线程执行完毕,那么结果为fasle
在这里插入图片描述

另外在使用isAlive()时,如果将线程对象以构造参数的形式传给Thread对象进行start()启动时,运行结果和之前的示例是有差别的
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述


5.sleep()方法

在指定的毫秒数内让当前"正在执行的线程"休眠(暂停执行),这个"正在执行的线程"指的是this.currentThread()返回的线程

注意:
this.currentThread()和this是完全不同的

  • this.currentThread()和Thread.currentThread()在大部分情况下结果是相同的,若当前子类重写了父类的currentThread()方法,那么Thread.currentThread()和this.currentThread()返回的结果会不同。
  • this表是线程类对象

在这里插入图片描述
结果如下:
在这里插入图片描述

6.getId()方法

作用:获取线程的唯一标识
在这里插入图片描述
main线程的唯一标识是1,myThread线程的唯一标识是10


7.停止线程

停止一个线程,意味着在线程处理完任务之前停掉正在做的操作,即放弃当前的操作
停止一个线程可以使用Thread.stop(),但是这个方法不安全,所以被废弃了

7.1 Java中3种可以终止正在运行的线程的方法

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止线程,不推荐
  • 使用interrupt方式中断线程

7.2 停止不了的线程

通过interrupt()方法来停止线程,但是interrupt()方法并不像for+break那样马上就停止循环,调用interrupt()方法仅仅是在当前线程中打了一个停止的标记,而非真正的停止线程
在这里插入图片描述
如上代码所示,在休眠2秒后,thread线程执行了interrupt()方法,但是并没有中断线程的执行

7.3 判断线程是否是停止状态

在java中,判断线程状态是否停止,有两种方法

  • this.interrupted():测试当前线程是否已经中断
    是一个静态方法,在一次执行后具有将状态标志清除为false的状态。即连续两次调用该方法后,第二次调用则会返回false。

  • this.isInterrupted():测试线程是否已经中断
    是一个实例方法,不会清除状态标志。

this.interrupted()的当前线程指运行this.interrupted()方法的线程

在这里插入图片描述
(1)此时没有任何的中断,所以结果都是false
(2)若是把Thread.currentThread().interrupt()加到代码中,结果0是true,1,2,3,4都是fasle,因为interrupted()测试的是当前线程的状态,0,1,3的当前线程是main(在main线程中,使用thread线程调用interrupted方法),而当前线程的状态已经中断了,所以是true.但是interrupted()有清除状态的功能,所以其他调用该方法的结果都是false。
(3)若把thread.interrupt();加到代码中,结果0,1,3,4是false,2是true,因为isInterrupted测试的是调用该方法的线程状态,因此2是true
(4)若把t1.interrupt()加到代码中,结果为0,1,2,3为false,4为true。原因同上。

7.4 停止线程的方式1_能停止的线程(异常法)

在这里插入图片描述
运行结果如下:
在这里插入图片描述
上面的示例,我们发现在休眠了2秒后,随着thread.interrupt(),线程确实被终止了,但是如果for语句下面还有语句,还是会继续运行
在main线程中,休眠2秒后,中断了thread线程,此时thread线程的run方法还未停止,且一直再判断thread线程的状态,此时捕获到thread线程中断了,所以执行break,但是break只跳出for循环

那么如何解决继续运行的问题呢?
解决方法:在线程中,循环判断当前线程的运行状态,如果已经中断,直接抛出一个异常
在这里插入图片描述
运行结果如下
在这里插入图片描述

7.5 停止线程的方式2_在沉睡中停止

原理:当线程在sleep()状态下,停止线程,会中断线程的执行,并抛异常
在这里插入图片描述
当在sleep状态下停止某一个线程,会抛出InterruptedException异常,且清除状态值,使变为false

上面的实验是线程先sleep,然后中断,那么我们换一下,让线程先中断,因为Interrupt只是打了中断的标记,所以还是继续执行run中代码,此时在run方法中,将线程sleep,那么会怎么样呢?
在这里插入图片描述
结果如下:在遍历时,其实已经执行了Interrupt方法,但是因为Interrupt方法并不会直接中断线程的执行,所以线程还是往下执行,在打印了run begin之后碰到sleep方法,直接抛出异常
在这里插入图片描述

7.6 停止线程的方式3_暴力停止

使用stop方法停止线程则非常暴力,不推荐使用
在这里插入图片描述

7.7 方法stop()和java.lang.ThreadDeath异常

调用stop()时,会抛出java.lang.ThreadDeath异常,但在通常情况下,这个异常不用显示的捕获
在这里插入图片描述
stop()已经作废了,因为线程直接停止可能使一些清理性的工作得不到完成,且对锁定的对象进行解锁,导致数据得不到同步的处理,出现数据不一致的情况

7.8 释放锁的不良后果

使用stop释放锁,会造成数据不一致的结果,如果出现这样的情况,程序处理的数据就会遭到破坏,最终导致程序执行的流程错误,一定要注意

7.9 使用return停止线程

使用interrupt()和return结合使用也能实现停止线程的效果
在这里插入图片描述
不过还是推荐使用"抛异常"的方法,因为抛出异常,可以将异常向上传播

8.暂停线程

暂停线程意味着线程还能恢复运行,这是跟停止线程最大的不同,在java中,我们通过suspend()方法暂停线程,以及resume()方法恢复线程

8.1 suspend和resume的使用

在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
我们发现在线程暂停的这段时间内,并没有去执行run方法.从时间上来看,线程被暂停了,同时也可以通过resume恢复成运行的状态

8.2 suspend和resume方法的缺点1——独占

在使用suspend和resume方法时,如此使用不当,容易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象
在这里插入图片描述在这里插入图片描述
结果如下:
在这里插入图片描述
我们发现thread2线程启动了,也执行了run方法,但是没有执行object.printString(),因为锁并未归还

还有另外一种独占锁的情况,也要格外注意!
在这里插入图片描述
控制台并没有打印"main end",原因在于,当程序运行到printIn()方法内部停止时,同步锁并没有被释放,这导致当前PrintStream对象的printIn()一直处于"暂停"状态,而且"锁未释放",而main中代码迟迟不能打印

8.3 suspend和resume方法的缺点2——不同步

在使用suspend和resume方法时也容易造成因为线程暂停而导致数据不同步的情况
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
thread线程在设置值的时候,被暂停了.而thread2线程再打印值的时候,打印出了a 11

9.yieId方法

yieId()方法作用是放弃当前的CPU资源,将它让给其他任务去占用CPU执行时间,但放弃的时间不确定,很可能当前刚放弃,马上就又获得CPU时间

在这里插入图片描述
执行结果如下:
在这里插入图片描述
当释放了注释后
在这里插入图片描述

10.线程的优先级

线程可以划分优先级,优先级较高的线程得到CPU的资源较多,就是说CPU优先执行优先级较高的线程对象中的任务

设置线程优先级,有利于帮助"线程规划器"确定在下一次选择哪一个线程来优先执行

设置优选级使用setPriority()方法

10.1 线程的优先级的继承特性

在A线程中启动了B线程,则B线程的优先级跟A一样
在这里插入图片描述
结果如下:
在这里插入图片描述
如果将注释去掉,main线程的优先级设置为6,那么在main线程中启动的myThread线程的优先级也是6,同理myThread2也是6

10.2 优先级具有规则性

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部执行完,不要以为myThread的线程先被main调用就会先执行完,线程的执行顺序,主要由myThread1的优先级为10造成的,谁先执行完跟代码的调用顺序无关

在这里插入图片描述
结果如下:
在这里插入图片描述

10.3 优先级具有随机性

线程具有"随机性",优先级高的线程不一定每次都先执行
当我们把两个线程的优先级设置为5和6,两个线程的优先级很接近,此时运行的结果就是交错的运行线程

不要把线程的优先级与运行结果的顺序作为衡量的标准,优先级较高的线程不一定每次都先执行完

10.4 看谁运行的快

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最终显示:
在这里插入图片描述

11.守护线程

java线程中分为两种

  • 用户线程
  • 守护线程

当进程中不存在非守护线程后,则守护线程自动销毁,典型的守护线程就是垃圾回收线程
在这里插入图片描述
我们发现将myThread设置为守护线程后,主线程休眠了5秒,myThread也就运行了5秒,当main线程结束,myThread线程也就不工作了

猜你喜欢

转载自blog.csdn.net/qq_24099547/article/details/90393249