一、线程的创立
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: trueisInterrupted: falseisInterrupted: falseisInterrupted: trueMain 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()底层实现