三、启动线程
启动线程一般用创建的线程对象来调用 start() 方法即可,如下
只有调用 start 才会真正创建线程!!!不调用 start 就是没创建线程(在内核里创键PCB)
注意区分,调用start 和 run 的区别:
一般情况,我们创建的线程对象调用run方法和start方法时,产生的结果可能会相同,这就会导致我们误以为 run() 和 start() 没有区别!
直接调用run方法并没有创建线程,只是在原来的线程中运行代码
而直接调用start方法,则是创建一个新的线程,在新线程中执行代码(和原来的线程是并发的)
我们来看下面代码:
class MyThread extends Thread{ @Override public void run() { while(true){ try { System.out.println("hello Thread"); Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo1 { public static void main(String[] args) { Thread t1 = new MyThread(); t1.run(); //t1.start(); while(true){ try { System.out.println("hello main"); Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
当t1调用 start() 方法时的结果:
我们会发现,t1调用run()方法只会打印“hello Thread”。这是因为是main线程调用的run()方法!!代码顺序执行,同时run()方法是一直循环,所以就不会打印“hello main”。
而t1调用start()方法,则是创建一个新线程,新线程调用run,所以我们会看到 “hello Thread”和“hello main”都有打印。
四、中断线程
一般情况下,只有 run方法执行完了,线程才结束,但是有点时候我们想要线程提前结束那该怎么办呢?这就涉及到我们中断线程的方式了。
中断线程一般会设置标志位,而设置标志位分两种方式:
直接自己定义一个标志位,作为线程是否结束的标记
使用标准库里自带的一个标志位
(1)自定义标志位
自定义一个变量作为一个标志位,通过对变量的修改来结束run
public class Demo2 { private static boolean isQuit = false; public static void main(String[] args) { Thread t = new Thread(()->{ while(!isQuit){ System.out.println("hello Thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } isQuit = true; System.out.println("设置让 t 线程结束"); } }
(2)使用 Thread.currentThread().isInterrupted() 作为标志位
currentTrhead():是Thread类的静态方法,通过这个方法,可以拿到当前线程的实例
isInterrupted():调用这个方法判断标志位
在主线程中,通过 t.interrupt 来中断这个线程,设置标志位为 true!
public class Demo3 { public static void main(String[] args) { Thread t = new Thread(()->{ while(!Thread.currentThread().isInterrupted()){ System.out.println("hello Thread"); } System.out.println("t 执行完"); }); t.start(); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); System.out.println("设置让 t 线程结束"); } }
注意:interrupt 方法的行为,有两种情况
① 当 t 线程在运行状态时,会设置标志位(Thread.currentThread().isInterrupted())为 true.
例如上述代码。
② 当 t 线程在阻塞状态时(sleep),就不会设置标志位,而是触发一个 InterruptedException,这个异常会把 sleep 提前唤醒。
上图代码中,处理异常只是打印日志,并没有结束循环。所以这种情况并不能中断线程
这时我们要想中断线程或者其他操作,我们就要在捕获异常的代码块中进程操作!
提示:在 java 中,中断线程并非强制的!!! 由线程自身的代码来进行判断处理的!!!
线程怎么处理,取决于代码怎么写!
五、线程等待
一般情况下,线程之间的调度顺序,是不确定的!但是可以通过一些特殊操作,来对线程的执行顺序做出干预。
其中 join 就是一个方法,控制线程之间的结束顺序!!
例如:
创建一个线程 t,并让他循环打印 “hello” 五次,同时在main线程中调用 t.join()。
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ for (int i = 0; i < 3; i++) { System.out.println("hello"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); System.out.println("main 线程 join 之前"); t.join(); System.out.println("main 线程结束"); }
结果如下:
我们发现 main 线程是等待 t 线程工作结束后才继续执行,结果是确定的。
t.join() 效果就是:让 mian 线程阻塞等待,等到线程之间的结束顺利!!
而如果我们不加 t.join(),运行结果就是随机的了。如下
在实际开发中很少会用到 无限等待的方法,大多数都是指定了最大等待,避免程序因为死等,导致“卡死”的情况。
join() 带参数的方法如下:
方法 | 说明 |
public void join(long millis) | 等待线程结束,最多等待 millis 毫秒 |
public void join(long millis,int nanos) | 等待 millis 毫秒 + nanos 纳秒(精确版) |
六、获取当前线程引用
方法 | 说明 |
public static Thread currentThread() | 返回当前线程对象的引用 |
获取当前这个线程对应 Thread 对象的引用,哪个线程里面调用,得到的就是哪个线程的引用。
public static void main(String[] args) { Thread t = new Thread(()->{ System.out.println(Thread.currentThread().getName()); }); t.start(); Thread ct = Thread.currentThread(); System.out.println(ct.getName()); }
七、线程休眠
sleep 方法 可以指定休眠时间,让线程休息(相当于阻塞一会)
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis,int nanos) throws InterruptedException | 休眠 millis 毫秒 + nanos 纳秒 |
public static void sleep(long millis,int nanos) throws InterruptedException 休眠 millis 毫秒 + nanos 纳秒 public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMillis();//记录开始时间(时间戳) Thread.sleep(1000);//让main先休眠 1000毫秒 Long end = System.currentTimeMillis();//记录结束时间 System.out.println("耗时:"+(end - start)); }
sleep 的原理就是让原本在 就绪队列 中的线程,转移到 阻塞队列 中去,等 sleep 的时间到了,再转移到 就绪队列 中。