栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA高级(三)——多线程(1)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA高级(三)——多线程(1)

JAVA高级(三)--多线程(1)
    • 概述
      • 为什么要多线程呢?
      • 如何实现多线程呢?
        • 继承Thread类
        • 实现Runable接口
          • 实现Runnable接口的调用过程
      • 如何使用线程呢?
        • 解决由于共享变量造成的安全问题
        • currentThread()方法
        • isAlive()方法
        • sleep(long millis)方法
      • 停止线程
        • interrupt()停止线程
          • 判断线程是否停止状态
        • 异常法停止线程
        • 在sleep下停止线程
        • 使用暴力法stop()停止线程
        • 使用return ;停止线程

概述 为什么要多线程呢?

多线程能够让数据同时进行处理,比如原来一个线程处理数据,但是现在我有100个线程处理,那么现在的速率是不是>=100倍?

如何实现多线程呢? 继承Thread类

继承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在操作系统中被成功执行。

这里可以总结一下:

调用start()不代表执行了run()

实现Runable接口

实现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();
    }
}

小结:

1、当类是单继承的时候可以直接使用Aclass extend Thread;

2、当类需要实现多继承的时候就需要使用到 Aclass implements Runnable;

实现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

执行方法run()和start()还是有一些区别的。
1)my.run():立即执行run()方法,不启动新的线程。
2)my.start():执行run()方法时机不确定,启动新的线程。

isAlive()方法

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:true
sleep(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相关的信息。

停止线程

由于多线程的出现,给研发人员带来了许多的挑战,如果不正确的处理停止线程后所带来的问题,那么会产生一些不可避免的影响,那么接下来我们来讲述如何停止线程;

在Java中有3种方法可以使正在运行的线程终止运行:
1) 使用退出标志使线程正常退出。
2)使用stop()方法强行终止线程,但是这个方法不推荐使用,因为stop()和suspend()、resume()一样,都是作废过期的方法,使用它们可能发生不可预料的结果。
3)使用interrupt()方法中断线程。

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();
        }

    }
}

运行结果:

由程序运行结果可以看出,线程终于被正确停止了。这种方式就是以前介绍的第三种停止线程的方法——使用interrupt()方法中断线程,然后抛出异常进行彻底中断法。

在sleep下停止线程

当线程在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)]

结论:

不管其调用的顺序,只要interrupt()和sleep()方法碰到一起就会出现异常:
1)在sleep状态执行interrupt()方法会出现异常;
2)调用interrupt()方法给线程打了中断的标记,再执行sleep()方法也会出现异常。

使用暴力法stop()停止线程

使用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();
        }
    }
}

效果:

小结:

由运行结果可以看出,线程被暴力停止了,stop()方法呈删除线程状态,是不再被采用的方法,原因是stop()方法容易造成业务处理的不确定性。

使用return ;停止线程

将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();
        }
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/571737.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号