Java线程基础

public static final void sleepSecond(long seconds) {

try {

TimeUnit.SECONDS.sleep(seconds);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

package com.lizba.p2;

/**

  •  线程状态示例代码
    
  • @Author: Liziba

  • @Date: 2021/6/14 13:25

*/

public class ThreadStateDemo {

public static void main(String[] args) {

// TimeWaiting

new Thread(new TimeWaiting(), “TimeWaitingThread”).start();

// Waiting

new Thread(new Waiting(), “WaitingThread”).start();

// Blocked1和Blocked2一个获取锁成功,一个获取失败

new Thread(new Blocked(), “Blocked1Thread”).start();

new Thread(new Blocked(), “Blocked2Thread”).start();

}

// 线程不断的进行睡眠

static class TimeWaiting implements Runnable {

@Override

public void run() {

while (true) {

SleepUtil.sleepSecond(100);

}

}

}

// 线程等待在Waiting.class实例上

static class Waiting implements Runnable {

@Override

public void run() {

while (true) {

synchronized (Waiting.class) {

try {

Waiting.class.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}

// 该线程Blocked.class实例上加锁,不会释放该锁

static class Blocked implements Runnable {

@Override

public void run() {

synchronized (Blocked.class) {

while (true) {

SleepUtil.sleepSecond(100);

}

}

}

}

}

使用JPS查看Java进程:

在这里插入图片描述

查看示例代码ThreadStateDemo进程ID是2576,键入jstack 2576查看输出:

在这里插入图片描述

整理输出结果:

在这里插入图片描述

| 线程名称 | 线程状态 |

| — | — |

| Blocked2Thread | BLOCKED (on object monitor),阻塞在获取Blocked.class的锁上 |

| Blocked1Thread | TIMED_WAITING (sleeping) |

| WaitingThread | WAITING (on object monitor) |

| TimeWaitingThread | TIMED_WAITING (sleeping) |

总结:

线程在自身生命周期中不是规定处于某一个状态,而是随着代码的执行在不同的状态之间进行切换。

Java线程的状态变化图如下:

在这里插入图片描述

Java线程状态变迁图

总结:

  • 线程创建后,调用start()方法开始运行

  • 线程执行wait()方法后,线程进入等待状态,进入等待的线程需要依靠其他线程才能够返回到运行状态

  • 超时等待相当于在等待状态的基础上增加了超时限制,达到设置的超时时间后返回到运行状态

  • 线程执行同步方法或代码块时,未获取到锁的线程,将会进入到阻塞状态。

  • 线程执行完Runnable的run()方法之后进入到终止状态

  • 阻塞在Java的concurrent包中Lock接口的线程是等待状态,因为Lock接口阻塞的实现使用的是Daemon线程

6、Daemon线程

简介:

Daemon线程是一种支持型线程,它的主要作用是程序中后台调度和支持性工作。当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。Daemon线程需要在启动之前设置,不能在启动之后设置。

设置方式:

Thread.setDaemon(true)

需要特别注意的点:

Daemon线程被用作支持性工作的完成,但是在Java虚拟机退出时Daemon线程的finally代码块不一定执行。

示例代码:

package com.lizba.p2;

/**

  •  DaemonRunner线程
    
  • @Author: Liziba

  • @Date: 2021/6/14 19:50

*/

public class DaemonRunner implements Runnable{

@Override

public void run() {

try {

SleepUtil.sleepSecond(100);

} finally {

System.out.println(“DaemonRunner finally run …”);

}

}

}

测试:

package com.lizba.p2;

/**

  • @Author: Liziba

  • @Date: 2021/6/14 19:59

*/

public class DaemonTest {

public static void main(String[] args) {

Thread t = new Thread(new DaemonRunner(), “DaemonRunner”);

t.setDaemon(true);

t.start();

}

}

输出结果:

在这里插入图片描述

总结:

不难发现,DaemonRunner的run方法的finally代码块并没有执行,这是因为,当Java虚拟机中已经没有非Daemon线程时,虚拟机会立即退出,虚拟机中的所以daemon线程需要立即终止,所以线程DaemonRunner会被立即终止,finally并未执行。

二、线程启动和终止


1、构造线程

运行线程之前需要构造一个线程对象,线程对象在构造的时候需要设置一些线程的属性,这些属性包括线程组、线程的优先级、是否是daemon线程、线程名称等信息。

代码示例:

来自java.lang.Thread

/**

  • Initializes a Thread.

  • @param g the Thread group

  • @param target the object whose run() method gets called

  • @param name the name of the new Thread

  • @param stackSize the desired stack size for the new thread, or

  •    zero to indicate that this parameter is to be ignored.
    
  • @param acc the AccessControlContext to inherit, or

  •        AccessController.getContext() if null
    
  • @param inheritThreadLocals if {@code true}, inherit initial values for

  •        inheritable thread-locals from the constructing thread
    

*/

private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {

if (name == null) {

throw new NullPointerException(“name cannot be null”);

}

// 设置线程名称

this.name = name;

// 当前线程设置为该线程的父线程

Thread parent = currentThread();

SecurityManager security = System.getSecurityManager();

if (g == null) {

if (security != null) {

g = security.getThreadGroup();

}

if (g == null) {

g = parent.getThreadGroup();

}

}

g.checkAccess();

if (security != null) {

if (isCCLOverridden(getClass())) {

security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);

}

}

g.addUnstarted();

// 设置线程组

this.group = g;

// 将daemon属性设置为父线程的对应的属性

this.daemon = parent.isDaemon();

// 将prority属性设置为父线程的对应的属性

this.priority = parent.getPriority();

if (security == null || isCCLOverridden(parent.getClass()))

this.contextClassLoader = parent.getContextClassLoader();

else

this.contextClassLoader = parent.contextClassLoader;

this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();

this.target = target;

setPriority(priority);

// 复制父线程的InheritableThreadLocals属性

if (inheritThreadLocals && parent.inheritableThreadLocals != null)

this.inheritableThreadLocals =

ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

/* Stash the specified stack size in case the VM cares */

this.stackSize = stackSize;

// 设置一个线程id

tid = nextThreadID();

}

总结:

在上述代码中,一个新构建的线程对象时由其parent线程来分配空间的,而child继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时会分配一个唯一的ID来标志线程。此时一个完整的能够运行的线程对象就初始化好了,在堆内存中等待运行。

2、什么是线程中断

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以通过调用静态方法Thread.interrupted()对当前线程的中断标志位进行复位。

如下情况不能准确判断线程是否被中断过:

  1. 线程已经终止运行,即使被中断过,isInterrupted()方法也会返回false

  2. 方法抛出InterruptedException异常,即使被中断过,调用isInterrupted()方法将会返回false,这是因为抛出InterruptedException之前会清除中断标志。

示例代码:

package com.lizba.p2;

/**

  •  线程中断示例代码
    
  • @Author: Liziba

  • @Date: 2021/6/14 20:36

*/

public class Interrupted {

public static void main(String[] args) {

// sleepThread不停的尝试睡眠

Thread sleepThread = new Thread(new SleepRunner(), “sleepThread”);

sleepThread.setDaemon(true);

// busyThread

Thread busyThread = new Thread(new BusyRunner(), “busyThread”);

busyThread.setDaemon(true);

// 启动两个线程

sleepThread.start();

busyThread.start();

// 休眠5秒,让sleepThread和busyThread运行充分

SleepUtil.sleepSecond(5);

// 中断两个线程

sleepThread.interrupt();

busyThread.interrupt();

System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());

System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());

// 睡眠主线程,防止daemon线程退出

SleepUtil.sleepSecond(2);

}

static class SleepRunner implements Runnable {

@Override

public void run() {

while (true) {

SleepUtil.sleepSecond(10);

}

}

}

static class BusyRunner implements Runnable {

@Override

public void run() {

while (true) {}

}

}

}

查看运行结果:

在这里插入图片描述

总结:

抛出InterruptedException的是sleepThread线程,虽然两者都被中断过,但是sleepThread线程的中断标志返回的是false,这是因为TimeUnit.SECONDS.sleep(seconds)会抛出InterruptedException异常,抛出异常之前,sleepThread线程的中断标志被清除了。但是,busyThread一直在运行没有抛出异常,中断位没有被清除。

3、suspend()、resume()和stop()

举例:

线程这三个方法,相当于QQ音乐播放音乐时的暂停、恢复和停止操作。(注意这些方法已经过期了,不建议使用。)

示例代码:

package com.lizba.p2;

import java.text.DateFormat;

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.concurrent.TimeUnit;

/**

  •  线程过期方法示例
    
  • @Author: Liziba

  • @Date: 2021/6/14 20:57

*/

public class Deprecated {

static DateFormat format = new SimpleDateFormat(“HH:mm:ss”);

public static void main(String[] args) {

Thread printThread = new Thread(new PrintThread(), “PrintThread”);

printThread.start();

SleepUtil.sleepSecond(3);

// 暂停printThread输出

printThread.suspend();

System.out.println("main suspend PrintThread at " + format.format(new Date()));

SleepUtil.sleepSecond(3);

// 恢复printThread输出

printThread.resume();

System.out.println("main resume PrintThread at " + format.format(new Date()));

SleepUtil.sleepSecond(3);

// 终止printThread输出

printThread.stop();

System.out.println("main stop PrintThread at " + format.format(new Date()));

SleepUtil.sleepSecond(3);

}

static class PrintThread implements Runnable {

@Override

public void run() {

while (true) {

System.out.println(Thread.currentThread().getName() + "Run at "

  • format.format(new Date()));

SleepUtil.sleepSecond(1);

}

}

}

}

输出结果:

在这里插入图片描述

总结:

上述代码执行输出的结果,与API说明和我们的预期完成一致,但是看似正确的代码却隐藏这很多问题。

存在问题:

  • suspend()方法调用后不会释放已占有的资源(比如锁),可能会导致死锁

  • stop()方法在终结一个线程时不能保证资源的正常释放,可能会导致程序处于不确定的工作状态

4、正确的终止线程

  1. 调用线程的interrupt()方法

  2. 使用一个Boolean类型的变量来控制是否停止任务并终止线程

示例代码:

package com.lizba.p2;

/**

  •  标志位终止线程示例代码
    
  • @Author: Liziba

  • @Date: 2021/6/14 21:17

*/

public class ShutDown {

public static void main(String[] args) {

Runner one = new Runner();

Thread t = new Thread(one, “CountThread”);

t.start();

SleepUtil.sleepSecond(1);

t.interrupt();

Runner two = new Runner();

t = new Thread(two, “CountThread”);

t.start();

SleepUtil.sleepSecond(1);

two.cancel();

}

private static class Runner implements Runnable {

private long i;

private volatile boolean on = true;

@Override

public void run() {

while (on && !Thread.currentThread().isInterrupted()) {

i++;

}

System.out.println("Count i = " +i);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

t.start();

SleepUtil.sleepSecond(1);

two.cancel();

}

private static class Runner implements Runnable {

private long i;

private volatile boolean on = true;

@Override

public void run() {

while (on && !Thread.currentThread().isInterrupted()) {

i++;

}

System.out.println("Count i = " +i);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-jNrBijbi-1715761211435)]

[外链图片转存中…(img-nu6itpjM-1715761211436)]

[外链图片转存中…(img-Iz4PzckV-1715761211436)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值