Java线程创建与Thread类的使用方法

目录
  • 1.线程与Thread类
    • 1.1操作系统中的线程与Java线程
      • 1.1.1线程与Thread类
      • 1.1.2Thread类的构造方法
      • 1.1.3启用java线程必会的方法
    • 1.2第一个Java多线程程序
    • 1.3使用Runnable对象创建线程
    • 1.4使用内部类创建线程
    • 1.5使用Lambda表达式创建线程
    • 1.6多线程并发执行简单演示
    • 1.7多线程并发执行的优势
  • 2.Thread类的常用属性与方法
    • 2.1Thread类中的重要属性
    • 2.2Thread类中常用方法总结
      • 2.2.1常用方法
      • 2.2.2中断线程
      • 2.2.3线程等待
      • 2.2.4start方法与run方法的区别
  • 3.Java线程的状态
    • 3.1java线程中的基本状态
    • 3.2线程状态转移

1.线程与Thread类

1.1操作系统中的线程与Java线程

1.1.1线程与Thread类

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装. 也就是说Thread类的一个实例就对应着一个线程。

1.1.2Thread类的构造方法

序号 方法名 解释
1 public Thread() 无参数构造方法
2 public Thread(Runnable target) 传入实现Runnable接口的对象(任务对象)构造线程
3 public Thread(Runnable target, String name) 根据目标任务并指定线程名创建线程
4 public Thread(ThreadGroup group, Runnable target) 根据线程组和任务创建线程(了解)
5 public Thread(ThreadGroup group, Runnable target, String name) 比构造方法4多一个指定线程名
6 public Thread(String name) 指定线程名创建线程
7 public Thread(ThreadGroup group, String name) 根据线程组并指定线程名创建线程
8 public Thread(ThreadGroup group, Runnable target, String name,long stackSize) 构造函数与构造方法5相同,只是它允许指定线程堆栈大小

 注:线程可以被用来分组管理,分好的组即为线程组,Runnable类表示任务类,也就是线程需执行的任务。

1.1.3启用java线程必会的方法

想要使用java线程至少得知道Thread类中这几个方法:

方法名 解释
public void run() 该方法用来封装线程运行时执行的内容
public synchronized void start() 线程创建并执行run方法
public static native void sleep(long millis) throws InterruptedException 使线程休眠millis毫秒

创建Thread对象,必须重写run方法,因为你创建一个线程肯定要用运行一些代码嘛。

1.2第一个Java多线程程序

首先,我们可以创建一个MyThread类继承Thread类,并重写run方法。

class MyThread extends Thread{
    //重写run方法
    @Override
    public void run() {
        System.out.println("你好!线程!");
    }
}
public class TestDemo {
    public static void main(String[] args) {
        //创建MyThread线程对象,但是线程没有创建
        Thread thread = new MyThread();
        //线程创建并运行
        thread.start();
    }
}

使用new创建线程对象,线程并没有被创建,仅仅只是单纯地创建了一个线程对象,运行start方法时才会创建线程并执行run方法。

运行结果:

1.3使用Runnable对象创建线程

除了使用子类继承Thread类并重写run方法,使用子类实现Runnable接口(该接口中也有一个run方法,表示任务的内容),该对象可以理解为“任务”,也就是说Thread对象可以接受Runnable引用,并执行Runnable引用的run方法。

因为Runable是一个接口,所以需要实现run方法,线程Thread对象创建好后,此时线程并没有创建运行,需要调用start方法来创建启动线程。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("使用Runnable描述任务!");
    }
}

public class TestDemo3 {
    public static void main(String[] args) {
        //将Runnable任务传给Thread对象来创建运行线程
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);

        thread.start();
    }
}

运行结果:

根据“低内聚,高耦合”的

编程风格,使用Runnable的方式创建更优。

1.4使用内部类创建线程

当然也可以使用匿名内部类,来传入匿名对象来重写run方法。

public class TestDemo4 {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("使用匿名内部类创建线程匿名对象");
            }
        };
        thread.start();
    }
}

运行结果:

1.5使用Lambda表达式创建线程

使用Lambda表达式,本质还是使用匿名内部类创建的Thread

public class TestDemo6 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("使用Lambda表达式表示匿名内部类来创建匿名任务"));
        thread.start();
    }
}

运行结果:

1.6多线程并发执行简单演示

在一个进程中至少会有一个线程,如果不使用多线程编程,一个进程中默认会有执行main方法的main线程(该线程是自动创建的),当你创建一个新的线程t,该线程会与main线程并发执行。

public class TestDemo7 {
    public static void main(String[] args) {
        //thread 线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("thread线程执行中!");
                    //为了使效果更加明显 可以使用sleep方法设定线程睡眠时间
                    try {
                        Thread.sleep(1000);//每执行一次循环就睡眠1秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();

        //main 线程
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程执行中!");
            //为了使效果更加明显 可以使用sleep方法设定线程睡眠时间
            try {
                Thread.sleep(1000);//每执行一次循环就睡眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

从上面的运行结果可以看出一个问题,因为thread线程与main线程都是每打印一句语句线程休眠1秒,两个线程唤醒的先后顺序是随机的,这也是java多线程中的一个“万恶之源”,这个问题给我们带来了很多麻烦,细节等后续的博客细说。

1.7多线程并发执行的优势

加入我们现在有一个任务,就是分别将ab两个变量都自增20亿次,我们来看看使用两个线程和单独使用一个线程分别所需的时间是多少。

public class Test {
    private static final long COUNT = 20_0000_0000L;
    //两个线程
    public static void many() throws InterruptedException {
        //获取开始执行时间戳
        long start = System.currentTimeMillis();

        Thread thread1 = new Thread(() -> {
            long a = 0;
            for (long i = 0; i < COUNT; i++) {
                a++;
            }
        });
        thread1.start();
        Thread thread2 = new Thread(() -> {
            long b = 0;
            for (long i = 0; i < COUNT; i++) {
                b++;
            }
        });
        thread2.start();

        //等待两个线程结束 再获取结束时的时间戳
        thread1.join();
        thread2.join();
        long end = System.currentTimeMillis();

        //执行时间,单位为毫秒
        System.out.println("多线程执行时间:" + (end - start) + "ms");
    }
    //单线程
    public static void single() {
        //记录开始执行的时间戳
        long start = System.currentTimeMillis();

        long a = 0;
        for (long i = 0; i < COUNT; i++) {
            a++;
        }
        long b = 0;
        for (long i = 0; i < COUNT; i++) {
            b++;
        }
        //获取执行结束时的时间戳
        long end = System.currentTimeMillis();
        System.out.println("单线程执行时间:" + (end - start) + "ms");
    }

    public static void main(String[] args) throws InterruptedException {
        //多线程
        many();
        //单线程
        single();
    }
}

我们来看看完成这个任务所需的时间:

根据结果我们发现两个线程并发执行的时间大约是500ms左右,单线程执行的时间大约是1000ms左右,当然如果任务量不够大,可能多线程相比于单线程并不会有优势,毕竟创建线程本身还是有开销的。

2.Thread类的常用属性与方法

2.1Thread类中的重要属性

属性 获取该属性的方法
线程的唯一标识ID public long getId()
线程的名称name public final String getName()
线程的状态state public State getState()
线程的优先级priority public final int getPriority()
线程是否后台线程 public final boolean isDaemon()
线程是否存活 public final native boolean isAlive()
线程是否中断 public boolean isInterrupted()

每一个线程都拥有一个id作为标识,其中处于同一进程的所有线程id相同,每个进程间都有唯一的id标识。
线程也是拥有名字的,如果我们创建Thread对象时,没有指定线程对象的名称,则会默认命名为Thread-i,其中i为整数。

通过了解进程,我们知道进程拥有3种状态,分别为阻塞,执行和就绪。而java中的线程也有类似与这种状态的定义,后面我们细说,优先级也一样就不用多说了。

java线程分为后台线程与前台线程,其中后台线程不会影响到进程的退出,而前台线程会影响进程的退出,比如有线程t1与线程t2,当这两个线程为前台线程时,main方法执行完毕时,t1t2不会立即退出,要等到线程执行完毕,整个进程才会退出,反之,当这两个线程为后台线程时,main方法执行完毕时,t1t2线程被强制结束,整个进程也就结束了。

关于java线程的属性,我们可以通过java官方的jconsole调试工具查看java线程的一些属性。 这个工具一般在jdk的bin目录,

双击打开有如下界面:

选择需要查看的线程并查看:

选择你需要查看的进程属性:

2.2Thread类中常用方法总结

2.2.1常用方法

方法名 解释
public void run() 该方法用来封装线程运行时执行的内容
public synchronized void start() 线程创建并执行run方法
public static native void sleep(long millis) throws InterruptedException 使线程休眠millis毫秒
public final void join() throws InterruptedException 等待线程结束(在哪个线程中调用哪个对象的join方法,哪个线程就等待哪个对象)
public final synchronized void join(long millis) throws InterruptedException 等待线程结束,最多等待millis毫秒
public final synchronized void join(long millis, int nanos) throws InterruptedException 指定最多等待时间等待线程,精确到纳秒
public void interrupt() 中断线程对象所关联的对象,如果线程在休眠(阻塞状态)会抛出异常通知,否则设置中断标志位
public static boolean interrupted() 判断当前线程的中断标志位是否设置,调用后会清除线程的中断标志位
public boolean isInterrupted() 判断当前线程的中断标志位是否设置,调用后不会影响线程的标志位
public final synchronized void setName(String name) 修改线程对象名称
public static native Thread currentThread() 获取当前线程对象

2.2.2中断线程

如果我们想中断一个正在执行的线程,该如何做呢?最简单但不严谨的方法就是我们在run方法中定义一个中断标志位(需要中断时标志位为true,默认情况为false),每次执行具体任务时需要先判断中断标志位是否为true,如果是就结束线程,否则继续执行。

public class TestDemo8 {
    private static boolean isQuit = false;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(!isQuit) {
                //每隔1秒打印一句
                System.out.println("一个不起眼的线程!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        //main线程阻塞5秒 按理会打印5句话
        Thread.sleep(5000);
        isQuit = true;
    }
}

运行结果:

但是该方法是不够严谨的,有些场景可能达不到预期的效果,最优的做法就是调整线程对象或者线程类中的自带标志位。

方式1:使用Thread对象中的标志位首先使用 currentThread方法获取线程对象,然后再调用该对象中的isterrupted方法获取该对象的中断标志位代替我们自己所写的isQuit标志位,然后等该线程运行一段时间后使用interrupt方法改变标志位,中断线程,写出如下代码,看看能不能达到预期效果:

public class TestDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() ->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("又是一个不起眼的线程!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        //main休眠5秒
        Thread.sleep(5000);

        //使用interrupt方法修改线程标志位,使其中断
        thread.interrupt();
    }
}

我们来看一看:

失败了,抛出一个InterruptedException异常后,线程没有中断,而是继续运行,原因是interrupt方法遇到因为调用 wait/join/sleep 等方法而阻塞的线程时会使sleep等方法抛出异常,并且中断标志位不会修改为true,这时我们的catch语句里面值输出了异常信息并没有去中断异常,所以我们需要在catch语句中加上线程结束的收尾工作代码和退出任务循环的break语句就可以了。

public class TestDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() ->{
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("又是一个不起眼的线程!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //收尾工作
                    System.out.println("收尾工作!");
                    break;
                }
            }
        });
        thread.start();

        //main休眠5秒
        Thread.sleep(5000);

        //使用interrupt方法修改线程标志位,使其中断
        thread.interrupt();
    }
}

运行结果:

方式2:使用Thread类中的标志位除了isInterrupted,还有一个静态方法interrupted能够访问类中的标志位,一般一个程序中只有一个,我们也可以使用该静态方法来作为中断标志位,然后到时机后使用interrupt方法来中断线程执行。

public class TestDemo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (!Thread.interrupted()) {
                System.out.println("又又是一个不起眼的线程!");
                try {
                	//设置打印频率为1s
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //收尾工作
                    System.out.println("收尾工作!");
                    break;
                }
            }
        });
        thread.start();

        //main休眠5秒
        Thread.sleep(5000);

        //使用interrupt方法修改线程标志位,使其中断
        thread.interrupt();
    }
}

运行结果:

综上所述,一般以方式1的方式无脑中断线程就可以。

2.2.3线程等待

像上面的计算自增20亿次的例子就需要线程等待join方法,main线程需要等两个线程运行完毕后才能计算计算结束时的时间戳。
针对这一点java还准备了带参数的join方法,可以指定最长的等待时间。
还有一个细节那join方法是谁等谁呢?
我们来假设几个线程,线程A表示调用join方法的线程,线程B表示join方法来自B线程对象,那么在A线程使用B.join方法,那就是A线程等待B线程结束。

2.2.4start方法与run方法的区别

我们知道执行一个线程的任务就是线程对象中所重写的run方法,那么可以直接调用run方法来代替start方法吗?

当然不行!因为你调用run方法就是单纯地调用了Thread对象中的一个普通方法而已,并没有创建一个新线程来执行run方法,而是通过main线程来执行的run方法,而使用start方法,会创建一个新线程并执行run方法。

3.Java线程的状态

3.1java线程中的基本状态

操作系统中进程的状态有三种分别为阻塞,就绪和执行,而java线程中的状态基本上相同,但做了细分,有一点区别,我们来认识一下。

NEW: 安排了工作, 还未开始行动,就是线程对象存在,但没有执行start方法,java内部的状态,与进程中的状态无关。
RUNNABLE: 就绪状态。
BLOCKED: 线程正在等待锁释放而引起的阻塞状态(synchronized加锁)。
WAITING: 线程正在等待等待唤醒而引起的阻塞状态(waitf方法使线程等待唤醒)。
TIMED_WAITING: 在一段时间内处于阻塞状态,通常是使用sleep或者join(带参数)方法引起。
TERMINATED:Thread对象还存在,但是关联的线程已经工作完成了,java内部的状态,与进程中的状态无关。

3.2线程状态转移

我先使用一个流程图来简要说明状态之间的关系:

上面这个图简单地说明了这几种状态之间的转移,关于图中的wait以及synchronized关键字会在讨论线程安全问题时介绍。

这期的内容分享了有关线程创建执行以及有关Thread类中的基本方法,下期继续介绍多线程更深入的知识,比如线程安全问题,如何加锁等更深一点的内容。

到此这篇关于Java线程创建与Thread类的使用方法的文章就介绍到这了,更多相关Java线程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java继承Thread类创建线程类示例

    本文实例讲述了Java继承Thread类创建线程类.分享给大家供大家参考,具体如下: 一 点睛 通过继承Thread类创建线程并启动多线程的步骤: 1 定义Thread的子类,并重写该类的run()方法,该run()方法的方法体代表了线程需要完成的任务.因此run()方法称为线程执行体. 2 创建Thread子类的实例,即创建子线程对象. 3 调用线程对象的start()方法来启动该线程. 二 代码 // 通过继承Thread类来创建线程类 public class FirstThread ex

  • java线程之用Thread类创建线程的方法

    在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread类的构造方法被重载了八次,构造方法如下: 复制代码 代码如下: public Thread( ); public Thread(Runnable target); public Thread(String name); public Thread

  • Java创建线程池为什么一定要用ThreadPoolExecutor

    目录 先说结论 OOM风险演示 内存溢出原因分析 使用ThreadPoolExecutor来改进 其他创建线程池的问题 总结 前言: 在 Java 语言中,并发编程都是依靠线程池完成的,而线程池的创建方式又有很多,但从大的分类来说,线程池的创建总共分为两大类:手动方式使用ThreadPoolExecutor创建线程池和使用 Executors 执行器自动创建线程池. 那究竟要使用哪种方式来创建线程池呢?我们今天就来详细的聊一聊. 先说结论 在 Java 语言中,一定要使用 ThreadPoolE

  • java多线程编程之使用thread类创建线程

    在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread类的构造方法被重载了八次,构造方法如下: 复制代码 代码如下: public Thread( );public Thread(Runnable target);public Thread(String name);public Thread(Ru

  • Java使用Thread创建多线程并启动操作示例

    本文实例讲述了Java使用Thread创建多线程并启动操作.分享给大家供大家参考,具体如下: 按照教程实现了一个单线程的创建,但是单线程的创建于启动并不是很有实用价值的.毕竟直接在main方法中放着相关的执行操作本身也就是一种单线程的实现.接下来在之前用过的代码基础上稍作修改,形成如下代码: class ThreadDemo extends Thread { ThreadDemo(){}; ThreadDemo(String szName) { super(szName); } public v

  • Java线程创建与Thread类的使用方法

    目录 1.线程与Thread类 1.1操作系统中的线程与Java线程 1.1.1线程与Thread类 1.1.2Thread类的构造方法 1.1.3启用java线程必会的方法 1.2第一个Java多线程程序 1.3使用Runnable对象创建线程 1.4使用内部类创建线程 1.5使用Lambda表达式创建线程 1.6多线程并发执行简单演示 1.7多线程并发执行的优势 2.Thread类的常用属性与方法 2.1Thread类中的重要属性 2.2Thread类中常用方法总结 2.2.1常用方法 2.

  • Java线程编程中Thread类的基础学习教程

    一.线程的状态 在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解. 线程从创建到最终的消亡,要经历若干个状态.一般来说,线程包括以下这几个状态:创建(new).就绪(runnable).运行(running).阻塞(blocked).time waiting.waiting.消亡(dead). 当需要新起一个线程来执行某个子任务时,就创建了一个线程.但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内

  • java 线程创建多线程详解

    Java 线程类也是一个 object 类,它的实例都继承自 java.lang.Thread 或其子类. 可以用如下方式用 java 中创建一个线程,执行该线程可以调用该线程的 start()方法: Tread thread = new Thread(); thread.start(); 在上面的例子中,我们并没有为线程编写运行代码,因此调用该方法后线程就终止了. 编写线程运行时执行的代码有两种方式:一种是创建 Thread 子类的一个实例并重写 run 方法,第二种是创建类的时候实现 Run

  • Java线程创建静态代理模式代码实例

    一.背景 在通过Runnable接口创建线程时,启动线程需要借助Thread类,这里就涉及到了静态代理模式. 二.实例 以歌手演出为例,在演出的这个过程中,歌手与他的助理他们有一个共同的目标"完成这场演出". 为啥需要歌手需要有助理呢? 因为举办好一场演出有很多繁琐的事情要做,为了让歌手专心完成"唱歌"这件事,助理就需要在背后帮助歌手做很多事情. 1.助理负责帮助歌手做一些辅助工作,例如帮忙宣传.帮忙计划行程.帮忙订机票等等. 2.歌手负责唱歌这件事情. 三.实例的

  • Java线程创建(卖票),线程同步(卖包子)的实现示例

    1.线程两种创建方式:new Thread(new Runnable() {}) 如下FileOutputStream源码中抛出异常,为了让写代码人自己写try catch异常提示信息. package com.itheim07.thread; /* * 进程和线程 * 1. 进程 : 航空母舰(资源: 燃油 弹药) * 2. 线程 : 舰载机 * 一个软件运行: 一个军事活动, 必须有一艘航母出去,但执行具体任务的是航母上的舰载机 * 一个软件运行,至少一个进程, 一个进程中至少一个线程.谷歌

  • Java线程的并发工具类实现原理解析

    目录 一.fork/join 1. Fork-Join原理 2. 工作窃取 3. 代码实现 二.CountDownLatch 三.CyclicBarrier 四.Semaphore 五.Exchange 六.Callable.Future.FutureTask 在JDK的并发包里提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程间交换数据的一种手段.本章会配合一些应

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

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

  • Java线程安全的常用类_动力节点Java学院整理

    线程安全类 在集合框架中,有些类是线程安全的,这些都是jdk1.1中的出现的.在jdk1.2之后,就出现许许多多非线程安全的类. 下面是这些线程安全的同步的类: vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用.在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的. statck:堆栈类,先进后出 hashtable:就比hashmap多了个线程安全 除了这些之外,其他的集合大都是非线程安全的类和接口. 线程安全的类其方法是同步

  • Java实现创建运行时类的对象操作示例

    本文实例讲述了Java实现创建运行时类的对象操作.分享给大家供大家参考,具体如下: 获取运行时类的方法: public void test() throws ClassNotFoundException { /* * Class类是反射的源头 * 创建一个类,通过编译(javac.exe),生成对应的.class文件,之后使用java.exe加载(JVM的类加载器完成的)此.class文件. * 此.class文件加载到内存后,就是一个运行时类,存放在缓存区. * 那么这个运行时类本身就是一个C

随机推荐