Java线程基础

  1. 更快响应时间

在复杂业务场景中,我们可以将非强一致性关联的业务派发给其他线程处理(或者使用消息队列)。这样可以减少应用响应用户请求的时间

  1. 更好的编程模型

合理使用Java的提供的多线程编程模型,能使得程序员更好的解决问题,而不需要过于复杂的考虑如何将其多线程化。

4、线程的优先级

现代操作系统基本采用的是时间片分配的方式来调度线程,也就是操作系统将CPU的运行分为一个个时间片,线程会分配的若干时间片,当线程时间片用完了,就会发生线程调度等待下次时间片的分配。线程在一次CPU调度中能执行多久,取决于所分时间片的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。

在Java线程中,线程的优先级的可设置范围是1-10,默认优先级是5,理论上优先级高的线程分配时间片数量要优先于低的线程(部分操作系统这个设置是不生效的);

示例代码:

package com.lizba.p2;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.TimeUnit;

/**

  •  线程优先级设置
    
  • @Author: Liziba

  • @Date: 2021/6/14 12:03

*/

public class Priority {

/** 线程执行流程控制开关 */

private static volatile boolean notStart = true;

/** 线程执行流程控制开关 */

private static volatile boolean notEnd = true;

public static void main(String[] args) throws InterruptedException {

List jobs = new ArrayList<>();

// 设置5个优先级为1的线程,设置5个优先级为10的线程

for (int i = 0; i < 10; i++) {

int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;

Job job = new Job(priority);

jobs.add(job);

Thread thread = new Thread(job, “Thread:” + i);

thread.setPriority(priority);

thread.start();

}

notStart = false;

TimeUnit.SECONDS.sleep(10);

notEnd = false;

jobs.forEach(

job -> System.out.println("Job priority : " + job.priority + ", Count : " + job.jobCount)

);

}

/**

  • 通过Job来记录线程的执行次数和优先级

*/

static class Job implements Runnable {

private int priority;

private long jobCount;

public Job(int priority) {

this.priority = priority;

}

@Override

public void run() {

while (notStart) {

// 让出CPU时间片,等待下次调度

Thread.yield();

}

while (notEnd) {

// 让出CPU时间片,等待下次调度

Thread.yield();

jobCount++;

}

}

}

}

执行结果:

在这里插入图片描述

从输出结果上来看,优先级为1的线程和优先级为10的线程执行的次数非常相近,因此这表明程序正确性是不能依赖线程的优先级高低的。

5、线程的状态

线程的生命周期如下:

| 状态名称 | 说明 |

| — | — |

| NEW | 初始状态,线程被构建,并未调用start()方法 |

| RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行中” |

| BLOCKED | 阻塞状态,线程阻塞于锁 |

| WAITING | 等待状态,线程进入等待状态,进入该状态表示当前线程需要等待其他线程作出一些特定动作(通知或中断) |

| TIME_WAITING | 超时等待,先比WAITING可以在指定的时间内自行返回 |

| TERMINATED | 终止状态,表示当前线程已经执行完毕 |

通过代码来查看Java线程的状态

代码示例:

package com.lizba.p2;

import java.util.concurrent.TimeUnit;

/**

  •  睡眠指定时间工工具类
    
  • @Author: Liziba

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

*/

public class SleepUtil {

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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值