Thread中的实例方法
调用Thread中的方法的时候,在线程类中,有两种方式,一定要理解这两种方式的区别
(1)this.XXX()
这种调用方式表示的线程是线程实例本身
(2)Thread.currentThread.XXX()或Thread.XXX()
上面两种写法是一样的意思。这种调用方式表示的线程是正在执行Thread.currentThread.XXX()所在代码块的线程
简单方法
方法名 |
描述 |
start() |
就是通知”线程规划器”,此线程可以运行了,正在等待CPU调用线程对象得run()方法,产生一个异步执行的效果。 |
isAlive() |
测试线程是否处于活动状态,只要线程启动且没有终止,方法返回的就是true |
getId() |
在一个Java应用中,有一个long型的全局唯一的线程ID生成器threadSeqNumber,每new出来一个线程都会把这个自增一次,并赋予线程的tid属性,这个是Thread自己做的,用户无法执行一个线程的Id。 |
getName() |
获取线程名,如果指定,那么线程的名字就是我们自己指定的,如果不指定,那么Thread中有一个int型全局唯一的线程初始号生成器threadInitNum,Java先把threadInitNum自增,然后以”Thread-threadInitNum”的方式来命名新生成的线程 |
stop() |
终止正在运行的线程,已过时,不建议使用 |
因为使用 stop 方法是很危险的,就象突然关闭计算机电源, 而不是按正常程序关机。在程序中,我们是不能随便 stop 一个线程的,我们无法知道这个线程正运行在什么状态,它可能持有某把锁,强行中断线程可能导致锁不能释放的问题;或者线程可能 在操作数据库,强行中断线程可能导致数据不一致的问题。正由于使用 stop 方法来终止线程可能会产生不可预料的结果,因此并不推荐使用。
稍稍需要注意的方法
线程优先级
getPriority()和setPriority(int newPriority)
在 Thread 类中有一个实例属性和两个实例方法,专用于进行线程优先级相关的操作,与线程 优先级相关的成员属性为:
这两个方法用于获取和设置线程的优先级,优先级高的CPU得到的CPU资源比较多,设置优先级有助于帮”线程规划器”确定下一次选择哪一个线程优先执行。换句话说,两个在等待CPU的线程,优先级高的线程越容易被CU选择执行。—-> 这个不是绝对的,不能太过于依赖这个设置,看下面的例子就能理解了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class PriorityDemo {
public static final int SLEEP_GAP = 1000;
static class PrioritySetThread extends Thread { static int threadNo = 1;
public PrioritySetThread() { super("thread-" + threadNo); threadNo++; }
public long opportunities = 0;
@Override public void run() { for (int i = 0; ; i++) { opportunities++; } } }
public static void main(String args[]) throws InterruptedException { PrioritySetThread[] threads = new PrioritySetThread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new PrioritySetThread(); threads[i].setPriority(i + 1); } for (int i = 0; i < threads.length; i++) { threads[i].start(); } Thread.sleep(SLEEP_GAP); for (int i = 0; i < threads.length; i++) { threads[i].stop(); } for (int i = 0; i < threads.length; i++) { System.out.println(threads[i].getName() + "优先级为:" + threads[i].getPriority() + ",机会值为:" + threads[i].opportunities ); } } }
thread-1优先级为:1,机会值为:598373125 thread-2优先级为:2,机会值为:633411616 thread-3优先级为:3,机会值为:715661603 thread-4优先级为:4,机会值为:643894812 thread-5优先级为:5,机会值为:771752736 thread-6优先级为:6,机会值为:764895904 thread-7优先级为:7,机会值为:779410974 thread-8优先级为:8,机会值为:780571797 thread-9优先级为:9,机会值为:792711249 thread-10优先级为:10,机会值为:797973867
|
如果按照理论来预测结果,那么优先级越高的线程,获得的 CPU 时间片就会越多,从而导致opportunities 就会越大。但是示例中10 条线程停下来之后,结果并非如此,分析案例的执行结果,可以看出以下结论:
设置守护进程
Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,在 JVM 属于保姆的地位:只要 JVM 实例中尚存在任何一个用户线程没有结束,守护线程就能执行自己工作;只有当最后一个用户线程结束,守护线程随着 JVM 一同结束工作。
用户线程和用户线程在 JVM 虚拟机进程终止的方向不同:
isDaeMon、setDaemon(boolean on)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class DaemonDemo extends Thread { private int i = 0;
@Override public void run() { try { while (true) { i++; System.out.println("i = " + i); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } }
public static void main(String[] args) { try { DaemonDemo mt = new DaemonDemo(); mt.setDaemon(true); mt.start(); Thread.sleep(5000); System.out.println("主线程已结束!"); } catch (InterruptedException e) { e.printStackTrace(); } } }
i = 1 i = 2 i = 3 i = 4 i = 5 主线程已结束! i = 6
|
守护线程有如下特点:
- 守护线程必须在启动前,将其守护状态设置为 true;启动之后,不能再将用户线程设置 为守护线程。否则,JVM 会抛出一个 InterruptedException 异常
- 守护线程存在被 JVM 强行终止的风险,所以,在守护线程中尽量不去访问系统资源, 如文件句柄、数据库连接等等。守护线程被强行终止时,可能会引发系统资源操作的不负责任的 中断,从而导致资源不可逆的损坏。
- 默认情况下,守护线程创建的线程,也是守护线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class DaemonDemo02 {
static class DaemonThread extends Thread { @Override public void run() { try { while (true) { Thread.sleep(500); System.out.println(Thread.currentThread() + "===>守护线程状态为:" + this.isDaemon()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public static void main(String[] args) throws InterruptedException {
Thread testThread = new Thread(() -> { for (int i = 0; i < 5; i++) { DaemonThread daemonThread = new DaemonThread(); daemonThread.start(); } });
testThread.setDaemon(true); testThread.start();
Thread.sleep(1000); System.out.println(Thread.currentThread() + "线程结束"); } }
Thread[Thread-2,5,main]===>守护线程状态为:true Thread[Thread-5,5,main]===>守护线程状态为:true Thread[Thread-1,5,main]===>守护线程状态为:true Thread[Thread-3,5,main]===>守护线程状态为:true Thread[Thread-4,5,main]===>守护线程状态为:true Thread[main,5,main]线程结束
|
线程中断
interrupt()
在JAVA中,我们使用interrupt()**来进行线程中断,但是,此方法本质不是用来中断一个线程,而是将线程设置为中断状态。**它有两个作用:
如果此线程处于阻塞状态(如调用了 Object.wait 方法),则会立马退出阻塞,并抛出InterruptedException 异常,线程就可以通过捕获 InterruptedException 来做一定的处理,然后让线程退出。更确切的说,如果线程被 Object.wait, Thread.join 和 Thread.sleep 三种方法之一阻塞,此时调用该线程的 interrupt()方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。
如果线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为 true。所以,程序可以在适当的位置,通过调**isInterrupted()**方法来查看自己是否被中断,并做退出操作。
如果线程的 interrupt 方法先被调用,然后线程开始调用阻塞方法进入阻塞状态,InterruptedException 异常依旧会抛出。如果线程捕获 InterruptedException 异常后, 继续调用阻塞方法,将不再触发 InterruptedException 异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| public class InterruptDemo { public static final int SLEEP_GAP = 5000;
static class SleepThread extends Thread { static int threadSeqNumber = 1;
public SleepThread() { super("sleepThread-" + threadSeqNumber); threadSeqNumber++; }
@Override public void run() { try { System.out.println(getName() + " 进入睡眠."); Thread.sleep(SLEEP_GAP); } catch (InterruptedException e) { e.printStackTrace(); System.out.println(getName() + " 发生被异常打断."); return; } System.out.println(getName() + " 运行结束."); } }
public static void main(String[] args) throws InterruptedException { Thread thread1 = new SleepThread(); thread1.start(); Thread thread2 = new SleepThread(); thread2.start(); TimeUnit.SECONDS.sleep(2); thread1.interrupt(); thread1.interrupt(); TimeUnit.SECONDS.sleep(5); thread2.interrupt(); TimeUnit.SECONDS.sleep(1); System.out.println("程序运行结束."); } }
sleepThread-1 进入睡眠. sleepThread-2 进入睡眠. java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.cheng.base.method.InterruptDemo$SleepThread.run(InterruptDemo.java:31) sleepThread-1 发生被异常打断. sleepThread-2 运行结束. 程序运行结束.
|
从结果可以看到:
sleepThread-1 线程在大致睡眠了 2 秒后,被主线程打断(或者中断)。被打断的 sleepThread-1 线程停止睡眠,并捕获到 InterruptedException 受检异常。程序在异常处理时,进行了直接返回,其后面的执行逻辑被跳过。
捕获了 InterruptedException 异常以后,再次调用interrupt()方法,InterruptedException 异常不再被捕获。
sleepThread-2 线程在睡眠了 7 秒后,被主线程中断,但是在 sleepThread-2 线程被中断的时候,已经执行结束,所以 thread2.interrupt() 中断操作没有发生实质性的效果。
总结:
Thread.interrupt( )方法并不像 Thread.stop( )方法那样中止一个正在运行的线程,其作用是设置线程的中断状态位(为 true),至于线程是死亡、还是等待新的任务或是继续运行至下一步, 就取决于这个程序本身。线程可以不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为 true)。总之,Thread.interrupt( )方法只是改变中断状态,不会中断一个正在运行的线程。
interrupt()
在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞状态。换句话说,没有被阻塞的线程,调用interrupt()方法是不起作用的。
isInterrupted()
查看线程是否已经中断,但不清除状态标识。这个和interrupt()方法一样。
线程是否停止执行,需要用户程序去监视线程的isInterrupted()状态,并做对应的处理。 下边就来看一下如何使用 isInterrupted()实例方法监视线程的中断状态,如果发现线程被中断,如何做出对应的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class IsInterruptedDemo {
public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { System.out.println("线程启动了"); while (true) { System.out.println(isInterrupted()); sleepMilliSeconds(5000); if (isInterrupted()) { System.out.println("线程结束了"); return; } } } }; thread.start(); sleepMilliSeconds(2000); thread.interrupt(); sleepMilliSeconds(2000); thread.interrupt(); }
public static void sleepMilliSeconds(int millisecond) { LockSupport.parkNanos(millisecond * 1000L * 1000L); } }
线程启动了 false 线程结束了
|
线程合并
join()
调用 join()方法的语句可以理解为合并点,合并可以理解为:线程 A 需要在合并点等待,一直等线程 B 执行完成,或者等待超时。
在看join()方法之前请可以先看看线程间通信一文,即wait()/notify()/notifyAll()机制已熟练掌握,才能更好的理解下文。
join()方法的作用是等待线程销毁。join()方法反应的是一个很现实的问题,比如main线程的执行时间是1s,子线程的执行时间是10s,但是主线程依赖子线程执行完的结果,此时有两个方案:
- 像生产者/消费者模型一样,搞一个缓冲区,子线程执行完把数据放在缓冲区中,通知main线程去拿,这样就不会浪费main线程的时间了
- 使用join()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class JoinDemo extends Thread {
@Override public void run() { try { System.out.println("子线程休眠开始"); Thread.sleep(5000); System.out.println("子线程休眠完毕"); } catch (InterruptedException e) { e.printStackTrace(); } }
public static void main(String[] args) throws InterruptedException { JoinDemo t1 = new JoinDemo(); t1.start(); t1.join(); System.out.println("当前线程继续执行"); } }
子线程休眠开始 子线程休眠完毕 当前线程继续执行
|
从结果看,join()方法会使调用join()方法的线程(t1线程)所在的线程(main线程)无限阻塞,直到调用join()方法的线程销毁为止,此例中main线程就会无限期阻塞直到t1的run()方法执行完毕。
Join()方法有三个重载版本:
1 2 3 4 5 6 7 8
| public final void join() throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException;
public final synchroinzed void join(long millis, int nanos) throws InterruptedException;
|
使用 join()方法的要点:
join()方法是实例方法,需要使用被合并线程的句柄(或者指针、变量)去调用,如 threadb.join() 。 执行threadb.join( ) 这行代码的当前线程为合并线程, 进入 TIMED_WAITING 等待状态,出让 CPU。
如果设置了被合并线程的执行时间 millis(或者 millis+nanos),并不能保证当前线程 一定会在 millis 时间后变为 RUNNABLE。
如果主动方合并线程的在等待时被中断,则会抛出 InterruptedException 受检异常。
join()方法的一个重点是要区分出和sleep()方法的区别。join(2000)也是可以的,表示调用join()方法所在的线程最多等待2000ms,两者的区别在于:
sleep(2000)不释放锁,join(2000)释放锁,因为join()方法内部使用的是wait(),因此会释放锁。看一下join(long millis)的源码就知道了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0;
if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); }
if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
|
Thread中的静态方法
Thread类中的静态方法表示操作的线程是”正在执行静态方法所在的代码块的线程“。为什么Thread类中的静态方法是为了能对CPU当前正在运行的线程进行操作。
获取当前线程
currentThread()
currentThread()方法返回的是对当前正在执行线程对象的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class MyThread04 extends Thread { static { System.out.println("静态块的打印:" + Thread.currentThread().getName()); }
public MyThread04() { System.out.println("构造方法的打印:" + Thread.currentThread().getName()); }
@Override public void run() { System.out.println("run()方法的打印:" + Thread.currentThread().getName()); } }
public class MyThread01 extends Thread{ public static void main(String[] args) { MyThread04 mt = new MyThread04(); mt.start(); } }
静态块的打印:main 构造方法的打印:main run()方法的打印:Thread-0
|
这个例子说明了,线程类的构造方法、静态块是被main线程调用的,而线程类的run()方法才是应用线程自己调用的。在这个例子的基础上,再深入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class MyThread05 extends Thread { public MyThread05() { System.out.println("MyThread5----->Begin"); System.out.println("Thread.currentThread().getName()----->" + Thread.currentThread().getName()); System.out.println("this.getName()----->" + this.getName()); System.out.println("MyThread5----->end"); }
@Override public void run() { System.out.println("run----->Begin"); System.out.println("Thread.currentThread().getName()----->" + Thread.currentThread().getName()); System.out.println("this.getName()----->" + this.getName()); System.out.println("run----->end"); } }
public class MyThread01 extends Thread { public static void main(String[] args) { MyThread05 thread05 = new MyThread05(); thread05.start(); } }
MyThread5----->Begin Thread.currentThread().getName()----->main this.getName()----->Thread-0 MyThread5----->end run----->Begin Thread.currentThread().getName()----->Thread-0 this.getName()----->Thread-0 run----->end
|
就是”this.XXX()”和”Thread.currentThread().XXX()”的区别,这个就是最好的例子。必须要清楚的一点就是:当前执行的Thread未必就是Thread本身。
线程休眠
sleep(long millis)
sleep(long millis)方法的作用是在指定的毫秒内让当前”正在执行的线程“休眠(暂停执行)。这个”正在执行的线程“是关键,指的是Thread.currentThread()返回的线程。根据JDK API的说法,”该线程不丢失任何监视器的所属权”,简单说就是sleep代码上下文如果被加锁了,锁依然在,但是CPU资源会让出给其他线程。看一下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class MyThread06 extends Thread { @Override public void run() { try { System.out.println("run threadName = " + this.getName() + " begin"); Thread.sleep(2000); System.out.println("run threadName = " + this.getName() + " end"); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class MyThread01 extends Thread { public static void main(String[] args) { MyThread06 mt = new MyThread06(); System.out.println("begin = " + System.currentTimeMillis()); mt.start(); System.out.println("end = " + System.currentTimeMillis()); } }
begin = 1604373745809 end = 1604373745810 run threadName = Thread-0 begin run threadName = Thread-0 end
|
yield()
暂停当前执行的线程对象,并执行其他线程。这个暂停是会放弃CPU资源的,并且放弃CPU的时间不确定,有可能刚放弃,就获得CPU资源了,也有可能放弃好一会儿,才会被CPU执行。看一下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class MyThread07 extends Thread { @Override public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 500000000; i++) { Thread.yield(); count = count + i + 1; } long endTime = System.currentTimeMillis(); System.out.println("用时:" + (endTime - beginTime) + "毫秒!"); } }
public class MyThread01 extends Thread { public static void main(String[] args) { MyThread07 mt = new MyThread07(); mt.start(); } }
|
interrupted()
测试当前线程是否已经中断,执行后具有将状态标识清除为false的功能。换句话说,如果连续两次调用该方法,那么返回的必定是false:
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { Thread.currentThread().interrupt(); System.out.println("是否停止1?" + Thread.interrupted()); System.out.println("是否停止2?" + Thread.interrupted()); System.out.println("end!"); }
是否停止1?true 是否停止2?false end!
|