Java并发编程艺术 - 多线程

启动Java应用程序则是一个进程,一个进程可以启动多个线程,线程是操作系统调度最小单元。线程拥有独立的计数器、栈和局部变量,可以共享堆内存。

一个Java程序入口是main主线程,线程优先级从1-10,优先级越高分配时间片越长。

为什么使用多线程

  • 更多的处理器核心

    使用多线程技术,将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着更多处理器核心的加入而变得更有效率。

  • 更快的响应时间

    使用多线程技术,即将数据一致性不强的操作派发给其他线程处
    理(也可以使用消息队列),如生成订单快照、发送邮件等。这样做的好处是响应用户请求的线程能够尽可能快地处理完成,缩短了响应时间,提升了用户体验。

  • 更好的编程模型

    Java为多线程编程提供了良好、考究并且一致的编程模型,使开发人员能够更加专注于问题的解决,即为所遇到的问题建立合适的模型,而不是绞尽脑汁地考虑如何将其多线程化。一
    旦开发人员建立好了模型,稍做修改总是能够方便地映射到Java提供的多线程编程模型上。

线程状态

  • NEW 新建
    • Thread.start();
  • RUNNABLE 运行,包括就绪和运行
    • Thread.run();
  • BLOCKED 阻塞
    • synchronized
  • WAITING 等待,等待其他线程通知和中断
    • Object.wait()
    • Object.join()
    • LockSupport.park(Thread)
  • TIME_WAITTING 等待,等待指定时间超时
    • Thread.sleep(long)
    • Object.wait(long)
    • Object.join(long)
    • LockSupport.parkNanos()
    • LockSupport.parkUntil()
  • TERMINATED 终止
    • interrupt(),isInterrupted()和interrupted()
    • suspend()、resume()和stop()
      线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换
  • 线程创建之后,调用start()方法开始运行。
  • 当线程执行wait()方法之后,线程进入等待状态。
  • 进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态
  • 而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。
  • 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。
  • 线程在执行Runnable的run()方法之后将会进入到终止状态。

  • Java将操作系统中的运行和就绪两个状态合并称为运行状态。

  • 阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
  • 但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

线程优先级

1
2
3
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(priority);
thread.start();

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。

线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java线程对于优先级的设定。

Daemon线程

守护线程,为非Daemon线程提供后台调度和服务,例如GC线程。

1
2
3
Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
thread.setDaemon(true);
thread.start();

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。

Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。

线程中断

其他线程调用该线程的interrupt()方法进行中断,线程本身调用isInterrupted(()判断是否中断,也可以调用Thread.interrupted()对当前线程的中断标识位进行复位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Thread2 {

public static void main(String[] args) throws Exception {
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread1");
countThread.start();

// 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();

Runner two = new Runner();
countThread = new Thread(two, "CountThread2");
countThread.start();

// 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束
TimeUnit.SECONDS.sleep(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()) {
try {
i++;
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//重新设置中断标示
}
}
System.out.println("Count i = " + i + ",Name:" + Thread.currentThread().getName());
}

public void cancel() {
on = false;
}
}
}

线程等待和通知

  • notify() 随机通知在对象上等待的另一个线程,使其在wait()状态上尝试获取锁,如获取锁成功则返回
  • notifyAll() 通知在对象上等待的所有线程,竞争锁
  • wait() 进入WAITING状态,等待通知或中断,会释放锁
  • wait(long) 进入TIME_WAITTING状态,等待超时
  • join() 进入WAITING状态,等待其他线程终止后返回,不释放锁
  • join(long) 进入TIME_WAITTING状态,等待其他线程终止后返回,不释放锁
------ 本文结束------

本文标题:Java并发编程艺术 - 多线程

文章作者:Perkins

发布时间:2019年06月03日

原始链接:https://perkins4j2.github.io/posts/24227/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。