建立线程的方式有不少种,下面咱们就最基本的两种方式进行说明。主要先介绍使用方式,再从源码角度进行解析。java
这两种方式是最基本的建立线程的方式,其实核心也就是Thread类,后面分析源码会讲到,下面先介绍使用方式。编程
1,建立线程步骤设计模式
代码以下:多线程
package com.yefengyu.thread; //1,建立一个子类继承于Thread类 public class SubThread extends Thread { //2,子类重写Thread类的run方法,方法内实现子线程要完成的功能 @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("Thread ...." + i); } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //3,建立一个子类的对象。 SubThread subThread = new SubThread(); //4,调用线程的start()的方法。该方法有两个做用:启动此线程;调用响应的run方法。不能显示调用run方法,由于这样不能开启线程 subThread.start(); for (int i = 0; i < 5; i++) { System.out.println("main ...." + i); } } }
每次执行结果都不相同,这是由于多个线程都在获取cpu的执行权,cpu执行到谁,就执行谁。可是要明确一点,在某个时刻,单个cpu只能执行一个线程,cpu进行着快速切换,已达到看上去是并行执行的效果,这就是线程的一个特色:随机性,哪一个线程抢到cpu资源,就执行该线程,至于执行多长时间,是cpu说了算。 ide
main ....0 Thread ....0 Thread ....1 Thread ....2 main ....1 main ....2 main ....3 main ....4 Thread ....3 Thread ....4
2,设置与获取线程名称函数
因为默认的线程名称没有可读性,所以设置一个线程名称仍是比较重要的,Thread类有个构造方法:源码分析
public Thread(String name) { init(null, null, name, 0); }
所以子类增长线程名称则比较简单:学习
package com.yefengyu.thread; //1,建立一个子类继承于Thread类 public class SubThread extends Thread { //经过构造函数设置线程名称,固然使用set方法也能够 public SubThread(String name) { super(name); } //2,子类重写Thread类的run方法,方法内实现子线程要完成的功能 @Override public void run() { for (int i = 0; i < 5; i++) { //下面两种方法都会获取线程名称 System.out.println(this.getName()+" ... " + i); System.out.println(Thread.currentThread().getName()+" *** " + i); } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //(3)建立一个子类的对象。 SubThread subThread1 = new SubThread("my-thread11111"); SubThread subThread2 = new SubThread("my-thread22222"); //(4)调用线程的start()的方法。该方法有两个做用:启动此线程;调用响应的run方法。不能显示调用run方法,由于这样不能开启线程 subThread1.start(); subThread2.start(); for (int i = 0; i < 5; i++) { System.out.println("main ...." + i); } } }
结果:this
main ....0 my-thread22222 ... 0 my-thread11111 ... 0 my-thread22222 *** 0 my-thread22222 ... 1 my-thread22222 *** 1 main ....1 my-thread22222 ... 2 my-thread11111 *** 0 my-thread22222 *** 2 main ....2 main ....3 main ....4 my-thread22222 ... 3 my-thread11111 ... 1 my-thread11111 *** 1 my-thread11111 ... 2 my-thread22222 *** 3 my-thread22222 ... 4 my-thread22222 *** 4 my-thread11111 *** 2 my-thread11111 ... 3 my-thread11111 *** 3 my-thread11111 ... 4 my-thread11111 *** 4
3,实战:汽车票买票程序spa
假如车站有3张票,三个窗口,多线程如何卖票?
package com.yefengyu.thread; public class SubThread extends Thread { //票的总数 private int ticket = 3; public SubThread(String name) { super(name); } @Override public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖票 " + ticket--); } } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //三个线程模拟三个窗口同时卖票 SubThread subThread1 = new SubThread("my-thread11111"); SubThread subThread2 = new SubThread("my-thread22222"); SubThread subThread3 = new SubThread("my-thread33333"); subThread1.start(); subThread2.start(); subThread3.start(); } }
结果和咱们想的大不相同,居然每一个线程都卖了3张票,一共卖了9张票,这是不能够忍受的。
my-thread11111卖票 3 my-thread11111卖票 2 my-thread11111卖票 1 my-thread33333卖票 3 my-thread22222卖票 3 my-thread33333卖票 2 my-thread22222卖票 2 my-thread33333卖票 1 my-thread22222卖票 1
修改1:使用静态变量:
//票的总数 private static int ticket = 3;
定义静态能够解决卖出多余票的状况,可是这种变量通常不定义静态的,由于静态属性生命周期太长。
修改2:new一个SubThread实例,屡次启动
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //一个线程对象屡次启动 SubThread subThread1 = new SubThread("my-thread11111"); subThread1.start(); subThread1.start(); subThread1.start(); } }
出现异常:
my-thread11111卖票 3Exception in thread "main" my-thread11111卖票 2 my-thread11111卖票 1 java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:708) at com.yefengyu.thread.TestThread.main(TestThread.java:8)
源码这样说:线程不是NEW状态是不能够调用start方法的,调用会报异常,也就是一个线程启动以后不能再启动。关于线程状态后面博文会讲到。
/** * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException();
如何解决卖票程序中的问题呢?使用Runnable接口。
1,建立线程步骤
2,代码演示
package com.yefengyu.thread; public class SubThread implements Runnable { //票的总数 private int ticket = 3; @Override public void run() { while (true) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖票 " + ticket--); } } } }
package com.yefengyu.thread; public class TestThread { public static void main(String[] args) { //建立该类的对象 SubThread subThread = new SubThread(); //在建立 Thread 时做为一个参数来传递并启动 new Thread(subThread, "线程1").start(); new Thread(subThread, "线程2").start(); new Thread(subThread, "线程3").start(); } }
运行结果以下,是咱们想要的结果。
线程1卖票 3 线程3卖票 2 线程2卖票 1
稍微研究一下,第一种继承Thread类的方式,new了3次SubThread实例,那么实例的变量ticket也有三个,线程分别拥有各自的ticket。而实现Runnable接口的方式,数据只存在与SubThread这个实例对象中,代码中只需new一次,所以只有一份ticket数据,而将持有这份数据的对象经过构造方法传入到多个线程中的时候,线程对象只是执行的载体,真实数据只有一份,所以Runnable接口的实现方式适合多个相同的程序代码的线程去处理同一个资源。
3,本节小总结
1,理论分析
多线程是java开发中必不可少的一项技术点,下面主要研究Thread类,经过分析该类了解多线程执行的过程,为之后的线程池等高级技术打下坚实基础。
当咱们使用多线程进行开发的时候,最开始学习的例子就是使用Thread类。使用步骤以下,和上面演示的对比,简化了步骤:
编写一个类,继承Thread
重写run方法
经过调用start方法启动线程。
后来又有一种方法,实现Runnable接口,主要步骤以下:
编写一个类,实现Runnable 接口,重写run方法
将该类的对象传入Thread类中
经过调用start方法启动线程。
经过上面,咱们来分析一下,线程执行离不开Thread类,最后都要使用start方法启动线程。咱们能够想到start方法是一个入口方法,它能够作不少事。假如start方法作了以下的事情:
do x
do y
do run
do z
咱们不关心x、y、z具体是什么,只要明白start方法是一个入口方法,它作了不少事情,可是在某一步,它调用了 run 方法。而run方法是咱们必须实现的,也就是咱们本身实现的逻辑在start里面被执行了。接着咱们考虑下 run 方法,怎么样才能本身定义run方法,而后在run方法里面写本身的逻辑?
一种方法是,在Thread类里面,咱们定义一个抽象方法 run,这个时候,必须有子类来实现。这是模板设计模式思想。
另外一种方法是提供一个接口,而且接口中有个方法 run,Thread类持有这个接口(经过属性持有,再经过构造器传入),而且Thread类也有个run方法(为啥也要有个run方法后面会提到),该run方法调用接口的run方法。此时只要编写一个类实现接口,重写run方法,并传入Thread类,那么Thread类在执行start方法的时候,会调用自身的run方法,该run方法又会调用接口实现的run方法,这是策略设计模式思想。
以上两种模式就是实现Thread类和实现Runnable接口的实现原理。须要注意的是,Thread类自己也实现了Runnable接口,那么Thread类自己拥有run方法则水到渠成。
2,源码分析
Runnable的源码很简单:
@FunctionalInterface public interface Runnable { public abstract void run(); }
Thread类的属性很是多,咱们暂时无论,注意有一个属性,它是对Runnable接口的引用。
private Runnable target;
有了属性,咱们须要看如何传入这个属性值:
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); }
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
init方法内容不少,可是关于target变量,只有一句:
this.target = target;
所以能够判断,上面两个构造器,第一个没有给Runnable赋值,值为null,第二个经过参数进行赋值。
咱们再看下Thread类:
public class Thread implements Runnable
Thread类实现了Runnable接口,所以必须实现run方法:
@Override public void run() { if (target != null) { target.run(); } }
对于这个run方法,若是是使用继承Thread类重写run方法;那么这里面的内容将会被覆盖,若是是实现Runnable接口重写run方法,那么此处就会调用接口实现的run方法。这就让两种实现多线程的方式得以共存。
这个run方法什么时候调用?在start方法中:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } private native void start0();
注意start方法中的start0方法和最后一行start0方法。该start0方法是native方法,实质是调用run方法,此处暂时不作详解。
Thread类使用模板设计模式,模板方法是start,start方法里面的start0方法才能真正启动线程、调用了Thread类的run方法。
3,Runnable接口的好处: