新建线程很简单。只须要使用new关键字建立一个线程对象,而后调用它的start()启动线程便可。java
Thread thread1 = new Thread1(); t1.start();
那么线程start()以后,会干什么呢?线程有个run()方法,start()会建立一个新的线程并让这个线程执行run()方法。微信
这里须要注意,下面代码也能经过编译,也能正常执行。可是,却不能新建一个线程,而是在当前线程中调用run()方法,将run方法只是做为一个普通的方法调用。多线程
Thread thread = new Thread1(); thread1.run();
因此,但愿你们注意,调用start方法和直接调用run方法的区别。并发
start方法是启动一个线程,run方法只会在当前线程中串行的执行run方法中的代码。eclipse
默认状况下, 线程的run方法什么都没有,启动一个线程以后立刻就结束了,因此若是你须要线程作点什么,须要把您的代码写到run方法中,因此必须重写run方法。异步
Thread thread1 = new Thread() { @Override public void run() { System.out.println("hello,我是一个线程!"); } }; thread1.start();
上面是使用匿名内部类实现的,重写了Thread的run方法,而且打印了一条信息。咱们能够经过继承Thread类,而后重写run方法,来自定义一个线程。但考虑java是单继承的,从扩展性上来讲,咱们实现一个接口来自定义一个线程更好一些,java中恰好提供了Runnable接口来自定义一个线程。分布式
@FunctionalInterface public interface Runnable { public abstract void run(); }
Thread类有一个很是重要的构造方法:ide
public Thread(Runnable target)
咱们在看一下Thread的run方法:高并发
public void run() { if (target != null) { target.run(); } }
当咱们启动线程的start方法以后,线程会执行run方法,run方法中会调用Thread构造方法传入的target的run方法。大数据
实现Runnable接口是比较常见的作法,也是推荐的作法。
通常来讲线程执行完毕就会结束,无需手动关闭。可是若是咱们想关闭一个正在运行的线程,有什么方法呢?能够看一下Thread类中提供了一个stop()方法,调用这个方法,就能够当即将一个线程终止,很是方便。
package com.itsoku.chat01; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.TimeUnit; /** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公众号:路人甲Java,专一于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注! */ @Slf4j public class Demo01 { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { log.info("start"); boolean flag = true; while (flag) { ; } log.info("end"); } }; thread1.setName("thread1"); thread1.start(); //当前线程休眠1秒 TimeUnit.SECONDS.sleep(1); //关闭线程thread1 thread1.stop(); //输出线程thread1的状态 log.info("{}", thread1.getState()); //当前线程休眠1秒 TimeUnit.SECONDS.sleep(1); //输出线程thread1的状态 log.info("{}", thread1.getState()); } }
运行代码,输出:
18:02:15.312 [thread1] INFO com.itsoku.chat01.Demo01 - start 18:02:16.311 [main] INFO com.itsoku.chat01.Demo01 - RUNNABLE 18:02:17.313 [main] INFO com.itsoku.chat01.Demo01 - TERMINATED
代码中有个死循环,调用stop方法以后,线程thread1的状态变为TERMINATED(结束状态),线程中止了。
咱们使用idea或者eclipse的时候,会发现这个方法是一个废弃的方法,也就是说,在未来,jdk可能就会移除该方法。
stop方法为什么会被废弃而不推荐使用?stop方法过于暴力,强制把正在执行的方法中止了。
你们是否遇到过这样的场景:电力系统须要维修,此时我们正在写代码,维修人员直接将电源关闭了,代码还没保存的,是否是很崩溃,这种方式就像直接调用线程的stop方法相似。线程正在运行过程当中,被强制结束了,可能会致使一些意想不到的后果。能够给你们发送一个通知,告诉你们保存一下手头的工做,将电脑关闭。
在java中,线程中断是一种重要的线程写做机制,从表面上理解,中断就是让目标线程中止执行的意思,实际上并不是彻底如此。在上面中,咱们已经详细讨论了stop方法中止线程的坏处,jdk中提供了更好的中断线程的方法。严格的说,线程中断并不会使线程当即退出,而是给线程发送一个通知,告知目标线程,有人但愿你退出了!至于目标线程接收到通知以后如何处理,则彻底由目标线程本身决定,这点很重要,若是中断后,线程当即无条件退出,咱们又会到stop方法的老问题。
Thread提供了3个与线程中断有关的方法,这3个方法容易混淆,你们注意下:
public void interrupt() //中断线程 public boolean isInterrupted() //判断线程是否被中断 public static boolean interrupted() //判断线程是否被中断,并清除当前中断状态
interrupt()方法是一个实例方法,它通知目标线程中断,也就是设置中断标志位为true,中断标志位表示当前线程已经被中断了。isInterrupted()方法也是一个实例方法,它判断当前线程是否被中断(经过检查中断标志位)。最后一个方法interrupted()是一个静态方法,返回boolean类型,也是用来判断当前线程是否被中断,可是同时会清除当前线程的中断标志位的状态。
while (true) { if (this.isInterrupted()) { System.out.println("我要退出了!"); break; } } } }; thread1.setName("thread1"); thread1.start(); TimeUnit.SECONDS.sleep(1); thread1.interrupt();
上面代码中有个死循环,interrupt()方法被调用以后,线程的中断标志将被置为true,循环体中经过检查线程的中断标志是否为ture(this.isInterrupted()
)来判断线程是否须要退出了。
再看一种中断的方法:
static volatile boolean isStop = false; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { while (true) { if (isStop) { System.out.println("我要退出了!"); break; } } } }; thread1.setName("thread1"); thread1.start(); TimeUnit.SECONDS.sleep(1); isStop = true; }
代码中经过一个变量isStop来控制线程是否中止。
经过变量控制和线程自带的interrupt方法来中断线程有什么区别呢?
若是一个线程调用了sleep方法,一直处于休眠状态,经过变量控制,还能够中断线程么?你们能够思考一下。
此时只能使用线程提供的interrupt方法来中断线程了。
public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread() { @Override public void run() { while (true) { //休眠100秒 try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我要退出了!"); break; } } }; thread1.setName("thread1"); thread1.start(); TimeUnit.SECONDS.sleep(1); thread1.interrupt(); }
调用interrupt()方法以后,线程的sleep方法将会抛出InterruptedException
异常。
Thread thread1 = new Thread() { @Override public void run() { while (true) { //休眠100秒 try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (this.isInterrupted()) { System.out.println("我要退出了!"); break; } } } };
运行上面的代码,发现程序没法终止。为何?
代码须要改成:
Thread thread1 = new Thread() { @Override public void run() { while (true) { //休眠100秒 try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { this.interrupt(); e.printStackTrace(); } if (this.isInterrupted()) { System.out.println("我要退出了!"); break; } } } };
上面代码能够终止。
注意:sleep方法因为中断而抛出异常以后,线程的中断标志会被清除(置为false),因此在异常中须要执行this.interrupt()方法,将中断标志位置为true
为了支持多线程之间的协做,JDK提供了两个很是重要的方法:等待wait()方法和通知notify()方法。这2个方法并非在Thread类中的,而是在Object类中定义的。这意味着全部的对象均可以调用者两个方法。
public final void wait() throws InterruptedException; public final native void notify();
当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。这是什么意思?好比在线程A中,调用了obj.wait()方法,那么线程A就会中止继续执行,转为等待状态。等待到何时结束呢?线程A会一直等到其余线程调用obj.notify()方法为止,这时,obj对象成为了多个线程之间的有效通讯手段。
那么wait()方法和notify()方法是如何工做的呢?如图2.5展现了二者的工做过程。若是一个线程调用了object.wait()方法,那么它就会进出object对象的等待队列。这个队列中,可能会有多个线程,由于系统可能运行多个线程同时等待某一个对象。当object.notify()方法被调用时,它就会从这个队列中随机选择一个线程,并将其唤醒。这里但愿你们注意一下,这个选择是不公平的,并非先等待线程就会优先被选择,这个选择彻底是随机的。
除notify()方法外,Object独享还有一个nofiyAll()方法,它和notify()方法的功能相似,不一样的是,它会唤醒在这个等待队列中全部等待的线程,而不是随机选择一个。
这里强调一点,Object.wait()方法并不能随便调用。它必须包含在对应的synchronize语句汇总,不管是wait()方法或者notify()方法都须要首先获取目标独享的一个监视器。图2.6显示了wait()方法和nofiy()方法的工做流程细节。其中T1和T2表示两个线程。T1在正确执行wait()方法钱,必须得到object对象的监视器。而wait()方法在执行后,会释放这个监视器。这样作的目的是使其余等待在object对象上的线程不至于由于T1的休眠而所有没法正常执行。
线程T2在notify()方法调用前,也必须得到object对象的监视器。所幸,此时T1已经释放了这个监视器,所以,T2能够顺利得到object对象的监视器。接着,T2执行了notify()方法尝试唤醒一个等待线程,这里假设唤醒了T1。T1在被唤醒后,要作的第一件事并非执行后续代码,而是要尝试从新得到object对象的监视器,而这个监视器也正是T1在wait()方法执行前所持有的那个。若是暂时没法得到,则T1还必须等待这个监视器。当监视器顺利得到后,T1才能够在真正意义上继续执行。
给你们上个例子:
package com.itsoku.chat01; /** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公众号:路人甲Java,专一于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注! */ public class Demo06 { static Object object = new Object(); public static class T1 extends Thread { @Override public void run() { synchronized (object) { System.out.println(System.currentTimeMillis() + ":T1 start!"); try { System.out.println(System.currentTimeMillis() + ":T1 wait for object"); object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ":T1 end!"); } } } public static class T2 extends Thread { @Override public void run() { synchronized (object) { System.out.println(System.currentTimeMillis() + ":T2 start,notify one thread! "); object.notify(); System.out.println(System.currentTimeMillis() + ":T2 end!"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { new T1().start(); new T2().start(); } }
运行结果:
1562934497212:T1 start! 1562934497212:T1 wait for object 1562934497212:T2 start,notify one thread! 1562934497212:T2 end! 1562934499213:T1 end!
注意下打印结果,T2调用notify方法以后,T1并不能当即继续执行,而是要等待T2释放objec投递锁以后,T1从新成功获取锁后,才能继续执行。所以最后2行日志相差了2秒(由于T2调用notify方法后休眠了2秒)。
注意:Object.wait()方法和Thread.sleeep()方法均可以让现场等待若干时间。除wait()方法能够被唤醒外,另一个主要的区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放锁。
再给你们讲解一下wait(),notify(),notifyAll(),加深一下理解:
能够这么理解,obj对象上有2个队列,如图1,q1:等待队列,q2:准备获取锁的队列;两个队列都为空。
obj.wait()过程:
synchronize(obj){ obj.wait(); }
假若有3个线程,t一、t二、t3同时执行上面代码,t一、t二、t3会进入q2队列,如图2,进入q2的队列的这些线程才有资格去争抢obj的锁,假设t1争抢到了,那么t二、t3机型在q2中等待着获取锁,t1进入代码块执行wait()方法,此时t1会进入q1队列,而后系统会通知q2队列中的t二、t3去争抢obj的锁,抢到以后过程如t1的过程。最后t一、t二、t3都进入了q1队列,如图3。
上面过程以后,又来了线程t4执行了notify()方法,以下:**
synchronize(obj){ obj.notify(); }
t4会获取到obj的锁,而后执行notify()方法,系统会从q1队列中随机取一个线程,将其加入到q2队列,假如t2运气比较好,被随机到了,而后t2进入了q2队列,如图4,进入q2的队列的锁才有资格争抢obj的锁,t4线程执行完毕以后,会释放obj的锁,此时队列q2中的t2会获取到obj的锁,而后继续执行,执行完毕以后,q1中包含t一、t3,q2队列为空,如图5
接着又来了个t5队列,执行了notifyAll()方法,以下:
synchronize(obj){ obj.notifyAll(); }
2.调用obj.wait()方法,当前线程会加入队列queue1,而后会释放obj对象的锁
t5会获取到obj的锁,而后执行notifyAll()方法,系统会将队列q1中的线程都移到q2中,如图6,t5线程执行完毕以后,会释放obj的锁,此时队列q2中的t一、t3会争抢obj的锁,争抢到的继续执行,未加强到的带锁释放以后,系统会通知q2中的线程继续争抢索,而后继续执行,最后两个队列中都为空了。
Thread类中还有2个方法,即线程挂起(suspend)和继续执行(resume),这2个操做是一对相反的操做,被挂起的线程,必需要等到resume()方法操做后,才能继续执行。系统中已经标注着2个方法过期了,不推荐使用。
系统不推荐使用suspend()方法去挂起线程是由于suspend()方法致使线程暂停的同时,并不会释听任何锁资源。此时,其余任何线程想要访问被它占用的锁时,都会被牵连,致使没法正常运行(如图2.7所示)。直到在对应的线程上进行了resume()方法操做,被挂起的线程才能继续,从而其余全部阻塞在相关锁上的线程也能够继续执行。可是,若是resume()方法操做意外地在suspend()方法前就被执行了,那么被挂起的线程可能很难有机会被继续执行了。而且,更严重的是:它所占用的锁不会被释放,所以可能会致使整个系统工做不正常。并且,对于被挂起的线程,从它线程的状态上看,竟然仍是Runnable状态,这也会影响咱们队系统当前状态的判断。
上个例子:
/** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公众号:路人甲Java,专一于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注! */ public class Demo07 { static Object object = new Object(); public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { synchronized (object) { System.out.println("in " + this.getName()); Thread.currentThread().suspend(); } } } public static void main(String[] args) throws InterruptedException { T1 t1 = new T1("t1"); t1.start(); Thread.sleep(100); T1 t2 = new T1("t2"); t2.start(); t1.resume(); t2.resume(); t1.join(); t2.join(); } }
运行代码输出:
in t1 in t2
咱们会发现程序不会结束,线程t2被挂起了,致使程序没法结束,使用jstack命令查看线程堆栈信息能够看到:
"t2" #13 prio=5 os_prio=0 tid=0x000000002796c000 nid=0xa3c runnable [0x000000002867f000] java.lang.Thread.State: RUNNABLE at java.lang.Thread.suspend0(Native Method) at java.lang.Thread.suspend(Thread.java:1029) at com.itsoku.chat01.Demo07$T1.run(Demo07.java:20) - locked <0x0000000717372fc0> (a java.lang.Object)
发现t2线程在suspend0处被挂起了,t2的状态居然仍是RUNNABLE状态,线程明明被挂起了,状态仍是运行中容易致使咱们队当前系统进行误判,代码中已经调用resume()方法了,可是因为时间前后顺序的缘故,resume并无生效,这致使了t2永远滴被挂起了,而且永远占用了object的锁,这对于系统来讲多是致命的。
不少时候,一个线程的输入可能很是依赖于另一个或者多个线程的输出,此时,这个线程就须要等待依赖的线程执行完毕,才能继续执行。jdk提供了join()操做来实现这个功能。以下所示,显示了2个join()方法:
public final void join() throws InterruptedException; public final synchronized void join(long millis) throws InterruptedException;
第1个方法表示无限等待,它会一直只是当前线程。知道目标线程执行完毕。
第2个方法有个参数,用于指定等待时间,若是超过了给定的时间目标线程还在执行,当前线程也会中止等待,而继续往下执行。
好比:线程T1须要等待T二、T3完成以后才能继续执行,那么在T1线程中须要分别调用T2和T3的join()方法。
上个示例:
/** * <b>description</b>: <br> * <b>time</b>:2019/7/12 17:18 <br> * <b>author</b>:微信公众号:路人甲Java,专一于java技术分享(带你玩转 爬虫、分布式事务、异步消息服务、任务调度、分库分表、大数据等),喜欢请关注! */ public class Demo08 { static int num = 0; public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println(System.currentTimeMillis() + ",start " + this.getName()); for (int i = 0; i < 10; i++) { num++; try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(System.currentTimeMillis() + ",end " + this.getName()); } } public static void main(String[] args) throws InterruptedException { T1 t1 = new T1("t1"); t1.start(); t1.join(); System.out.println(System.currentTimeMillis() + ",num = " + num); } }
执行结果:
1562939889129,start t1 1562939891134,end t1 1562939891134,num = 10
num的结果为10,一、3行的时间戳相差2秒左右,说明主线程等待t1完成以后才继续执行的。
看一下jdk1.8中Thread.join()方法的实现:
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; } } }
从join的代码中能够看出,在被等待的线程上使用了synchronize,调用了它的wait()方法,线程最后执行完毕以后,系统会自动调用它的notifyAll()方法,唤醒全部在此线程上等待的其余线程。
注意:被等待的线程执行完毕以后,系统自动会调用该线程的notifyAll()方法。因此通常状况下,咱们不要去在线程对象上使用wait()、notify()、notifyAll()方法。
另一个方法是Thread.yield(),他的定义以下:
public static native void yield();
yield是谦让的意思,这是一个静态方法,一旦执行,它会让当前线程出让CPU,但须要注意的是,出让CPU并非说不让当前线程执行了,当前线程在出让CPU后,还会进行CPU资源的争夺,可是可否再抢到CPU的执行权就不必定了。所以,对Thread.yield()方法的调用好像就是在说:我已经完成了一些主要的工做,我能够休息一下了,可让CPU给其余线程一些工做机会了。
若是以为一个线程不过重要,或者优先级比较低,而又担忧此线程会过多的占用CPU资源,那么能够在适当的时候调用一下Thread.yield()方法,给与其余线程更多的机会。
java高并发系列连载中,总计估计会有四五十篇文章,能够关注公众号:javacode2018,获取最新文章。