共计 14822 个字符,预计需要花费 38 分钟才能阅读完成。
一、线程的创立
1.Oracle 官网形容
There are two ways to create a new thread of execution.
One is to declare a class to be a subclass of Thread
. This subclass should override the run
method of class Thread
. An instance of the subclass can then be allocated and started.
The other way to create a thread is to declare a class that implements the Runnable
interface. That class then implements the run
method. An instance of the class can then be allocated, passed as an argument when creating Thread
, and started.
有两种形式能够创立新的执行线程。一种办法是定义一个 Thread 类的子类,该子类重写 Thread 类的 run 办法。而后能够调配并启动子类的实例。创立线程的另一种办法是定义一个实现 Runnable 接口的类,而后能够调配该类的实例,在创立 Thread 时将其作为参数传递并启动。
1.1 继承 Thread 类
public class ThreadStyle extends Thread {
@Override
public void run() {System.out.println("用 Thread 形式创立线程");
}
public static void main(String[] args) {new ThreadStyle().start();}
}
定义一个类继承 Thread 类,而后重写 run 办法,间接通过该类的实例启动线程,输入后果为:用 Thread 形式创立线程。
1.2 实现 Runnable 接口
public class RunnableStyle implements Runnable {
@Override
public void run() {System.out.println("用 Runnable 形式实现线程");
}
public static void main(String[] args) {new Thread(new RunnableStyle()).start();}
}
定义一个类实现 Runnable 接口并重写 run 办法,通过将该 Runnable 实例传入 Thread 类参数中启动线程,输入后果为:用 Runnable 形式实现线程。
1.3 两种办法比照
精确的讲,创立线程都是通过结构 Thread 类这一种形式实现,实现线程的执行单元有两种形式。
public class Thread implements Runnable {
/** 省略 */
private Runnable target;
@Override
public void run() {if (target != null) {target.run();
}
}
}
- 办法一(继承 Thread 类):Thread 类自身也是实现了 Runnable 接口而后重写 run 办法的,当通过继承 Thread 类创立线程时,Thread 类的整个 run 办法都会被重写。
- 办法二(实现 Runnable 接口):Thread 类中有一个名为 target 的 Runnable 变量,在 Thread(Runnable target) 构造方法中传入 Runnable 实例会初始化 target 属性,通过该办法创立线程,最终只会调用 target.run() 办法,并不会重写整个 run 办法。
通过办法二创立线程的形式其实更好:
- 实现 Runnable 接口的形式与 Thread 类解耦。
- 接口能够多实现,继承 Thread 类的形式限度了可扩展性。
- 继承 Thread 类的话,每次新建线程都会去创立一个独立的线程,开销大,不适宜资源共享。实现 Runnable 接口的话,则很容易的实现资源共享,而且能够利用线程池等工具,大大减小创立线程和销毁线程带来的损耗。
同时应用这两种办法创立线程:
先通过在 Thread 类结构器中传入匿名外部类(Runnable 实例)的形式创立线程,而后在此基础上重写了 Thread 类的 run 办法,最终输入:应用 Thread 形式创立。因为传入 Runnable 实例创立线程是调用 run 办法中的 target.run() 执行的,然而前面重写了 run 办法,导致此办法生效。
public class BothRunnableAndThread {public static void main(String[] args) {new Thread(() -> System.out.println("应用 Runnable 形式创立")) {
// 重写了 run 办法,笼罩了 run 里的三行代码
// runnable 传入进去却没有运行
@Override
public void run() {System.out.println("应用 Thread 形式创立");
}
}.start();}
}
创立线程的形式通常分为两类,除此之外,通过实现 Callable 接口、应用线程池等形式也能够创立线程,然而实质上都是继承 Thread 类或实现 Runnable 接口这两种办法。
1.4 线程初始化
不论通过哪种形式创立线程,都会调用线程初始化函数 init,能够通过不同的构造函数初始化线程的局部参数。如下 init 函数所示,一个新结构的线程对象是由其父线程来进行空间调配的,而子线程继承了父线程的 daemon、priority 等属性,同时还会调配一个惟一的 id 来标识这个线程。
public class Thread implements Runnable {public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {init(null, null, name, 0);
}
public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {init(null, target, name, 0);
}
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();
// 复制父线程的 daemon 和 priority 属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
this.target = target;
setPriority(priority);
// 复制父线程的 inheritableThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 调配线程 ID
tid = nextThreadID();}
}
2. 实现 Callable 接口
public class CallableDemo implements Callable<String> {
@Override
public String call() {return "hncboy";}
public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<String> ft = new FutureTask<>(new CallableStyle());
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
}
3. 应用线程池
public class ThreadPoolDemo {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(new TaskThread());
}
private static class TaskThread implements Runnable {
@Override
public void run() {System.out.println("hncboy");
}
}
}
4. 应用定时器
public class TimerTaskDemo {public static void main(String[] args) {Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {System.out.println("hncboy");
}
}, 1000, 1000);
}
}
二、线程的启动
1.start() 办法
1.1 办法含意
启动新线程:告诉 JVM 在有闲暇的状况下启动线程,实质是申请 JVM 来运行咱们的线程,线程何时运行由线程调度器来确定。该线程启动的同时会启动两个线程:第一个是用来执行 start 办法的父线程或主线程,第二个是被创立的子线程。
筹备工作:让线程处于就绪状态(曾经取得了除 CPU 以外的其余资源,如曾经设置了上下文,线程状态,栈等),做完筹备工作后,能力被 JVM 或操作系统调度到执行状态获取 CPU 资源,而后才会执行 run 办法。
反复调用 start():抛出异样 Exception in thread “main” java.lang.IllegalThreadStateException。一旦线程 start 当前,就从 NEW 状态进入到其余状态,比方 RUNNABLE,只有处于 NEW 状态的线程能力调用 start() 办法。
1.2 原理剖析
通过 threadStatus 属性来判断是否反复启动并抛出异样,理论的启动办法是 native 办法 start0()。
public class Thread implements Runnable {
/**
* 线程状态,初始化为 0,示意还未启
*/
private volatile int threadStatus = 0;
public synchronized void start() {
// 判断线程的状态,也就是判断是否启动,反复启动时抛出 IllegalThreadStateException
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将线程退出线程组
group.add(this);
boolean started = false;
try {start0();
started = true;
} finally {
try {if (!started) {
// 告知线程组该线程启动失败
group.threadStartFailed(this);
}
} catch (Throwable ignore) {}}
}
private native void start0();}
通过 /src/share/native/java/lang/Thread.c 可知,start0() 办法对应 JVM_StartThread 办法
static JNINativeMethod methods[] = {{"start0", "()V", (void *)&JVM_StartThread},
};
位于 /src/hotspot/share/prims/jvm.cpp 的 JVM_StartThread 办法中有段正文
// Since JDK 5 the java.lang.Thread threadStatus is used to prevent
// re-starting an already started thread, so we should usually find
// that the JavaThread is null. However for a JNI attached thread
// there is a small window between the Thread object being created
// (with its JavaThread set) and the update to its threadStatus, so we
// have to check for this
该段正文说自从 JDK5 后 应用 Thread 类的 threadStatus 属性去形式线程反复启动,接下来看下 /src/share/vm/runtime/thread.cpp 中的 start 办法,该办法中判断如果该线程是 Java 线程,则将该线程的状态改为 RUNNABLE。
void Thread::start(Thread* thread) {trace("start", thread);
if (!DisableStartThread) {if (thread->is_Java_thread()) {
// 这里调用 set_thread_status 办法将线程的状态批改为 RUNNALBE
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
os::start_thread(thread);
}
}
2.run() 办法
run() 只是 Thread 类的一个根本办法
public class Thread implements Runnable {
/** 省略 */
@Override
public void run() {if (target != null) {target.run();
}
}
}
3. 比拟两办法
输入:main 和 Thread-0
public class StartAndRunMethod {public static void main(String[] args) {Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
runnable.run();
new Thread(runnable).start();}
}
调用 start 办法才是真正意义上启动了一个线程,会经验线程的各个生命周期,如果间接调用 run 办法,则只是一般的调用办法,不会通过子线程去调用。
三、线程的终止
1. 过期的 suspend()、resume()、stop()
这三个办法曾经被破除,通过查看 Oracle 官网文档 能够得悉。应用 stop() 办法进行线程会开释线程的所有 monitor,该办法在终止一个线程时不会保障线程的资源失常开释,并且抛出 ThreadDeath 异样,通常是没有给予线程实现资源开释工作的机会,因而会导致程序呈现数据不同步。suspend() 办法则容易造成死锁,该办法在调用后,线程不会开释曾经占有的资源(比方锁),而是占有着资源进入挂起状态。resume() 必须和 suspend() 一起应用,当要复原指标线程的线程在调用 resume 之前尝试锁定这个 monitor,此时就会导致死锁。
2.volatile 标记位
通过 volatile 润饰的共享变量能够进行线程的终止。
2.1 胜利案例
子线程每隔 1 秒输入:继续运行。主线程在 2 秒后将 stop 置为 true,此时子线程 while 循环进行,子线程运行完结。循环只进行了两次。
public class RightVolatileDemo {
private static volatile boolean stop = false;
public static void main(String[] args) throws InterruptedException {new Thread(() -> {while (!stop) {System.out.println("继续运行");
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}).start();
TimeUnit.SECONDS.sleep(2);
stop = true;
}
}
运行后果如下:
继续运行
继续运行
2.3 失败案例
应用 volatile 的局限性,当线程陷入阻塞时,应用 volatile 润饰的变量无奈进行线程。
通过生产者消费者例子来演示阻塞状况下 volatile 的局限性,定义一个生产者类实现 Runnable 接口重写 run 办法,在 run 中当 volatile 润饰的 canceled 变量为 false 时,生产者通过 BlockingQueue 的 put 办法一直增加数据,当阻塞队列达到下限时,put 办法会阻塞。定义一个消费者类,通过 needMoreCount 办法判断消费者是否完结生产。
在主函数中初始化一个长度为 10 的阻塞队列,构建生产者和消费者实例,当消费者完结生产时,将生产者的 canceled 属性值改为 true,然而此时生产者依然在运行,因为生产者线程阻塞在 put 办法。这就是 volatile 标记位的局限性了。
public class WrongVolatileDemo {public static void main(String[] args) throws InterruptedException {
// 定义容量为 10 的阻塞队列
BlockingQueue<Integer> storage = new ArrayBlockingQueue<>(10);
// 启动生产者线程
Thread producerThread = new Thread(new Producer(storage));
producerThread.start();
Thread.sleep(1000);
// 启动消费者
Consumer consumer = new Consumer(storage);
while (consumer.needMoreCount()) {System.out.println("消费者生产:" + consumer.getStorage().take());
Thread.sleep(100);
}
System.out.println("消费者生产齐全完结");
// 此时生产者不应该持续生产
Producer.canceled = true;
}
/**
* 生产者
*/
private static class Producer implements Runnable {
static volatile boolean canceled = false;
private BlockingQueue<Integer> storage;
public Producer(BlockingQueue<Integer> storage) {this.storage = storage;}
@Override
public void run() {
int count = 1;
try {while (!canceled) {
// 如果队列满的话,put 办法会阻塞以后线程
storage.put(count);
System.out.println("生产者生产:" + count);
count++;
}
} catch (InterruptedException e) {e.printStackTrace();
} finally {System.out.println("生产者进行运行");
}
}
}
/**
* 消费者
*/
private static class Consumer {
private BlockingQueue<Integer> storage;
public Consumer(BlockingQueue<Integer> storage) {this.storage = storage;}
public BlockingQueue<Integer> getStorage() {return storage;}
public boolean needMoreCount() {return Math.random() < 0.95;
}
}
}
3.interrupt 办法
interrupt 翻译为中断,中断能够了解为线程的一个标识位属性,它示意一个运行中的线程是否被其余线程进行了中断操作。中断好比其余线程对该线程打了个招呼,其余线程通过调用该线程的 interrupt() 办法对其进行中断操作。
举几个例子来演示 interrupt 的不同用法。
3.1 不带阻塞的中断
该例子是最简略的中断,thread 线程启动后,休眠 1ms 再调用该对象的 interrupt 办法,此时线程中正在执行的循环检测到 Thread.currentThread().isInterrupted() 为 true 完结循环,输入 count 变量的值。
当线程调用本身的 interrupt 办法时,会将中断标记设置为 ture,线程外部循环会通过查看本身是否被中断来完结循环,而 线程外部的 isInterrupted() 办法就能判断线程是否被中断。
public class InterruptThreadWithoutSleep {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {
int count = 0;
// 查看本身是否被中断来完结循环
while (!Thread.currentThread().isInterrupted()) {count++;}
System.out.println(count);
});
thread.start();
Thread.sleep(1);
// 设置中断标记
thread.interrupt();}
}
3.2 带有阻塞的中断
该例子演示带有 sleep 阻塞的中断办法应用。sleep 办法应用须要抛出 InterruptedException,阐明该办法能够响应 interrupt 中断。在线程启动后,该线程会休眠 1s,而主线程在休眠 100ms 后会调用中断办法,此时该线程是处于阻塞状态,在阻塞状态下响应到中断,sleep 办法会抛出 InterruptedException,然而在抛出该异样前,JVM 会先将该线程的中断标识位革除,而后才抛出 InterruptedException,此时调用 isInterrupted() 办法将会返回 false。
如果在执行过程中,每次循环都会调用 sleep 办法,那么其实能够不须要每次迭代都通过 isInterrupted() 办法查看中断,因为 sleep 办法会响应中断。
public class InterruptThreadWithSleep {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
System.out.println("中断标记:" + Thread.currentThread().isInterrupted());
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();}
}
运行后果如下:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.hncboy.interrupt.InterruptThreadWithSleep.lambda$main$0(InterruptThreadWithSleep.java:15)
at java.lang.Thread.run(Thread.java:748)
中断标记:false
3.3 interrupt 相干办法
3.4.1 interrupt()
设置中断标记,最终调用 native 的 interrupt0() 办法设置中断标记。
public void interrupt() {if (this != Thread.currentThread())
// 权限查看
checkAccess();
synchronized (blockerLock) {
// IO 读写相干
Interruptible b = blocker;
if (b != null) {interrupt0();
b.interrupt(this);
return;
}
}
// 该办法肯定会执行
interrupt0();}
private native void interrupt0();
找到 interrupt0 办法对应的 JVM_Interrupt 办法,找到该办法代码。
JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread))
JVMWrapper("JVM_Interrupt");
// Ensure that the C++ Thread and OSThread structures aren't freed before we operate
oop java_thread = JNIHandles::resolve_non_null(jthread);
MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock);
// We need to re-resolve the java_thread, since a GC might have happened during the
// acquire of the lock
JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread));
if (thr != NULL) {Thread::interrupt(thr);
}
JVM_END
找到要害办法 Thread::interrupt 的代码。
void Thread::interrupt(Thread* thread) {trace("interrupt", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
os::interrupt(thread);
}
找到要害办法 os::interrupt 的代码,此时找到了设置中断标记的办法,Java 中的每个线程都与操作系统的线程一一对应,一个 osthread 就对应 Java 中的一个线程,如果 osthread 没有被设置为中断,则设置中断标记为 true。
void os::interrupt(Thread* thread) {assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
// 如果线程没有被中断
if (!osthread->interrupted()) {
// 设置中断标记为 true
osthread->set_interrupted(true);
// More than one thread can get here with the same value of osthread,
// resulting in multiple notifications. We do, however, want the store
// to interrupted() to be visible to other threads before we execute unpark().
OrderAccess::fence();
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;}
// For JSR166. Unpark even if interrupt status already was set
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;}
3.4.2 isInterrupted() 和 interrupted()
返回线程的中断状态。interrupted 为静态方法,两个办法都调用了 isInterrupted 办法,不过传入的参数不一样,true 示意革除中断状态,false 示意不革除。
Thread.interrupted() 在哪个线程里被调用,就返回哪个线程的中断标记。
public boolean isInterrupted() {return isInterrupted(false);
}
public static boolean interrupted() {return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);
3.4.2 综合例子
public class InterruptComprehensive {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {});
// 启动线程
thread.start();
// 设置中断标记
thread.interrupt();
// 获取中断标记,被中断了返回 true
System.out.println("isInterrupted:" + thread.isInterrupted());
// 获取中断标记并重置,interrupted 静态方法调用的是执行它的线程,也就是 main 线程,返回 false
System.out.println("isInterrupted:" + thread.interrupted());
// 获取中断标记并重置,Main 函数没有没有被中断,返回 false
System.out.println("isInterrupted:" + Thread.interrupted());
// 获取中断标记,中断标记没有被革除,返回 true
System.out.println("isInterrupted:" + thread.isInterrupted());
thread.join();
System.out.println("Main thread is over.");
}
}
运行后果:
isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true
Main thread is over.
3.4 能响应中断的局部办法
有些阻塞办法是不可中断的,例如 I/O 阻塞和 synchronized 阻塞,须要针对某一些锁或某一些 I/O 给出特定的计划。
- Object.wait()/wait(long)/wait(long, int)
- Thread.sleep(long)/sleep(long, int)
- Thread.join()/join(long)/join(long, int)
- java.util.concurrent.BlockingQueue.take()/put(E)
- java.util.concurrent.locks.Lock.lockInterruptibly()
- java.util.concurrent.CountDownLatch.await()
- java.util.concurrent.CyclicBarrier.await()
- java.util.concurrent.Exchanger.exchange(V)
- java.nio.channels.InterruptibleChannel 相干办法
- java.nio.channels.Selector 相干办法
3.5 InterruptedException 异样解决
3.5.1 传递中断
当在 run 中调用了一个有异样的办法时,该异样应该在办法中用 throws 申明,传递到 run 办法,而不是在办法中捕捉,此时可能会造成不可意料的后果。throwInMethod2() 为正确做法,throwInMethod1() 为错误做法。
public class HandleInterruptedException implements Runnable {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new HandleInterruptedException());
thread.start();
Thread.sleep(1000);
thread.interrupt();}
@Override
public void run() {while (true) {System.out.println("work");
try {throwInMethod2();
} catch (InterruptedException e) {System.out.println("保留日志、进行程序");
e.printStackTrace();}
}
/*while (true) {System.out.println("go");
throwInMethod1();}*/
}
private void throwInMethod2() throws InterruptedException {Thread.sleep(2000);
}
private void throwInMethod1() {
try {Thread.sleep(2000);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}
3.5.2 从新设置中断状态
因为阻塞抛出 InterruptedException 异样后,会革除中断状态。能够在 catch 子语句中调用 Thread.currentThread().interrupt() 办法来复原设置中断状态,以便于在后续的执行中,仍然可能查看到方才产生了中断。
public class HandleInterruptedException2 implements Runnable {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new HandleInterruptedException2());
thread.start();
Thread.sleep(1000);
thread.interrupt();}
@Override
public void run() {while (true) {if (Thread.currentThread().isInterrupted()) {System.out.println("产生中断,程序运行完结");
break;
}
System.out.println("work");
reInterrupt();}
}
private void reInterrupt() {
try {Thread.sleep(2000);
} catch (InterruptedException e) {Thread.currentThread().interrupt();
e.printStackTrace();}
}
}
《Java 并发编程的艺术》
深刻了解 Thread.run() 底层实现