Java并发编程艺术之Java并发编程基础
一、线程简介
1. 什么是线程
线程是现代操作系统调度的最小单位,也称为轻量级进程,这些线程拥有各自的计数器、堆栈、局部变量等信息,并且能够访问主内存中的共享变量。
从宏观的角度来看,使用者可能觉得多个线程在同时进行,但是是处理器在这些线程上高速切换。比如下面的 代码示例
public class TestThread {
public static void main(String[] args) {
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = mxBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo : threadInfos) {
System.out.println("["+ threadInfo.getThreadId() +"] " + threadInfo.getThreadName());
}
}
}
运行上面的程序输出下面的结果:
[ 5 ]Attach Listener // 线程间通信监听的线程
[ 4 ]Signal Dispatcher // 分发处理发送给JVM信号的线程
[ 3 ]Finalizer // 调用finalizer对象的线程
[ 2 ]Reference Handler // 清除Reference的线程
[ 1 ]main // main线程,程序的主入口
从上面的运行结果可以看出,java程序的运行不仅main线程,还有其它线程。
2. 为什么要使用线程
正确的使用多线程,主要有下面几点:
更多的处理器数量
随着处理器数量的增加、处理器性能的提升,如果程序以单线程运行,多余的处理器将会被浪费,如果使用多线程技术,可以最大化的利用处理器,减少程序处理时间,变得更有效率。更快的响应时间
对一些业务复杂的代码,在多线程处理的情况下,对一些数据一致性要求不高的操作,可以交给其它线程处理,这样好处是响应用户请求的程序能够尽快的完成,缩短了响应时间,提升用户体验。更好的编程模型
Java为多线程编程提供了良好的编程模型,使得开发人员更加专注问题的解决,一旦建立好了合适的模型,稍作修改可以映射到Java提供的多线程模型上。
3. 线程优先级
线程执行的前提是系统给该线程分配了时间片,分配的时间片越多,获得的处理器资源越多,而线程的优先级决定了需要分配多一点还是少一点处理器资源。
优先级的范围一般在1-10之间,可以通过setPriority(int)方法来设置,可以针对不同不同的需求设置不同的优先级:
- 针对频繁阻塞、休眠、I/O操作 的线程需要设置比较高的优先级
- 针对偏重计算、较多CPU时间的线程需要设置比较低的优先级
注意: 优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java对优先级的设定,比如下面的示例代码
public class Priority {
private static volatile boolean notStart = true;
private static volatile boolean notEnd = true;
public static void main(String[] args) throws Exception {
List<Job> jobs = new ArrayList<Job>();
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;
for (Job job : jobs) {
System.out.println("Job Priority : " + job.priority + ",Count : " + job.jobCount);
}
}
static class Job implements Runnable {
private int priority;
private long jobCount;
public Job(int priority) {
this.priority = priority;
}
public void run() {
while (notStart) {
Thread.yield(); //所有通过start()启动的线程都会暂停在此处
}
while (notEnd) {
Thread.yield();
jobCount++;
}
}
}
}
在不同的操作系统环境下,输出的结果不同,
- MAC系统 (jdk version 1.7.0_71)
Job Priority : 1, Count : 1259592
Job Priority : 1, Count : 1260717
Job Priority : 1, Count : 1264510
Job Priority : 1, Count : 1251897
Job Priority : 1, Count : 1264060
Job Priority : 10, Count : 1256938
Job Priority : 10, Count : 1267663
Job Priority : 10, Count : 1260637
Job Priority : 10, Count : 1261705
Job Priority : 10, Count : 1259967
- ThinkPad E470 Windows 10 (jdk version 1.8.0_101)
Job Priority : 1,Count : 1272179
Job Priority : 1,Count : 1272751
Job Priority : 1,Count : 1274397
Job Priority : 1,Count : 1273538
Job Priority : 1,Count : 1273410
Job Priority : 10,Count : 4584184
Job Priority : 10,Count : 4596352
Job Priority : 10,Count : 4594366
Job Priority : 10,Count : 4586751
Job Priority : 10,Count : 4574652
4. 线程状态
线程存在多中状态,可以参见下表:
关于线程状态的情况可以通过下面的示例代码来查看:
//ThreadState代码
public class ThreadState {
public static void main(String[] args) {
new Thread(new TimeWaiting (), "TimeWaitingThread").start();
new Thread(new Waiting(), "WaitingThread").start();
// 使用两个Blocked线程,一个获取锁成功,另一个被阻塞
new Thread(new Blocked(), "BlockedThread-1").start();
new Thread(new Blocked(), "BlockedThread-2").start();
}
static class TimeWaiting implements Runnable {
@Override
public void run() {
while(true) {
SleepUtils.second(100L);
}
}
}
static class Waiting implements Runnable {
@Override
public void run() {
while(true) {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class Blocked implements Runnable {
@Override
public void run() {
synchronized (Blocked.class) {
while(true) {
SleepUtils.second(100L);
}
}
}
}
}
//SleepUtils代码
public class SleepUtils {
public static final void second(Long seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
查看线程状态信息的操作步骤如下:
1. 通过eclipse将工程导出jar文件,并指定main路径(可能jar中包含多个main入口)
2. 通过 java -jar JavaInBase.jar 启动jar程序
3. 通过jps查看运行的线程pid,如下图
4. 通过 jstack pid 查看线程状态,如下图
关于Java线程状态变迁,可以通过下面图示进行查看
由上图可以看出,
1) 线程创建之后,调用start()开始运行,
2) 当线程执行wait()方法之后,线程进入等待状态(Waiting),线程进入等待状态的情况下,需要依靠其它线程的通知才能返回到运行状态,
3) 而超时等待状态(TIME_WAITING)在等待的基础上添加了超时的限制,即超时时间到达时会返回到运行状态,
4) 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态
5) 当线程的run方法执行完之后会进入stop状态
注意 : Java将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法
5.Daemon线程
线程分类:java中有两类线程: 用户线程(User Thred)、守护线程(Daemon Thread)
守护线程主要用作程序后台调度以及支持性工作,比如垃圾回收线程;守护线程和用户线程几乎没有什么区别,区别在于线程的离开,当用户线程全部退出时,守护线程没有了被守护者,也就没有了继续运行的必要。
关于守护线程需要注意下面几点:
- thread.setDaemon(true)必须在thread.start()之前设置,否则会报IllegalThreadStateException异常,即不能将一个常规线程设置为守护线程
- 在Daemon线程中产生的新线程也是Daemon的
- 守护线程不能访问固有资源,如: 文件、数据库,因为他会在任何时候发生中断。
- 在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源
的逻辑
可以通过下面的示例代码进行查看
public class DaemonThrad {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner() , "DaemonRunner");
thread.setDaemon(true);
thread.start();
}
static class DaemonRunner implements Runnable {
@Override
public void run() {
try {
SleepUtils.second(10L);
} finally {
System.out.println("DaemonThread finally run.");
}
}
}
}
二、 启动和终止线程
1. 构造线程
在运行线程之前需要先构造线程,对线程设置相关信息,比如:线程组(Thread Group)、 线程优先级(Thread Priority) 、是否守护线程(is Deamon)等信息,下面示例代码时java.lang.Thread类中线程初始化的代码
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
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);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
对上面的代码进行分析,
- 一个新构造的线程对象是由其 parent 线程来进行空间分配的,
- child线程继承了 parent 线程 是否守护线程、优先级、加载资源的contextClassLoader、ThreadLocal 等信息
- 分配唯一的线程 tid 来标志这个child线程
- 启动线程前,最好为该线程设置线程的名称,在通过jstack分析程序或进行问题排查时,可以给开发人员一个提示。
线程初始化好后,会在堆内存中等待运行
2. 启动线程
线程通过thread.start()来启动线程,start()语义是当前线程告诉Java虚拟机,只要处理器等资源空闲应立即调用 startI() 启动的线程。
3. 线程中断
线程中断可以理解为线程的一个标志位(flag),它标志一个运行中的线程是否被其它线程进行了中断操作,可以通过interrupt()方法对其进行中断操作。可以通过isInterrupted()方法校验线程是否已经被中断,也可以通过Thread.isInterrupted()来堆线程进行复位。
4. 线程的suspend()、resume()和stop()
关于线程的暂停、复位、停止可以参考下面的示例代码
public class DeprecatedThread {
public static void main(String[] args) throws InterruptedException {
DateFormat format = new SimpleDateFormat("HH:mm:ss");
Thread printThread = new Thread(new Runner(), "PrinterThread");
printThread.setDaemon(true);
printThread.start();
//main 线程停止运行3s钟
TimeUnit.SECONDS.sleep(3);
//printThread线程暂停
printThread.suspend();
System.out.println("Main suspend printThread at " + format.format(new Date()));
//main 线程停止运行3s钟
TimeUnit.SECONDS.sleep(3);
//对printThread进行复位操作
printThread.resume();
System.out.println("Main resume printThread at " + format.format(new Date()));
//main 线程停止运行3s
TimeUnit.SECONDS.sleep(3);
//对printThread进行停止
printThread.stop();
System.out.println("Main stop printThread at " + format.format(new Date()));
TimeUnit.SECONDS.sleep(3);
}
static class Runner implements Runnable {
@Override
public void run() {
DateFormat format = new SimpleDateFormat("HH:mm:ss");
while(true) {
System.out.println(Thread.currentThread().getName() + " Run at " + format.format(new Date()));
SleepUtils.second(1L); // 此行代码必须存在, 不然无法从run中退出
}
}
}
}
输出结果如下:
PrinterThread Run at 22:00:31
PrinterThread Run at 22:00:32
PrinterThread Run at 22:00:33
Main suspend printThread at 22:00:34
Main resume printThread at 22:00:37
PrinterThread Run at 22:00:37
PrinterThread Run at 22:00:38
PrinterThread Run at 22:00:39
Main stop printThread at 22:00:40
注意:上面的suspend()、resume()、stop()方法会带来副作用,是不被建议使用的,比如:
1) suspend(): 执行该方法时,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题
2)stop(): 执行该方法时,不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下
5. 安全的终止线程
线程的运行如果不能正确的终止,可能会造成严重的后果,因此需要保证程序正确的执行结束, 第4节内容suspend(), stop() 是不被推荐的,可以采用interrupted(), cancel()方法对线程进行正确的终止操作, 可以参见下面的代码
public class ShutDownThread {
public static void main(String[] args) throws InterruptedException {
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
// main 线程睡眠 1s, 让countThread线程运行
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two, "CountThread");
countThread.start();
//main线程睡眠 1s, 让countThread运行
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
static class Runner implements Runnable {
private long i ;
private volatile boolean isOn= true ;
@Override
public void run() {
while(isOn && !Thread.currentThread().isInterrupted()){
//如果标志位为true,并且当前线程的终止状态为false的情况下, 执行下面的代码
i++ ;
}
System.out.println("Count i = " + i);
}
public void cancel() {
isOn = false ;
}
}
}
代码运行结果如下:
Count i = 679027409
Count i = 684359115
针对上面的代码可以通过下面的方式正确的终止线程:
1) 调用interupted()方法
2) 线程内部设置中断标志位flag,比如上面的isOn, 通过设置isOn = false 来终止线程的运行。
三、线程建通信
孤立运行的线程没有价值或者价值很小, 现实情况中线程之间都是相互配合完成工作
1. volatile和synchronized关键字
- volatile , 关键字volatile可以用来修饰成员变量,当该成员变量修改之后会刷新到主内存中,并告诉其它需要对该变量进行访问的线程,该变量已经发生变更,需要从新从主内存中从新加载,以保持数据的可见性。
- synchronized,关键字synchronized可以修饰方法或者同步块,确保多个线程在同一个时刻只有一个线程处于方法或代码块中,以去报线程对变量访问的可见性和排它性
可以通过下面的示例代码,使用javap命令来熟悉同步代码块
public class SynchronizedExample {
public static void main(String[] args) {
//1. 对SynchronizedExample 对象进行加锁
synchronized (SynchronizedExample.class) {
}
//2. 静态同步方法, SynchronizedExample Class对象进行加锁
m();
}
public static synchronized void m() {
}
}
通过javap查看信息的具体操作步骤如下:
1) 书写上面的示例代码,保证代码的可以编译通过
2) 打开cmd窗口,进入上面代码编译后的class文件所在目录
3) 执行命令 javap -v SynchronizedExample.class (如果有修改文件类名,请使用修改后的文件名), 本机输出的信息如下:
C:\ityongman\workspace\svncode\JavaInBase\bin\com\ityongman\thread>javap -v SynchronizedExample.class
Classfile /C:/ityongman/workspace/svncode/JavaInBase/bin/com/ityongman/thread/SynchronizedExample.class
Last modified 2018-8-22; size 519 bytes
MD5 checksum 4a8966093f66babccd21068023ebc63c
Compiled from "SynchronizedExample.java"
public class com.ityongman.thread.SynchronizedExample
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // com/ityongman/thread/SynchronizedExample
#2 = Utf8 com/ityongman/thread/SynchronizedExample
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/ityongman/thread/SynchronizedExample;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Methodref #1.#17 // com/ityongman/thread/SynchronizedExample.m:()V
#17 = NameAndType #18:#6 // m:()V
#18 = Utf8 m
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 SourceFile
#22 = Utf8 SynchronizedExample.java
{
public com.ityongman.thread.SynchronizedExample();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/ityongman/thread/SynchronizedExample;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #1 // class com/ityongman/thread/SynchronizedExample
2: dup
3: monitorenter
4: monitorexit
5: invokestatic #16 // Method m:()V
8: return
LineNumberTable:
line 6: 0
line 10: 5
line 11: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
public static synchronized void m();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 15: 0
LocalVariableTable:
Start Length Slot Name Signature
}
SourceFile: "SynchronizedExample.java"
针对输出信息,有下面的结论:
- 对同步代码块,使用了monitorenter和monitorexit指令实现同步
- 对同步方法,使用方法修饰符上的ACC_SYNCHRONIZED来实现同步
注,任意对象都拥有自己的监视器,无论使用monitor相关指令还是在方法上使用ACC_SYNCHRONIZED修饰,其本质都是获取对象的监视器,执行方法必须获取到该对象的监视器才可以进入同步快或同步方法,而没有获取到监视器的线程将会进入BLOCKED状态。
为了更进一步了解对象、对象监视器、同步队列、执行线程之间的关系,可以参考下面截图:
针对上面图5,可以看出
- 如果要访问Object对象,必须先获取该Object对象的锁
- 如果获取对象监视器锁失败,将会进入同步队列,线程状态变为BLOCKED状态
- 如果之前访问Object的对象释放了监视器锁,将会释放信号唤醒同步队列中的线程,使其重新尝试获取监视器锁
2. 等待/通知机制
等待/通知方法在任意Java对象中都具备,因为这些信息定义在java.lang.Object上,如下面的类信息图 和 等待/通知的相关方法相关说明图:
等待/通知机制在功能层面上实现了解耦,体系结构上具有良好的伸缩性。在模型上和生产者/消费者很像,当一个线程修改了对象的值,另一个线程感知到这个对象变化,会进行相应的操作。
可以参见下面的伪代码:
while (value != desire) {
Thread.sleep(1000);
}
doSomething();
但是针对上面的代码,存在一些问题:
1) 难以确保及时性。虽然睡眠期间不怎么消耗系统资源,但是睡眠时间过久,不能及时发现条件的变化
2) 难以降低开销。如果休眠的时间过短,可以比较及时的发现资源的变化,但是会消耗比较多的系统资源。
可以通过下面的示例代码对wait()和notify()/notifyall()做进一步了解
public class WaitNotifyThread {
static boolean flag = true ;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1); // main线程让出资源, 让其它线程执行
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
synchronized (lock) {
while(flag) {
//条件不满足, 进入等待状态
try {
System.out.println(Thread.currentThread() + " flag is true , wait @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait(); //Wati线程进入BLOCKED状态, 并且释放持有的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//条件满足, 继续进行下面的处理
System.out.println(Thread.currentThread()+ " flag is false , runing @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock , notify @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll(); //Notify 发送信号量通知Wait重新尝试获取锁信息
flag = false ; // 设置flag = false ,避免仍然进入while中
SleepUtils.second(5L); // Notify线程睡眠5s, 但是线程依然持有者锁, 虽然notifyAll已经执行, 但是无法获取到锁
}
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again , notify @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5L); // 让Notify线程睡眠5s, 避免进行资源的竞争
}
}
}
}
针对上面的示例代码需要注意下面的几点:
- 使用wait()、notify()、notifyall()时需要先对调用对象加锁
- 调用wait()后,线程的状态由RUNNING转为WAITING, 并将当前线程放到对象的等待队列中
- notify()和nofityall()方法执行后,等待线程不一定会从wait状态返回,需要等调用notify()/notifyAll()的线程释放锁之后,等待线程才会有机会从wait状态返回。
- notify()是将一个等待线程从等待队列中移动到同步队列,notifyAll()是将所有等待线程从等待队列中移动到同步队列,被移动的线程从BLOCKED状态变为WAITING
- 从wait状态返回的前提条件是获取到了对象的锁
对上面示例代码和5点内容可以通过下面的图示来了解,
3. 等待/通知的经典范式
针对经典的等待/通知方式,可以分为等待方和通知方,它们的分别满足下列条件,
1) 等待方需要满足下面的规则;
- 获取对象的锁
- 如果条件不满足, 调用对象的wait()方法,被通知后仍然需要检测条件
- 条件满足执行对应的逻辑
伪代码:
synchronized(lock) {
while(条件不满足) {
lock.wait();
}
对象的处理逻辑。
}
2) 通知方需要满足下列条件
- 获取对象的锁
- 改变条件
- 通知所有等待在对象上的锁
伪代码
synchronized(lock) {
改变条件
lock.notifyAll()
}
4. 经典用例
1. 管道输入/输出流
管道输入/输出和普通文件的输入/输出或网络输入/输出是不同的,管道输入/输出主要是线程之间的数据传输(媒介为内存), 涉及的类有PipeOutputStream、PipeInputStream、PipeReader、PipeWriter, 可以参考下面的示例代码:
public class PipeThread {
public static void main(String[] args) throws IOException {
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();
//需要将输入流和输出流连接起来, 否则使用时会抛出IoException
writer.connect(reader);
Thread printThread = new Thread(new Print(reader), "PrintReader");
printThread.start();
int receive = 0 ;
try {
while ((receive = System.in.read()) != -1) {
writer.write(receive);
}
} finally {
writer.close();
}
}
static class Print implements Runnable {
PipedReader reader ;
public Print(PipedReader reader) {
this.reader = reader ;
}
@Override
public void run() {
int receive = 0 ;
try {
while((receive = reader.read()) != -1) {
System.out.print((char)receive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果如下:
Hello World
Hello World
2.Thread.join()的使用
如果一个线程A执行了thread.join(), 其含义是线程A等待thread线程终止之后才从thread.join()返回,对join(long millis)表示线程thread在给定的时间内没有终止,将从该超时方法返回。可以参考下面的示例代码:
public class JoinThread {
public static void main(String[] args) {
Thread previous = Thread.currentThread();
for(int i = 0; i < 10 ; i++) {
Thread thread = new Thread(new Domino(previous), String.valueOf(i));
thread.start();
previous = thread ;
}
System.out.println(Thread.currentThread().getName() + " terminated.");
}
static class Domino implements Runnable {
private Thread thread ;
public Domino(Thread thread) {
this.thread = thread ;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " terminated.");
}
}
}
运行结果如下:
main terminated.
0 terminated.
1 terminated.
2 terminated.
3 terminated.
4 terminated.
5 terminated.
6 terminated.
7 terminated.
8 terminated.
9 terminated.
3.ThreadLocal的使用
ThreadLocal是一个以ThreadLocal对象为键,任意对象为值的存储结构,通过set(T)方法设置值,get()方法获取原先的值, 可以参考下面的示例代码,
public class ProfileThread {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ProfileThread.begin();
TimeUnit.SECONDS.sleep(1);
System.out.println("Cost: " + ProfileThread.end() + " mills");
}
public static final void begin() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long end() {
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
}
输出结果:
Cost: 1003 mills
声明:本文中借鉴,总结自下面的文章,有兴趣的读者,可以购买正版图书或进入官网进一步阅读相关信息。
1. Java并发编程艺术
2. http://ifeve.com/