- 概述
- 为什么要多线程呢?
- 如何实现多线程呢?
- 继承Thread类
- 实现Runable接口
- 实现Runnable接口的调用过程
- 如何使用线程呢?
- 解决由于共享变量造成的安全问题
- currentThread()方法
- isAlive()方法
- sleep(long millis)方法
- 停止线程
- interrupt()停止线程
- 判断线程是否停止状态
- 异常法停止线程
- 在sleep下停止线程
- 使用暴力法stop()停止线程
- 使用return ;停止线程
如何实现多线程呢? 继承Thread类多线程能够让数据同时进行处理,比如原来一个线程处理数据,但是现在我有100个线程处理,那么现在的速率是不是>=100倍?
继承Thread类,重写里面的run方法,run里面的代码是要给jvm分配给线程执行的代码;
static class ThreadEX extends Thread{
@Override
public void run() {
super.run();
System.out.println("MyThread run");
}
}
public static void main(String[] args) {
ThreadEX threadEX = new ThreadEX();
threadEX.start();
System.out.println("运行结束");
}
运行结果:
运行结束 MyThread run
但是从上面可以发现run()方法执行居然比,“运行结束”还要晚,不合符从下向下的调用法则,这是为什么呢?
因为start()方法的执行比较耗时,这也增加了先输出“运行结束!”字符串的概率。start()方法耗时的原因是执行了多个步骤,步骤如下
1)通过JVM告诉操作系统创建Thread。
2)操作系统开辟内存并使用Windows SDK中的createThread()函数创建Thread线程对象。
3)操作系统对Thread对象进行调度,以确定执行时机。
4)Thread在操作系统中被成功执行。
这里可以总结一下:
实现Runable接口调用start()不代表执行了run()
实现Runnable接口的好处:
因为Java是单根继承,不支持多继承,所以为了改变这种限制,可以使用实现Runnable接口的方式来实现多线程技术。下面来看使用Runnable接口必要性的演示代码。
package service;
public class AServer {
public void a_save_method() {
System.out.println("a中的保存数据方法被执行");
}
}
再看业务B:
package service;
public class BServer1 extends AServer,Thread
{
public void b_save_method() {
System.out.println("b中的保存数据方法被执行");
}
}
BServer1.java类不支持在extends关键字后写多个类名,即Java并不支持多继承的写法,所以在代码“public class BServer1 extends AServer,Thread”处出现如下异常信息:
Syntax error on token “extends”, delete this token
所以现在就需要使用Interface 了
package service;
public class BServer2 extends AServer implements Runnable {
public void b_save_method() {
System.out.println("b中的保存数据方法被执行");
}
@Override
public void run() {
b_save_method();
}
}
接下来我们可以看一下Thread中的代码详情
由于该代码实现了Runnable接口,所以接下来代码就可以这么实现:
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
//MyThread是Thread的子类,而Thread是Runnable实现类
//所以MyThread也相当于Runnable的实现类
Thread t = new Thread(thread);
t.start();
}
}
实现Runnable接口的调用过程小结:
1、当类是单继承的时候可以直接使用Aclass extend Thread;
2、当类需要实现多继承的时候就需要使用到 Aclass implements Runnable;
其实Runnable和Thread的执行过程不太一样,接下来我们讲解是什么问题:
MyRunnable run = new MyRunnable(); Thread t = new Thread(run); t.start();
JVM直接调用的是Thread.java类的run()方法,该方法源代码如下:
这个target是在init()中进行初始化
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
……
this.target = target;
……
}
而方法init()是在Thread.java构造方法中被调用的,源代码如下:
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
如何使用线程呢?
解决由于共享变量造成的安全问题
非共享变量
首先我们看看不共享变量时候的情况:
public class NoShareThreadDemo extends Thread{
private int count = 4;
@Override
public void run() {
super.run();
while (count>0){
System.out.println("由"+Thread.currentThread().getName()+"执行第:"+count--+"次");
}
}
}
测试类:
// 测试多线程下非共享变量
@Test
public void test01(){
NoShareThreadDemo noShareThreadDemo1 = new NoShareThreadDemo();
NoShareThreadDemo noShareThreadDemo2 = new NoShareThreadDemo();
NoShareThreadDemo noShareThreadDemo3 = new NoShareThreadDemo();
noShareThreadDemo1.start();
noShareThreadDemo2.start();
noShareThreadDemo3.start();
}
结果:
该程序一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值,这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。
共享变量
接下来我们来看一下共享变量是如何产生问题的:
public class ShareThreadDemo extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("由" + Thread.currentThread().getName() + "执行第:" + count + "次");
}
}
测试类:
// 测试共享变量
@Test
public void test02(){
ShareThreadDemo shareThreadDemo = new ShareThreadDemo();
Thread a = new Thread(shareThreadDemo, "A");
Thread b = new Thread(shareThreadDemo, "B");
Thread c = new Thread(shareThreadDemo, "C");
a.start();
b.start();
c.start();
}
结果:
由A执行第:3次 由C执行第:2次 由B执行第:3次
这里我们发现,所有的线程都抢到了count=5这个,但是由的并没有进行减操作。如果在正常的秒杀环境中这个务必会造成超卖现象,所以接下来我们将会去展示如何解决这个问题;
安全共享变量
public class SaveShareThreadDemo extends Thread {
private int count = 5;
@Override
synchronized public void run() {
count--;
System.out.println("由" + Thread.currentThread().getName() + "执行第:" + count + "次");
}
}
测试类
// 测试安全共享变量
@Test
public void test03(){
SaveShareThreadDemo saveShareThreadDemo = new SaveShareThreadDemo();
Thread thread = new Thread(saveShareThreadDemo);
Thread thread1 = new Thread(saveShareThreadDemo);
Thread thread2= new Thread(saveShareThreadDemo);
Thread thread3 = new Thread(saveShareThreadDemo);
Thread thread4 = new Thread(saveShareThreadDemo);
thread.start();
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
测试结果
由Thread-1执行第:4次 由Thread-3执行第:3次 由Thread-2执行第:2次 由Thread-5执行第:1次 由Thread-4执行第:0次currentThread()方法
currentThread()方法可返回代码段正在被哪个线程调用。
首先创建一个测试的实体类信息;
public class MyThread extends Thread{
public MyThread(){
System.out.println("构造方法:"+Thread.currentThread().getName());
}
@Override
public void run() {
super.run();
System.out.println("run方法"+Thread.currentThread().getName());
}
}
创建测试类信息:
@Test
public void demo01(){
MyThread myThread = new MyThread();
myThread.start();
//myThread.run();
}
输出信息:
构造方法:main run方法Thread-1
这里我们可以发现,MyThread.java类的构造函数是被main线程调用的,而run()方法是被名称为Thread-1的线程调用的,run()方法是自动调用的方法。
接下来我们修改一下代码:
@Test
public void demo01(){
MyThread myThread = new MyThread();
//myThread.start();
myThread.run();
}
运行结果:
构造方法:main run方法main
isAlive()方法执行方法run()和start()还是有一些区别的。
1)my.run():立即执行run()方法,不启动新的线程。
2)my.start():执行run()方法时机不确定,启动新的线程。
isAlive():这个方法是用来判断线程是否存活,接下来我们来看一下代码
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("isAlive:"+Thread.currentThread().isAlive());
}
}
测试代码:
@Test
public void test01() throws InterruptedException {
MyThread myThread = new MyThread();
System.out.println("Begin:"+myThread.isAlive());
Thread.sleep(1000);
myThread.start();
System.out.println("End:"+myThread.isAlive());
}
运行结果:
Begin:false End:true isAlive:truesleep(long millis)方法
sleep()方法的作用是在指定的时间(毫秒)内让当前“正在执行的线程”休眠(暂停执行),这个“正在执行的线程”是指this.currentThread()返回的线程;
首先我们使用一个只调用run()方法的非创建线程的代码试一下:
public class MyThread1 extends Thread{
@Override
public void run() {
try {
System.out.println("run threadName="
+ this.currentThread().getName() + " begin");
Thread.sleep(2000);
System.out.println("run threadName="
+ this.currentThread().getName() + " end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意:下图两个睡眠方式效果是一样的,都是让当前线程进行休眠;
Thread.sleep(3000);
this.sleep(3000);
运行结果:
begin =1637301715082 run threadName=main begin run threadName=main end end =1637301717092
接下来我们调用start()方法来启用另一个线程调用sleep()看是什么效果:
public static void main(String[] args) {
MyThread1 mythread = new MyThread1();
System.out.println("begin =" + System.currentTimeMillis());
mythread.start();
System.out.println("end =" + System.currentTimeMillis());
}
运行结果:
begin =1637302217085 end =1637302217085 run threadName=Thread-0 begin run threadName=Thread-0 end
相信大家应该不难发现其中的问题吧?
停止线程由于main线程与Thread-0线程是异步执行的,所以首先输出的信息为begin和end,而Thread-0线程后运行的,在最后两行间隔了2s输出run…begin和run…end相关的信息。
由于多线程的出现,给研发人员带来了许多的挑战,如果不正确的处理停止线程后所带来的问题,那么会产生一些不可避免的影响,那么接下来我们来讲述如何停止线程;
interrupt()停止线程在Java中有3种方法可以使正在运行的线程终止运行:
1) 使用退出标志使线程正常退出。
2)使用stop()方法强行终止线程,但是这个方法不推荐使用,因为stop()和suspend()、resume()一样,都是作废过期的方法,使用它们可能发生不可预料的结果。
3)使用interrupt()方法中断线程。
interrup()其实不像for+break语句那样,马上就停止循环。当调用interrup()方法仅仅是在当前线程做了一个停止标记,并没有真正的停止线程。
我们创建测试代码:
public class MyThread extends Thread{
@Override
public void run() {
super.run();
for (int i = 0; i < 700000; i++) {
System.out.println("i="+(i+1));
}
}
}
测试类方法:
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
myThread.sleep(2000);
myThread.interrupt();
System.out.println("z-z-z-z-z---zzzz-z-z");
}
结果:
i=697875 i=697876 z-z-z-z-z---zzzz-z-z i=697877 i=697878 i=697879
这里我们发现,当线程睡眠2s的时间内for循环执行了697876次,所以interrupt()没有起到停止线程的效果,那么我们如何去停止线程呢?
判断线程是否停止状态- 1)public static boolean interrupted():测试currentThread()是否已经中断。
- 2)public boolean this.isInterrupted():测试this关键字所在类的对象是否已经中断。
这两个方法有什么区别呢?
1、interrupted()
public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { System.out.println("i=" + (i + 1)); } } }Run类代码:
public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); //Thread.currentThread().interrupt(); System.out.println("是否停止1?="+thread.interrupted()); System.out.println("是否停止2?="+thread.interrupted()); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } }输出结果:
i=699998 i=699999 是否停止1?false 是否停止2?false z-z-z-z-z---zzzz-z-z i=700000这里使用thread.interrupted()来判断线程是否终止,但是并没有终止。所以可以说明,这个interrupted()测试当前线程是否已经中断。这个“当前线程”是main,从未中断过,所以输出的结果是2个false。
接下来一个特殊例子,代码如下:
public class Demo02 { public static void main(String[] args) { System.out.println("当前线程名:"+Thread.currentThread().getName()); Thread.currentThread().interrupt();// 将线程中断 System.out.println("interrupted:"+Thread.interrupted()); System.out.println("interrupted:"+Thread.interrupted()); System.out.println("end"); } }输出结果:
当前线程名:main interrupted:true interrupted:false end为什么中断线程之后第一次查看为true,第二次查看为false呢?
接下来我们查看一下其源码注释
接下来我们来看一下isInterrupted()方法
2、isInterrupted()
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
Thread.currentThread().interrupt();
System.out.println("是否停止1?="+Thread.currentThread().isInterrupted());
System.out.println("是否停止2?="+Thread.currentThread().isInterrupted());
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
输出结果:
i=100000 是否停止1?=true 是否停止2?=true end!
所以中上所述:
异常法停止线程1)this.interrupted():测试当前线程是否已经是中断状态,执行后具有清除状态标志值为false的功能。
2)this.isInterrupted():测试线程Thread对象是否已经是中断状态,不清除状态标志。
测试代码:
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
}
public class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 500000; i++) {
if (this.interrupted()) {
System.out.println("已经是停止状态了!我要退出了!");
throw new InterruptedException();
}
System.out.println("i=" + (i + 1));
}
System.out.println("我在for下面");
} catch (InterruptedException e) {
System.out.println("进MyThread.java类run方法中的catch了!");
e.printStackTrace();
}
}
}
运行结果:
在sleep下停止线程由程序运行结果可以看出,线程终于被正确停止了。这种方式就是以前介绍的第三种停止线程的方法——使用interrupt()方法中断线程,然后抛出异常进行彻底中断法。
当线程在sleep下面,被停止线程是什么样的呢?
首先我们来看看代码:
public class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
System.out.println("在沉睡中被停止!进入catch!"+this.isInterrupted());
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(200);
thread.interrupt();
} catch (InterruptedException e) {
System.out.println("main catch");
e.printStackTrace();
}
System.out.println("end!");
}
}
运行结果:
从运行结果来看呢,说明了一个问题,如果线程在sleep状态下停止,则该线程会进入catch语句,并且清除停止状态值,变成false。
还有当前我们发现当先调用Interrupt()再调用sleep()时:
public class MyThread extends Thread {
@Override
public void run() {
super.run();
try {
for(int i=0;i<100000;i++){
System.out.println("i="+(i+1));
}
System.out.println("run begin");
Thread.sleep(200000);
System.out.println("run end");
} catch (InterruptedException e) {
System.out.println("先停止,再遇到了sleep!进入catch!");
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
thread.interrupt();
System.out.println("end!");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HcB4SFze-1637378129324)(E:/typora%E7%AC%94%E8%AE%B0/typoraImg/image-20211120105453678.png)]
使用暴力法stop()停止线程结论:
不管其调用的顺序,只要interrupt()和sleep()方法碰到一起就会出现异常:
1)在sleep状态执行interrupt()方法会出现异常;
2)调用interrupt()方法给线程打了中断的标记,再执行sleep()方法也会出现异常。
使用stop()方法可以强行停止线程,即暴力停止线程。
package com.xiao.java_base.threads.thread07;
public class MyThread extends Thread{
private int i = 0;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("i=" + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.xiao.java_base.threads.thread07;
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(8000);
thread.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
效果:
使用return ;停止线程小结:
由运行结果可以看出,线程被暴力停止了,stop()方法呈删除线程状态,是不再被采用的方法,原因是stop()方法容易造成业务处理的不确定性。
将interrupt()方法与“return;”语句结合使用也能实现停止线程的效果。
public class MyThread extends Thread {
@Override
public void run() {
while (true) {
if (this.isInterrupted()) {
System.out.println("停止了!");
return;
}
System.out.println("timer=" + System.currentTimeMillis());
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread t=new MyThread();
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
运行结果:
timer=1123123144556 timer=1123123144557 timer=1123123144558 timer=1123123144559 停止了
但是我们更希望能使用抛异常法,这样能够让日志统一处理,return方法虽然简单,但是不便于后期的处理
public class MyThread extends Thread {
@Override
public void run() {
// insert操作
if (this.interrupted()) {
System.out.println("写入log info");
return;
}
// update操作
if (this.interrupted()) {
System.out.println("写入log info");
return;
}
// delete操作
if (this.interrupted()) {
System.out.println("写入log info");
return;
}
// select操作
if (this.interrupted()) {
System.out.println("写入log info");
return;
}
System.out.println("for for for for for");
}
}
将return修改为抛异常
public class MyThread2 extends Thread {
@Override
public void run() {
try {
// insert操作
if (this.interrupted()) {
throw new InterruptedException();
}
// update操作
if (this.interrupted()) {
throw new InterruptedException();
}
// delete操作
if (this.interrupted()) {
throw new InterruptedException();
}
// select操作
if (this.interrupted()) {
throw new InterruptedException();
}
System.out.println("for for for for for");
} catch (InterruptedException e) {
System.out.println("写入log info");
e.printStackTrace();
}
}
}



