一、Thread线程类API
实现多线程从本质上都是由Thread类来进行操作的,我们来看看Thread类的一些重要的知识点。
Thread这个类很大,所以就看一些常见的,重要的方法。
1.1 设置线程名
我们在使用多线程的时候,想要查看线程名是很简单的,调用Thread.currentThread().getname即可。
如果没有做什么设置,我们会发现线程的名字是这样子的:主线程叫main,其他线程是Thread-x
下面我们来看看是怎么命名的
1 publicThread() {2 init(null, null, "Thread-" + nextThreadNum(), 0);3 }
nextThreadNum的方法实现是这样的
1 private static synchronized intnextThreadNum() {2 return threadInitNumber++;3 }
基于这么一个变量,线程的初始化数量
1 private static int threadInitNumber;
点进去看看init方法就可以确定了
1 private voidinit(ThreadGroup g, Runnable target, String name,2 longstackSize, AccessControlContext acc) {3 if (name == null) {4 throw new NullPointerException("name cannot be null");5 }6
7 this.name = name.toCharArray();
看到这里,如果我们想要为线程起个名字也是很简单的。Thread给我们提供了构造方法
1 publicThread(Runnable target, String name) {2 init(null, target, name, 0);3 }
当然了,我们还可以通过setName(String name)方法来改掉线程的名字,我们来看看方法的实现
1 public final synchronized voidsetName(String name) {2 checkAccess();3 this.name =name.toCharArray();4 if (threadStatus != 0) {5 setNativeName(name);6 }7 }
检查是否有权限修改
1 public voidcheckAccess(Thread t) {2 if (t == null) {3 throw new NullPointerException("thread can't be null");4 }5 if (t.getThreadGroup() ==rootGroup) {6 checkPermission(SecurityConstants.MODIFY_THREAD_PERMISSION);7 } else{8 //just return
9 }10 }
1.2 守护线程
守护线程是为其他线程服务的
垃圾回收线程就是守护线程~
守护线程有一个特点
当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止了
也就是说:守护线程为一个服务线程,没有服务对象就没必要继续运行了。
使用线程的时候要注意的地方
在线程启动前设置为守护线程,方法是setDaemon(boolean on)
使用守护线程不要访问共享资源和文件,因为它可能在任何时候就挂掉了
守护线程中产生的新线程也是守护线程
测试以下
1 public class MyThread implementsRunnable {2
3 @Override4 public voidrun() {5 //打印出当前线程的名字
6 System.out.println(Thread.currentThread().getName());7 }8 }
1 public classMyThreadDemo {2 public static voidmain(String[] args) {3
4
5 MyThread myThread = newMyThread();6
7 //带参构造方法给线程起名字
8 Thread thread1 = new Thread(myThread, "线程1");9 Thread thread2 = new Thread(myThread, "守护");10
11 //设置为守护线程
12 thread2.setDaemon(true);13
14 thread1.start();15 thread2.start();16 System.out.println("主线程"+Thread.currentThread().getName());17 }18 }
结果
1.3 优先级线程
线程优先级高仅仅表示线程获取CPU的时间片的几率高,但这不是一个确定因素,线程的优先级是高度依赖于操作系统的
可以看到的是Java提供的优先级默认是5,最低是1 ,最高是10
实现
1 public final void setPriority(intnewPriority) {2 ThreadGroup g;3 checkAccess();4 if (newPriority > MAX_PRIORITY || newPriority g.getMaxPriority()) {9 newPriority =g.getMaxPriority();10 }11 setPriority0(priority =newPriority);12 }13 }
先参数检查,如果存在线程组,那么该线程的优先级不能比组的优先级高
setPriority0是一个本地native方法
1 private native void setPriority0(int newPriority);
1.4 线程的生命周期
在上篇介绍过了线程有三个基本状态:执行就绪阻塞
在Java中我们就有了这个图,Thread上有很多方法都是用来切换线程的状态的,这一部分是重点
下面就来讲解与线程生命周期相关的方法
1.4.1 sleep方法
调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态。
1.4.2 yield方法
调用yield方法会先让别的线程执行,但是不确保真正让出
意思是:我有空,可以的话,就让你们执行,实际上很少用,它不确保一定会会让出CPU
1.4.3 join方法
调用join方法会等待该线程执行完毕后才执行别的线程
1 /**
2 * Waits for this thread to die.3 *4 *
An invocation of this method behaves in exactly the same5 * way as the invocation6 *7 *
8 * {@linkplain#join(long) join}{@code(0)}9 *10 *11 *@throwsInterruptedException12 * if any thread has interrupted the current thread. The13 * interrupted status of the current thread is14 * cleared when this exception is thrown.15 */
16 public final void join() throwsInterruptedException {17 join(0);18 }
我们进去看看具体的实现:
1 /**
2 * Waits at most {@codemillis} milliseconds for this thread to3 * die. A timeout of {@code0} means to wait forever.4 *5 *
This implementation uses a loop of {@codethis.wait} calls6 * conditioned on {@codethis.isAlive}. As a thread terminates the7 * {@codethis.notifyAll} method is invoked. It is recommended that8 * applications not use {@codewait}, {@codenotify}, or9 * {@codenotifyAll} on {@codeThread} instances.10 *11 *@parammillis12 * the time to wait in milliseconds13 *14 *@throwsIllegalArgumentException15 * if the value of {@codemillis} is negative16 *17 *@throwsInterruptedException18 * if any thread has interrupted the current thread. The19 * interrupted status of the current thread is20 * cleared when this exception is thrown.21 */
22 public final synchronized void join(longmillis)23 throwsInterruptedException {24 long base =System.currentTimeMillis();25 long now = 0;26
27 if (millis < 0) {28 throw new IllegalArgumentException("timeout value is negative");29 }30
31 if (millis == 0) {32 while(isAlive()) {33 wait(0);34 }35 } else{36 while(isAlive()) {37 long delay = millis -now;38 if (delay <= 0) {39 break;40 }41 wait(delay);42 now = System.currentTimeMillis() -base;43 }44 }45 }
0表示永远等待,循环调用wait方法,当线程终止了会调用notifyAll方法来唤醒
wait方法是在Object上定义的,它是native本地方法,所以就看不了了
wait方法实际上它也是计时等待的一种
1.4.3 interrupt方法
线程中断在之前的版本有stop方法,但是被设置过时了。现在已经没有强制线程终止的方法了。
由于stop方法可以让一个线程A终止掉另一个线程B
被终止的线程B会立刻释放锁,这可能会让对象处于不一致的状态
线程A也不知道线程B什么时候能够被终止掉,万一线程B还处理运行计算阶段,线程A调用stop方法终止B,那就很无辜了。
总而言之,stop就很暴力,不安全,所以被设置过时了。
我们一般使用的是interrupt来请求终止线程
要注意的是:interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号,告诉它,它应该要结束了(明白这点非常重要)
也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理了什么业务了。
具体到底是中断还是继续运行应该由被通知的线程自己处理
1 Thread t1 = new Thread( newRunnable(){2 public voidrun(){3 //若未发生中断,就正常执行任务
4 while(!Thread.currentThread.isInterrupted()){5 //正常任务代码……
6 }7 //中断的处理代码……
8 doSomething();9 }10 } ).start();
再次说明:调用interrupt并不是要真正终止掉当前线程,仅仅是设置了一个中断标志。这个中断标志可以给我们用来判断什么时候该干什么活。什么时候中断由我们自己来决定,这样就可以安全地终止线程了。
我们来看看源码是怎么讲的吧
1 /**
2 * Interrupts this thread.3 *4 *
Unless the current thread is interrupting itself, which is5 * always permitted, the {@link#checkAccess() checkAccess} method6 * of this thread is invoked, which may cause a {@link
7 * SecurityException} to be thrown.8 *9 *
If this thread is blocked in an invocation of the {@link
10 * Object#wait() wait()}, {@linkObject#wait(long) wait(long)}, or {@link
11 * Object#wait(long, int) wait(long, int)} methods of the {@linkObject}12 * class, or of the {@link#join()}, {@link#join(long)}, {@link
13 * #join(long, int)}, {@link#sleep(long)}, or {@link#sleep(long, int)},14 * methods of this class, then its interrupt status will be cleared and it15 * will receive an {@linkInterruptedException}.16 *17 *
If this thread is blocked in an I/O operation upon an {@link
18 * java.nio.channels.InterruptibleChannel InterruptibleChannel}19 * then the channel will be closed, the thread's interrupt20 * status will be set, and the thread will receive a {@link
21 * java.nio.channels.ClosedByInterruptException}.22 *23 *
If this thread is blocked in a {@linkjava.nio.channels.Selector}24 * then the thread's interrupt status will be set and it will return25 * immediately from the selection operation, possibly with a non-zero26 * value, just as if the selector's {@link
27 * java.nio.channels.Selector#wakeup wakeup} method were invoked.28 *29 *
If none of the previous conditions hold then this thread's interrupt30 * status will be set.
31 *32 *Interrupting a thread that is not alive need not have any effect.33 *34 *@throwsSecurityException35 * if the current thread cannot modify this thread36 *37 * @revised 6.038 * @spec JSR-5139 */
40 public voidinterrupt() {41 if (this !=Thread.currentThread())42 checkAccess();43
44 synchronized(blockerLock) {45 Interruptible b =blocker;46 if (b != null) {47 interrupt0(); //Just to set the interrupt flag
48 b.interrupt(this);49 return;50 }51 }52 interrupt0();53 }
中断当前线程,只能自己调用,不然会抛出安全异常。
前面说了,Java设计者设置中断标志的目的是想由被通知的线程自己处理,而这些方式都阻塞掉了。
被阻塞掉的线程调用中断方法是不合理的(不允许中断已经阻塞的线程)
因为可能会造成 中断无效。
以上三种情况都不发生,才能将对应的标志位置换
中断一个不活动的线程是没有意义的,首先检查有没有权限,任何看看是不是阻塞的线程调用
如果是,抛出异常,将中断标志位改为false
如果很顺利,就可以修改到标志位了。
再来看看刚才说抛出的异常是什么东东吧
所以说,interrupt方法根本不会对线程的状态造成影响的,它仅仅设置一个标志位罢了。
interrupt线程中断还有另外两个方法(检查该线程是否被中断)
静态方法:interrupted-->会清除中断标志位
实例方法:isInterrupted-->不会清除中断标志位
如果阻塞线程调用了interrupt方法,那么会抛出异常,设置标志位为false,同时该线程会退出阻塞
来测试一下
1 public classMain {2 /**
3 *@paramargs4 */
5 public static voidmain(String[] args) {6 Main main = newMain();7
8 //创建线程并启动
9 Thread t = newThread(main.runnable);10 System.out.println("This is main ");11 t.start();12
13 try{14
15 //在 main线程睡个3秒钟
16 Thread.sleep(3000);17 } catch(InterruptedException e) {18 System.out.println("In main");19 e.printStackTrace();20 }21
22 //设置中断
23 t.interrupt();24 }25
26 Runnable runnable = () ->{27 int i = 0;28 try{29 while (i < 1000) {30
31 //睡个半秒钟我们再执行
32 Thread.sleep(500);33
34 System.out.println(i++);35 }36 } catch(InterruptedException e) {37
38
39 //判断该阻塞线程是否还在
40 System.out.println(Thread.currentThread().isAlive());41
42 //判断该线程的中断标志位状态
43 System.out.println(Thread.currentThread().isInterrupted());44
45 System.out.println("In Runnable");46 e.printStackTrace();47 }48 };49 }
接下来我们分析一下它的执行流程是怎么样的:
1、在main线程中睡了3秒钟,因此我们去新的线程中执行了。
2、每次睡半秒钟才执行
3、在睡半秒钟的同时,main线程的3秒钟过去了,main线程设置了该线程中断
4、抛出了异常,该阻塞线程(睡半秒钟立马停止了阻塞)在catch方法还存活着,线程的中断标志位变成了false