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

 

posted @ 2019-09-11 20:28  chyblogs  阅读(205)  评论(0)    收藏  举报