下面是不添加线程的程序代码。java
package concurrent.chapter01; import java.util.concurrent.TimeUnit; public class TryConcurrency { public static void main(String[] args) { browseNews(); enjoyMusic(); } private static void browseNews() { while(true) { System.out.println("Uh-huh,the good news."); sleep(1); } } private static void enjoyMusic() { while(true) { System.out.println("Uh-huh,the nice music"); sleep(1); } } private static void sleep(int i) { try { TimeUnit.SECONDS.sleep(i); }catch (Exception e) { } } }
运行结果以下:算法
程序永远不会执行第二个方法。所以咱们须要使用线程。sql
这里经过匿名内部类的方式建立线程,而且重写其中的run方法,使程序交互运行。数据库
package concurrent.chapter01; import java.util.concurrent.TimeUnit; public class TryConcurrency { public static void main(String[] args) { new Thread() { @Override public void run() { enjoyMusic(); } }.start(); browseNews(); } private static void browseNews() { while(true) { System.out.println("Uh-huh,the good news."); sleep(1); } } private static void enjoyMusic() { while(true) { System.out.println("Uh-huh,the nice music"); sleep(1); } } private static void sleep(int i) { try { TimeUnit.SECONDS.sleep(i); }catch (Exception e) { } } }
运行结果以下:编程
注意:设计模式
一、建立一个线程,须要重写Thread中的run方法,Override的注解是重写的标识,而后将enjoyMusic交给他执行。安全
二、启动新的线程须要重写Thread的start方法,才表明派生了一个新的线程,不然Thread和其余普通的Java对象并没有区别,start放法是一个当即返回方法,并不会让程序陷入阻塞。网络
若是使用Lambda表达式改造上面的代码,那么代码会变得更简洁。数据结构
public static void main(String[] args) { new Thread(TryConcurrency::enjoyMusic).start(); browseNews(); }
当咱们用关键字new建立一个Thread对象时,此时他并不处于执行状态,由于没用start启动该线程,那么线程的状态为NEW状态,准确的说,它只是Thread对象的状态,由于在没用start以前,该线程根本不存在,与你用new建立一个普通的Java对象没什么区别。并发
线程对象进入RUNNABLE状态必须调用start方法,那么此时才是真正地在JVM中建立了一个线程,线程一经启动就能够当即执行吗?答案是否认的,线程的运行与否和进程同样都要听令于CPU的调度,那么咱们把这个中间状态成为可执行状态,也就是说它具有执行的资格,可是并无真正地执行起来,而是等待CPU的调度。
一旦CPU经过轮询或者其余方式从任务可执行队列中选中了线程,那么此时它才能真正的执行本身的逻辑代码,须要说明一点是一个正在RUNNING状态的线程事实上也是RUNNABLE的,可是反过来则不成立。
在该状态中,线程的状态能够发生以下的状态转换。
BLOCKED为线程阻塞时的状态,它能进入如下几个状态:
TERMINATED是一个线程的最终状态,在该状态中,线程将不会切换到其余任何状态,线程进入Terminated状态意味着整个线程的生命周期结束了,下列状况将会使线程进入Terminated状态。
首先:Thread start源码以下:
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();
start方法的源码足够简单,其实最核心的部分是start0这个本地方法,也就是JNI方法;
也就是说在start方法中会调用start0方法,那么重写的那个run方法什么时候被调用了呢?
实际上在开始执行这个线程的时候,JVM将会调用该线程的run方法,换言之,run方法是被JNI方法start0调用的,仔细阅读start的源码将会总结出以下几个知识要点。
如执行如下代码:
import java.util.concurrent.TimeUnit; public class A { public static void main(String[] args) { Thread thread = new Thread() { @Override public void run() { try { TimeUnit.SECONDS.sleep(10); }catch (Exception e) { e.printStackTrace(); } } }; thread.start(); thread.start(); } }
此时程序就会抛出
当咱们改下代码,也就是生命周期结束后,再从新调用时。
import java.util.concurrent.TimeUnit; public class A { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { @Override public void run() { try { TimeUnit.SECONDS.sleep(1); }catch (Exception e) { e.printStackTrace(); } } }; thread.start(); TimeUnit.SECONDS.sleep(5); thread.start(); } }
咱们会发现程序一样会抛出illegalThread异常。
注意:程序虽然一样会抛出异常,可是这俩个异常是有本质区别的。
经过以上分析咱们不难看出,线程真正的执行逻辑是在run方法中,一般咱们会把run方法成为线程的执行单元。
若是咱们没有重写run,那run就是个空方法。
Thread的run和start是一个比较经典的模板设计模式,父类编写算法结构代码,子类实现逻辑细节,下面是一个简单的模板设计模式。
package concurrent.chapter01; public class TemplateMethod { public final void print(String message) { System.out.println("###"); wrapPrint(message); System.out.println("###"); } protected void wrapPrint(String message) { } public static void main(String[] args) { TemplateMethod t1 = new TemplateMethod() { @Override protected void wrapPrint(String message) { System.out.println("*"+message+"*"); } }; t1.print("Hello Thread"); TemplateMethod t2 = new TemplateMethod() { @Override protected void wrapPrint(String message) { System.out.println("+"+message+"+"); } }; t2.print("Hello Thread"); } }
运行结果以下:
假设共有4台出号机,这就意味着有4个线程在工做,下面咱们用程序模拟一下叫号的过程,约定当天最多受理50笔业务,也就是说号码最多能够出到50
代码以下:
package concurrent.chapter01; public class TicketWindow extends Thread{ private final String name; private static final int MAX = 50; private int index = 1; public TicketWindow(String name) { this.name=name; } @Override public void run() { while(index<=MAX) { System.out.println("柜台:"+name+" 当前号码是:"+(index++)); } } public static void main(String[] args) { TicketWindow t1 = new TicketWindow("一号初号机"); t1.start(); TicketWindow t2 = new TicketWindow("二号初号机"); t2.start(); TicketWindow t3 = new TicketWindow("三号初号机"); t3.start(); TicketWindow t4 = new TicketWindow("四号初号机"); t4.start(); } }
运行结果以下:
显然这不是咱们想看到的。如何改进呢?
这里我将index设置为staic变量
貌似有了改善。可是会出现线程安全问题。
因此Java提供了一个接口:Runnable专门用于解决该问题,将线程和业务逻辑的运行完全分离开。
Runnalbe接口很是简单,只是定义了一个无参数无返回值的run方法,具体代码以下:
public interface Runnable{ void run(); }
在不少书中,都会说,建立线程有俩种方式,第一种是构造一个Thread,第二种是实现Runnable接口,这种说法是错误的,最起码是不严谨的,在JDK中,表明线程的就只有Thread这个类,咱们在前面分析过,线程的执行单元就是run方法,你能够经过继承Thread而后重写run方法实现本身的业务逻辑,也能够实现Runnable接口实现本身的业务逻辑,代码以下:
@override public void run(){ if(target!=null){ target.run(); } }
上面的代码段是Thread run方法的源码,咱们从中能够去理解,建立线程只有一种方式,那就是构造Thread类,而实现线程的执行单元则有俩种方式,第一种是重写Thread的run方法,第二种是实现Runnable接口的run方法,并将Runnable实例用做构造Thread的参数。
其实不管是Runnable的run方法仍是,Thread自己的run方法都说想将线程的控制自己和业务逻辑的运行分离开,达到职责分明,功能单一的原则,这一点与GoF设计模式中的策略设计模式很相近。
以JDBC来举例子:
package concurrent.chapter01; import java.sql.ResultSet; public interface RowHandler <T>{ T handle(ResultSet set); }
rowhandler接口只负责对从数据库中查询出来的结果集进行操做,至于最终返回成什么样的数据结构,须要本身去实现,相似于Runnable接口。
package concurrent.chapter01; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class RecordQuery { private final Connection connection; public RecordQuery(Connection connection) { this.connection = connection; } public<T> T query(RowHandler<T> handler,String sql,Object... params) throws SQLException{ try(PreparedStatement stmt = connection.prepareStatement(sql)){ int index = 1; for(Object param:params) { stmt.setObject(index++,param); } ResultSet resultSet = stmt.executeQuery(); return handler.handle(resultSet); } } }
上面的代码的好处就是能够用Query方法应对任何数据库的查询,返回结果的不一样只会由于你传入RowHandler的不一样而不一样,一样RecodeQuery只负责数据的获取,而RowHanlder则只负责数据的加工,职责分明,每一个类均功能单一。
重写Thread类的run方法和实现Runnable接口的run方法是不能共享的,也就是说A线程不能把B线程的run方法看成本身的执行单元,而使用Runnable接口则很容易就能实现这一点即便用同一个Runnable的实例构造不一样的实例。
若是不明白的话,看下面的代码。
package concurrent.chapter01; public class TicketWindowRunnable implements Runnable{ private int index = -1; private final static int MAX = 50; @Override public void run() { while(index<=MAX) { System.out.println(Thread.currentThread()+" 的号码是:"+(index++)); try { Thread.sleep(100); }catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { final TicketWindowRunnable task = new TicketWindowRunnable(); Thread WindowThread1 = new Thread(task,"一号窗口"); Thread WindowThread2 = new Thread(task,"二号窗口"); Thread WindowThread3 = new Thread(task,"三号窗口"); Thread WindowThread4 = new Thread(task,"四号窗口"); WindowThread1.start(); WindowThread2.start(); WindowThread3.start(); WindowThread4.start(); } }
运行结果以下:
惊不惊喜?
上面并无对index进行static进行修饰,可是和上面被static修饰的是一个效果。缘由是咱们每次操做的都是同一个对象即task。