thread类能运行线程的方法,java thread的使用

  thread类能运行线程的方法,java thread的使用

  00-1010 1.线程和线程类1.1操作系统中的线程和java线程1.1.1线程和线程类1.1.2线程类的构造方法1.1.3启用Java线程的必要方法1.2第一个Java多线程程序1.3用Runnable对象创建线程1.4用内部类创建线程1.5用Lambda表达式创建线程1.6多线程并发执行简单演示1.7多线程并发执行的优点2.1线程类的常见属性和方法2.1线程类中的重要属性2.2 线程类中常用方法总结2.2.2中断线程2.2.3线程等待2 . 2 . 4 start方法和run方法的区别3.1java线程状态3.1 Java线程中的基本状态2.2线程状态转换

  

目录

  

1.线程与Thread类

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

  00-1010序列号方法名称解释1public Thread()无参数构造方法2public Thread(Runnable target)传入对象(task object)实现Runnable接口构造线程3public Thread(Runnable target,String)创建一个线程4 public thread (thread group group,Runnable target)根据目标任务并指定线程名称。创建一个线程(know) 5公共线程(线程组group,runnable target,String name)比构造方法4多一个。指定线程名称6公共线程(字符串名称)指定创建线程的线程名称7公共线程(线程组组,字符串)创建线程8公共线程(线程组组,可运行目标,字符串名称,长栈大小)根据线程组并指定线程名称。该构造函数与构造函数5相同,它只允许对具有指定线程堆栈大小的注:线程进行分组管理。划分好的组就是线程组,Runnable类代表任务类,就是线程需要执行的任务。

  

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

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

  方法的名字解释了public void run()。该方法用于封装线程在运行时执行的内容。线程创建并执行run方法public static native void sleep(长毫秒)。Throws InterruptedException使线程休眠毫秒。要创建线程对象,必须重写run方法,因为必须运行一些代码来创建线程。

  00-1010首先,我们可以创建一个MyThread类来继承Thread类并覆盖run方法。

  Classthread扩展thread {//Override run方法@ Override public void run(){ system . out . println( Hello!“线程!”);}}公共类测试演示{ public static void main(string[]args){//创建MyThread线程对象,但线程没有创建Thread Thread=new MyThread();//Thread创建并运行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类中的重要属性

属性获取该属性的方法线程的唯一标识IDpublic long getId()线程的名称namepublic final String getName()线程的状态statepublic State getState()线程的优先级prioritypublic 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线程内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: