Java基础知识_Thread
一、Thread线程类API
实现多线程从本质上都是由Thread类来进行操作的,我们来看看Thread类的一些重要的知识点。
Thread这个类很大,所以就看一些常见的,重要的方法。
1.1 设置线程名
我们在使用多线程的时候,想要查看线程名是很简单的,调用Thread.currentThread().getname即可。
如果没有做什么设置,我们会发现线程的名字是这样子的:主线程叫main,其他线程是Thread-x
下面我们来看看是怎么命名的
1 public Thread() { 2 init(null, null, "Thread-" + nextThreadNum(), 0); 3 }
nextThreadNum的方法实现是这样的
1 private static synchronized int nextThreadNum() { 2 return threadInitNumber++; 3 }
基于这么一个变量,线程的初始化数量
1 private static int threadInitNumber;
点进去看看init方法就可以确定了
1 private void init(ThreadGroup g, Runnable target, String name, 2 long stackSize, AccessControlContext acc) { 3 if (name == null) { 4 throw new NullPointerException("name cannot be null"); 5 } 6 7 this.name = name.toCharArray();
看到这里,如果我们想要为线程起个名字也是很简单的。Thread给我们提供了构造方法
1 public Thread(Runnable target, String name) { 2 init(null, target, name, 0); 3 }
当然了,我们还可以通过setName(String name)方法来改掉线程的名字,我们来看看方法的实现
1 public final synchronized void setName(String name) { 2 checkAccess(); 3 this.name = name.toCharArray(); 4 if (threadStatus != 0) { 5 setNativeName(name); 6 } 7 }
检查是否有权限修改
1 public void checkAccess(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 implements Runnable { 2 3 @Override 4 public void run() { 5 // 打印出当前线程的名字 6 System.out.println(Thread.currentThread().getName()); 7 } 8 }
1 public class MyThreadDemo { 2 public static void main(String[] args) { 3 4 5 MyThread myThread = new MyThread(); 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(int newPriority) { 2 ThreadGroup g; 3 checkAccess(); 4 if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { 5 throw new IllegalArgumentException(); 6 } 7 if((g = getThreadGroup()) != null) { 8 if (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 * <p> An invocation of this method behaves in exactly the same 5 * way as the invocation 6 * 7 * <blockquote> 8 * {@linkplain #join(long) join}{@code (0)} 9 * </blockquote> 10 * 11 * @throws InterruptedException 12 * if any thread has interrupted the current thread. The 13 * <i>interrupted status</i> of the current thread is 14 * cleared when this exception is thrown. 15 */ 16 public final void join() throws InterruptedException { 17 join(0); 18 }
我们进去看看具体的实现:
1 /** 2 * Waits at most {@code millis} milliseconds for this thread to 3 * die. A timeout of {@code 0} means to wait forever. 4 * 5 * <p> This implementation uses a loop of {@code this.wait} calls 6 * conditioned on {@code this.isAlive}. As a thread terminates the 7 * {@code this.notifyAll} method is invoked. It is recommended that 8 * applications not use {@code wait}, {@code notify}, or 9 * {@code notifyAll} on {@code Thread} instances. 10 * 11 * @param millis 12 * the time to wait in milliseconds 13 * 14 * @throws IllegalArgumentException 15 * if the value of {@code millis} is negative 16 * 17 * @throws InterruptedException 18 * if any thread has interrupted the current thread. The 19 * <i>interrupted status</i> of the current thread is 20 * cleared when this exception is thrown. 21 */ 22 public final synchronized void join(long millis) 23 throws InterruptedException { 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( new Runnable(){ 2 public void run(){ 3 // 若未发生中断,就正常执行任务 4 while(!Thread.currentThread.isInterrupted()){ 5 // 正常任务代码…… 6 } 7 // 中断的处理代码…… 8 doSomething(); 9 } 10 } ).start();
再次说明:调用interrupt并不是要真正终止掉当前线程,仅仅是设置了一个中断标志。这个中断标志可以给我们用来判断什么时候该干什么活。什么时候中断由我们自己来决定,这样就可以安全地终止线程了。
我们来看看源码是怎么讲的吧
1 /** 2 * Interrupts this thread. 3 * 4 * <p> Unless the current thread is interrupting itself, which is 5 * always permitted, the {@link #checkAccess() checkAccess} method 6 * of this thread is invoked, which may cause a {@link 7 * SecurityException} to be thrown. 8 * 9 * <p> If this thread is blocked in an invocation of the {@link 10 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link 11 * Object#wait(long, int) wait(long, int)} methods of the {@link Object} 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 it 15 * will receive an {@link InterruptedException}. 16 * 17 * <p> 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 interrupt 20 * status will be set, and the thread will receive a {@link 21 * java.nio.channels.ClosedByInterruptException}. 22 * 23 * <p> If this thread is blocked in a {@link java.nio.channels.Selector} 24 * then the thread's interrupt status will be set and it will return 25 * immediately from the selection operation, possibly with a non-zero 26 * value, just as if the selector's {@link 27 * java.nio.channels.Selector#wakeup wakeup} method were invoked. 28 * 29 * <p> If none of the previous conditions hold then this thread's interrupt 30 * status will be set. </p> 31 * 32 * <p> Interrupting a thread that is not alive need not have any effect. 33 * 34 * @throws SecurityException 35 * if the current thread cannot modify this thread 36 * 37 * @revised 6.0 38 * @spec JSR-51 39 */ 40 public void interrupt() { 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 class Main { 2 /** 3 * @param args 4 */ 5 public static void main(String[] args) { 6 Main main = new Main(); 7 8 // 创建线程并启动 9 Thread t = new Thread(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


浙公网安备 33010602011771号