线程创建(五种方法)
run方法是线程的入口方法, 每次我们去创建线程时都要重写run方法, run方法执行结束即该线程结束.
1. 继承 Thread, 重写 run
class MyThread extends Thread { @Override public void run() { System.out.println("run"); } } public class ThreadDemo1 { public static void main(String[] args) { Thread t = new MyThread(); t.start(); System.out.println("main"); } }
输出: main run 或 run main
因为线程调度不同, 所以线程执行快慢不一样.
2. 实现 Runnable 接口, 重写 run
class MyRunnable implements Runnable { @Override public void run() { System.out.println("run"); } } public class ThreadDemo2 { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread t = new Thread(myRunnable); t.start(); System.out.println("main"); } }
3. 继承 Thread, 重写 run, 使用匿名内部类
public class ThreadDemo3 { public static void main(String[] args) { Thread t = new Thread() { @Override public void run() { System.out.println("run"); } }; t.start(); System.out.println("main"); } }
4. 实现 Runnable, 重写 run, 使用匿名内部类
public class ThreadDemo4 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { System.out.println("run"); } }); t.start(); System.out.println("main"); } }
5. 使用 lambda 表达式(重点掌握)
public class ThreadDemo5 { public static void main(String[] args) { Thread t = new Thread(() -> { System.out.println("run"); }); t.start(); System.out.println("main"); } }
线程休眠
class MyThread extends Thread { @Override public void run() { while(true) { System.out.println("run"); try { //因为sleep是静态方法,所以可以直接调用 Thread.sleep(1000); //可能会出现异常, 所以要捕获 } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadDemo1 { public static void main(String[] args) { Thread t = new MyThread(); t.start(); while(true) { System.out.println("main"); try { Thread.sleep(1000); //单位是毫秒, 这里是1s } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出:
因为线程调度不同, 所以这里打印是随机的, 这里没有体现出来.
因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的.
获取线程实例
Thread.currentThread() 获取线程的实例
public static void main(String[] args) { Thread thread = Thread.currentThread(); System.out.println(thread.getName()); }
输出: main
线程中断
就是让一个线程停下来, 线程终止.
本质上来说, 让一个线程终止就一个方法, 让线程的入口方法执行完.
1. 给线程中设定一个结束标志位
class MyThread extends Thread { public static boolean isQuit; //设置一个成员变量, 表示标志位 @Override public void run() { while(!isQuit) { //通过成员变量我们可以控制while循环 System.out.println("run"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("程序终止!"); } public static void main(String[] args) { Thread t = new MyThread(); t.start(); System.out.println("main"); isQuit = true; //在主线程中将标志位置为true, 影响到t线程 } }
上述代码中, 我们用的是成员变量设置标志位, 那局部变量可以吗?
我们可以试试:
显示找不到该变量, 为什么找不到呢?
按理来说线程与线程之间共用一个内存地址空间, 它们之间的变量都可以访问的, 那为什么不行呢?
其实这里和 lambda 表达式有关.
lambda 表达式中捕获的变量必须是 final 修饰的 或者是 “实际 final”(没有被final修饰, 但是代码中没有对该变量进行修改过)
再看上述代码, isQuit 在主线程中创建, 又被修改过, 所以不行.
2. 使用Thread类内置的标志位来控制
class MyThread extends Thread { @Override public void run() { //这里的Thread.currentThread()是获取当前对象的实例,也就是t //isInterrupted()得到内置标志位, 初始为false while(!Thread.currentThread().isInterrupted()) { System.out.println("run"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("程序终止!"); } public static void main(String[] args) { Thread t = new MyThread(); t.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //interrupt() 设置标志位为true t.interrupt(); } }
当我们在调用 interrupt() 时, 如果该线程在正在阻塞中(比如正在sleep中), 此时就会把阻塞状态唤醒, 通过抛出异常的方式让sleep立即结束.
注意:
当线程处于sleep状态被唤醒时, sleep会自动把isInterrupted标志位清空(true -> false). 这就导致下次循环还是会进去, 而主线程已近结束了, 没有interrupt() 来影响标志位, 就会一直循环打印run.
这就产生了上面的输出结果.
当然, 如果sleep刚刚好结束了, 这时候我们调用interrupt(), 则直接结束循环.(这种情况是很低的)
我们可以通过添加一个break来让代码抛出异常后退出循环.
class MyThread extends Thread { @Override public void run() { while(!Thread.currentThread().isInterrupted()) { System.out.println("run"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); break; } } System.out.println("程序终止!"); } public static void main(String[] args) { Thread t = new MyThread(); t.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); } }
线程等待
因为线程之间是并发执行的, 操作系统对线程的调度是无序的, 所以我们无法判断哪个线程先结束, 哪个后结束.
因此, 在Thread类里提供了一个 join 方法, 来进行线程间的等待.
比如:
t.join() 放在 main 线程里就是main线程等待 t 线程结束. 这时main线程是阻塞状态, 直到 t 执行结束main线程才会从阻塞解除, 继续执行.
例:
public class ThreadDemo5 { //使用join方法可能会有异常,所以声明一下 public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { System.out.println("t1"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t2 = new Thread(() -> { try { t1.join(); //放在这里就是t2等待t1结束 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2"); }); //首先创建两个线程 t1.start(); t2.start(); t2.join(); //这里main线程等待t2结束 System.out.println("main"); } }
首先main线程等待 t2 线程执行结束, 而 t2 线程在等待 t1 线程结束, 所以就出现上面结果.