详解Java创建线程的五种常见方式

目录
  • Java中如何创建线程呢?
    • 1.显示继承Thread,重写run来指定现成的执行代码。
    • 2.匿名内部类继承Thread,重写run来执行线程执行的代码。
    • 3.显示实现Runnable接口,重写run方法。
    • 4.匿名内部类实现Runnable接口,重写run方法
    • 5.通过lambda表达式来描述线程执行的代码
  • 【面试题】:Thread的run和start之间的区别?
  • Thread类的具体用法
  • Thread类常见的一些属性
  • 中断一个线程
    • 1.方法一:让线程run完
    • 2.方法二:调用interrupted()方法
  • 线程等待
  • 线程休眠
  • 线程的状态转换

Java中如何进行多线程编程,如何使用多线程?在Java标准库中提供了一个Thread类。Java中,一个进程正在运行时至少会有一个线程正在运行,这些线程在后台默默地执行,比如调用main()方法时就是这样的,主线程是由JVM创建的。实现多线程编程的方式主要有两种,一是继承Thread类,另一种是实现Runnable接口。这里我们先来看看Thread类的结构:

从源代码中可以发现,Thread类实现了Runnable接口,它们之间具有多态关系。其实,使用继承Thread类的方式创建新线程是,最大的局限就是不支持多继承,因为在Java语言特点就是单继承,所以为了支持多继承,完全可以实现Runnable接口的方式。

Java中如何创建线程呢?

1.显示继承Thread,重写run来指定现成的执行代码。

代码

public class Demo1 {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("hello world, 我是一个线程");
            while (true) {

            }
        }
    }

    public static void main(String[] args) {
        // 创建线程需要使用 Thread 类, 来创建一个 Thread 的实例.
        // 另一方面还需要给这个线程指定, 要执行哪些指令/代码.
        // 指定指令的方式有很多种方式, 此处先用一种简单的, 直接继承 Thread 类,
        // 重写 Thread 类中的 run 方法.

        // [注意!] 当 Thread 对象被创建出来的时候, 内核中并没有随之产生一个线程(PCB).
        Thread t = new MyThread();
        t.start();
        // 执行这个 start 方法, 才是真的创建出了一个线程.
        // 此时内核中才随之出现了一个 PCB, 这个 PCB 就会对应让 CPU 来执行该线程的代码. (上面的 run 方法中的逻辑)
        

        while (true) {
            // 这里啥都不干
        }
    }
}

2.匿名内部类继承Thread,重写run来执行线程执行的代码。

代码

public class Demo2 {
    // Runnable 本质上就是描述了一段要执行的任务代码是啥.
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我是一个新线程");
        }
    }

    public static void main(String[] args) {
        // 2. 通过匿名内部类的方式继承 Thread
        Thread t = new Thread() {
            @Override
            public void run() {

            }
        };
        t.start();
   }

3.显示实现Runnable接口,重写run方法。

代码

public class Demo3 {
    // Runnable 本质上就是描述了一段要执行的任务代码是啥.
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我是一个新线程");
        }
    }

    public static void main(String[] args) {
        // 3. 显式创建一个类, 实现 Runnable 接口, 然后把这个 Runnable 的实例关联到 Thread 实例上.
        Thread t = new Thread(new MyRunnable());
        t.start();
   }

4.匿名内部类实现Runnable接口,重写run方法

代码

public class Demo4 {
    // Runnable 本质上就是描述了一段要执行的任务代码是啥.
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我是一个新线程");
        }
    }

    public static void main(String[] args) {
        // 4. 通过匿名内部类来实现 Runnable 接口
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("我是一个新线程");
            }
        };
        Thread t = new Thread(runnable);
        t.start();
   }

5.通过lambda表达式来描述线程执行的代码

代码

public class Demo4 {
    // Runnable 本质上就是描述了一段要执行的任务代码是啥.
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("我是一个新线程");
        }
    }

    public static void main(String[] args) {
        // 5. 使用 lambda 表达式来指定 线程执行的内容
        Thread t = new Thread(() -> {
            System.out.println("我是一个新线程");
        });
        t.start();
   }

【面试题】:Thread的run和start之间的区别?

run()方法::普通的方法调用,没有创建新的线程,输出语句是在原线程中执行的。

start()方法::这才是创建了一个新线程,由新的线程来执行输出

Thread类的具体用法

Thread类常见的一些属性

ID是现成的唯一标识,不同线程不会重复

名称是各种调试工具会用到的

状态标识线程当前所处的一个情况

优先级高的线程理论上来说更容易被调度到

关于后台先后曾,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行

是否存活,即run方法是否运行结束了

线程的中断问题

我们通过编写具体的代码来观察方法的使用:

public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t=new Thread("cxk"){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        //run方法执行过程中就代表着系统内线程得生命周期
        //润方法执行中,内核的线程就存在
        //run方法执行完毕,内核中的线程随之销毁

        //这一组属性,只要线程创建完毕,属性就变了
        System.out.println(t.getName());
        System.out.println(t.getPriority());
        System.out.println(t.isDaemon());
        System.out.println(t.getId());
        //这俩属性会随着现成的运行过程而发生改变
        System.out.println(t.isAlive());
        System.out.println(t.isInterrupted());

        System.out.println(t.getState());
        t.start();

        while (t.isAlive()){
            System.out.println("cxk线程正在执行");
            System.out.println(t.isInterrupted());
            System.out.println(t.getState());
        }
    }
}

执行结果如下:会出现很多组相同的数据

中断一个线程

让一个线程结束有两种情况:

1.此线程已经把任务执行完了。即让线程run完(比较温和)。

2.此线程将任务执行到一半,被强制结束。即调用线程的interrupt()方法,比较激烈。

1.方法一:让线程run完

这种结束方式比较温和,当标记位被设置上之后,等到这次循环执行完了之后,在结束线程,如下,当线程执行到sleep的时候,已经sleep100ms了,此时isQuit被设置为true,当前线程不会立即退出,而是会继续sleep,把剩下的 400ms sleep完才会结束这个线程。

public class ThreadDemo7 {
    private static boolean isQuit=false;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
                while (!isQuit){
                    System.out.println("别烦我,我在忙着转账呢");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("转账操作被终止");
            }
        };
        t.start();

        Thread.sleep(500);
        //老板来电话了说对方是内鬼终止交易
        System.out.println("有内鬼,终止交易!!!");
        isQuit = true;
    }
}

执行结果:

2.方法二:调用interrupted()方法

public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            @Override
            public void run() {
                // 此处直接使用线程内部的标记位来判定.
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println("别管我, 我在忙着转账呢");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }
                System.out.println("转账被终止.");
            }
        };
        t.start();

        Thread.sleep(5000);
        System.out.println("对方是内鬼, 快终止交易!!!");
        t.interrupt();
    }
}

执行结果如下:

在这段代码中,t.start()是主线程继续往下执行之后,主线程还是会继续走,新线程则会执行run方法,如果没有后续的sleep,新线程能否继续输出就是不确定的了。原因:多线程之间是抢占实质性的,如果主线程中没有sleep,此时接下来CPU是执行主现成的isQuit=true还是新线程的while循环,这都是不确定的。对于新线程来说,run方法执行完,线程就结束了。对于主线程来说main方法执行完,住线程就结束了。

由上可得:

1.通过thread对象调用interrupt()方法通知该线程停止运行。

2.thread收到通知的方式有两种:

如果线程调用了wait/join/sleep等方法而阻塞挂起,则以InterrupterException异常的形式通知,清除中断标志

如果没有调用上述方式,就只是内部的一个中断标志被设置,thread可以通过Thread.interrupted()判断当前线程的中断标志被设置,来清除中断标志。也可以

使用Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,但是不会清除中断标志。

在Java中第二种方式通知收到的更及时,即使线程正在sleep也可以马上收到。

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().isInterrupted());
                    //仅仅是判定标记位,不会修改标记位
                }
            }
        };
        t.start();
        t.interrupt();
    }
}
public class ThreadDemo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
                System.out.println("我是新线程");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
        while (true){
            System.out.println("我是主线程");
            Thread.sleep(1000);
            //对于新线程来说,run方法执行完新线程就结束了,
            //对于主线程来说,main方法执行完主线程就结束了
        }
    }
}

线程等待

线程之间是并发执行的关系,多个线程之间,谁先执行,谁后执行,谁执行到何处让出CPU…开发人员是完全无法感知的,全权由系统内核负责,例如,创建一个新线程的时候,此时接下来是主线程继续执行,还是新线程执行,这个事情是不能保证的,这就是“抢占式”执行的重要特点。这时候就引入了线程等待:开发人员可以控制哪个线程先结束,哪个线程后结束。join()方法的执行就会让线程阻塞,一直阻塞到对应线程执行结束之后,才会继续执行。这就可以控制线程结束的先后顺序。如果线程结束了才调用到join,此时也会立刻返回。

public class ThreadDemo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("我是线程1");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("我是线程2");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();// join 起到的效果是等待线程结束. 当执行到这行代码是, 程序就阻塞了. 一直阻塞到 t1 结束, 才会继续执行.
        t2.join();
        System.out.println("主线程执行完毕");
    }
}

执行结果如下:

线程休眠

当线程在正常运行计算判断逻辑此时就是在就绪队列中排队,调度器就会从就绪队列中筛选出合适的PCB让他上CPU执行,如果某个线程调用sleep就会让对应的线程PCB进入到阻塞队列,线程一旦进入到了阻塞队列是没有办法上CPU执行的,对于sleep进入冷宫的时间是有限制的,时间到了之后,就自动被系统把这个PCB那回到原来的就绪队列中了。

线程的状态转换

以上就是详解Java创建线程的五种常见方式的详细内容,更多关于Java创建线程的资料请关注我们其它相关文章!

(0)

相关推荐

  • java实现/创建线程的几种方式小结

    进程与线程 进程可以简单理解成一个可执行程序例如.exe,在Windows中的任务管理器中可以查看每一个进程,进程是一次程序的执行,是程序在数据集合上运行的过程,是系统资源调度的一个单位.进程主要负责向操作系统申请资源.然而一个进程中,多个线程可以共享进程中相同的内存或文件资源.线程就是一个进程一个程序要完成所依赖的子任务,这些子任务便可以看作是一个线程. 第一种方式继承Thread类 从java源码可以看出Thread类本质上实现了Runnable接口的实例类,代表了线程的一个线程的实例,启动

  • Java创建多线程的8种方式集合

    目录 1.继承Thread类,重写run()方法 2.实现Runnable接口,重写run() 3.匿名内部类的方式 4.带返回值的线程(实现implements Callable<返回值类型>) 5.定时器(java.util.Timer) 6.线程池的实现(java.util.concurrent.Executor接口) 7.Lambda表达式的实现(parallelStream) 8.Spring实现多线程 1.继承Thread类,重写run()方法 //方式1 package cn.i

  • Java线程创建的四种方式总结

    多线程的创建,方式一:继承于Thread类 1.创建一个继承于Thread类的子类 2.重写Thread类的run()--->将此线程执行的操作声明在run()中 3.创建Thread类的子类的对象 4.通过此对象调用start(): start()方法的两个作用: A.启动当前线程 B.调用当前线程的run() 创建过程中的两个问题: 问题一:我们不能通过直接调用run()的方式启动线程 问题二:在启动一个线程,遍历偶数,不可以让已经start()的线程去执行,会报异常:正确的方式是重新创建一

  • 聊聊java多线程创建方式及线程安全问题

    什么是线程 线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位.每个程序程序都至少有一个线程,也即是程序本身. 线程的状态 新建(New):创建后尚未启动的线程处于这种状态 运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间. 等待(Wating):处于这种状态的线程不会被分配CPU执行时间.等待状态又分为无限期等待和有限期等待,处于无

  • 关于Java创建线程的2种方式以及对比

    目录 1. 继承Thread类 2. 实现Runnable接口: 创建线程的两种方式对比: 线程的完整生命周期: 总结 Java中两种创建线程的方式: 1. 继承Thread类 重写run()方法 new一个线程对象 调用对象的start()启动线程 class Handler extends Thread{ public void run(){ //重写run()方法 } public static void main(String[] args){ Thread thread=new Han

  • Java线程的三种创建方式

    目录 1.Thread 2.Runnable和Thread 3.Runnable和Thread 4.三者对比 5.注意项 1.Thread 继承Thread类,并重写run方法 class ThreadDemo1 extends Thread { @Override public void run() { log.info("{}", Thread.currentThread().getName()); } } 线程启动方式: ThreadDemo1 t1 = new ThreadDe

  • 一篇文中细看Java多线程的创建方式

    前言 Java现在有四种创建的方式:继承Threa类.实现Runnable接口.实现Callable接口.线程池 Thread.Runnable都在java.lang包下:Callable.线程池都在java.util.concurrent包下 1.继承Thread类,重写run方法 创建一个类继承Thread类,并重写run():因为run()是线程具体执行的方法. 在测试类或者main()创建Thread对象,并调用start()启动线程 备注:start()是启动线程,run()是线程执行

  • 详解Java创建线程的五种常见方式

    目录 Java中如何创建线程呢? 1.显示继承Thread,重写run来指定现成的执行代码. 2.匿名内部类继承Thread,重写run来执行线程执行的代码. 3.显示实现Runnable接口,重写run方法. 4.匿名内部类实现Runnable接口,重写run方法 5.通过lambda表达式来描述线程执行的代码 [面试题]:Thread的run和start之间的区别? Thread类的具体用法 Thread类常见的一些属性 中断一个线程 1.方法一:让线程run完 2.方法二:调用interr

  • 一文搞懂Java创建线程的五种方法

    目录 题目描述 解题思路 代码详解 第一种 继承Thread类创建线程 第二种:实现Runnable接口创建线程 第三种:实现Callable接口,通过FutureTask包装器来创建Thread线程 第四种:使用ExecutorService.Callable(或者Runnable).Future实现返回结果的线程 第五种:使用ComletetableFuture类创建异步线程,且是据有返回结果的线程 题目描述 Java创建线程的几种方式 Java使用Thread类代表线程,所有线程对象都必须

  • 详解Java停止线程的四种方法

    一.线程停止基础知识 interrupted(): 测试当前线程是否已经中断.该方法为静态方法,调用后会返回boolean值.不过调用之后会改变线程的状态,如果是中断状态调用的,调用之后会清除线程的中断状态. isInterrupted(): 测试线程是否已经中断.该方法由对象调用 interrupt(): 标记线程为中断状态,不过不会中断正在运行的线程. stop(): 暴力停止线程.已弃用. 二.停止线程方法1:异常法停止 线程调用interrupt()方法后,在线程的run方法中判断当前对

  • Java创建线程的五种写法总结

    目录 通过继承Thread类并实现run方法创建一个线程 通过实现Runnable接口,并实现run方法的方法创建一个线程 通过Thread匿名内部类创建一个线程 通过Runnable匿名内部类创建一个线程 通过Lambda表达式的方式创建一个线程 通过继承Thread类并实现run方法创建一个线程 // 定义一个Thread类,相当于一个线程的模板 class MyThread01 extends Thread { // 重写run方法// run方法描述的是线程要执行的具体任务@Overri

  • 详解Java创建多线程的四种方式以及优缺点

    java有以下四种创建多线程的方式 1:继承Thread类创建线程 2:实现Runnable接口创建线程 3:使用Callable和FutureTask创建线程 4:使用线程池,例如用Executor框架创建线程 DEMO代码 package thread; import java.util.concurrent.*; public class ThreadTest { public static void main(String[] args) throws ExecutionExceptio

  • 详解Java合并数组的两种实现方式

    最近在写代码时遇到了需要合并两个数组的需求,突然发现以前没用过,于是研究了一下合并数组的方式,总结如下. 1.System.arraycopy()方法 (1) 解析 通过阅读JDK源码,我可以知道方法原型如下: public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); 其中: src是源数组 srcPos是源数组复制的起始位置 dest是目标数组 destP

  • 详解java动态代理的2种实现方式

    java的动态代理在接java的api上有说明,这里就不写了.我理解的代理: 对特定接口中特定方法的功能进行扩展,这就是代理.代理是通过代理实例关联的调用处理程序对象调用方法. 下面通过一个例子看一下: 接口: public interface Num { void show(); int getNum(); int getProduct(int x); } 实现类: public class MyNum implements Num { @Override public int getNum(

  • 详解Java子线程异常时主线程事务如何回滚

    一.提出问题 最近有一位朋友问了我这样一个问题,问题的截图如下: 这个问题问的相对比较笼统,我来稍微详细的描述下:主线程向线程池提交了一个任务,如果执行这个任务过程中发生了异常,如何让主线程捕获到该异常并且进行事务的回滚. 二.主线程与子线程 先来看看基础,下图体现了两种线程的运行方式, 左侧的图,体现了主线程启动一个子线程之后,二者互不干扰独立运行,生死有命,从此你我是路人! 右侧的图,体现了主线程启动一个子线程之后继续执行主线程程序逻辑,在某一节点通过阻塞的方式来获取子线程的执行结果. 对于

  • 详解Java内存溢出的几种情况

    JVM(Java虚拟机)是一个抽象的计算模型.就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域.目的是为构建在其上运行的应用程序提供一个运行环境.JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构. 1. 前言 JVM提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理,大部分情况下不会出现内存泄漏和内存溢出问题.但是基本不会出现并不等于不会出现,所以掌握Java内存模型原理和学会分析出现的内存溢出或内存泄漏,对于使用J

  • 详解java调用python的几种用法(看这篇就够了)

    java调用python的几种用法如下: 在java类中直接执行python语句 在java类中直接调用本地python脚本 使用Runtime.getRuntime()执行python脚本文件(推荐) 调用python脚本中的函数 准备工作: 创建maven工程,结构如下: 到官网https://www.jython.org/download.html下载Jython的jar包或者在maven的pom.xml文件中加入如下代码: <dependency> <groupId>org

随机推荐