多线程(一)两种线程的实现方法、后台线程和线程组、线程的生命周期
一、线程的两种启动方法
分别是继承java.lang.Thread类和实现java.lang.Runnable接口两种方法。
举个例子:
package base; public class ThreadTest extends Thread{ private int count=10; public void run() { for(int i=0;i<10;i++) if(count>0) System.out.println(this.getName()+":count"+--count); } public static void main(String []args) { ThreadTest t1=new ThreadTest(); ThreadTest t2=new ThreadTest(); ThreadTest t3=new ThreadTest(); t1.start(); t2.start(); t3.start(); } }
结果显示3个线程各记了10个数,但是并非按照顺序来排列:
Thread-0:count9 Thread-2:count9 Thread-1:count9 Thread-2:count8 Thread-1:count8 Thread-2:count7 Thread-1:count7 Thread-2:count6 Thread-1:count6 Thread-1:count5 Thread-1:count4 Thread-1:count3 Thread-1:count2 Thread-1:count1 Thread-1:count0 Thread-2:count5 Thread-2:count4 Thread-2:count3 Thread-2:count2 Thread-2:count1 Thread-2:count0 Thread-0:count8 Thread-0:count7 Thread-0:count6 Thread-0:count5 Thread-0:count4 Thread-0:count3 Thread-0:count2 Thread-0:count1 Thread-0:count0
接着是Runable接口的展示,但是与Thread类有所不同,因为接口的特性可以实现多个,而不能继承多个父类
package base; public class RunnableTest implements Runnable{ private int count=10; public void run() { for(int i=0;i<10;i++) if(count>0) System.out.println(Thread.currentThread().getName()+":count"+--count); } public static void main(String []args) { RunnableTest rt=new RunnableTest(); Thread th1=new Thread(rt); Thread th2=new Thread(rt); Thread th3=new Thread(rt); th1.start(); th2.start(); th3.start(); ; } }
结果:
Thread-1:count9 Thread-2:count8 Thread-0:count7 Thread-2:count5 Thread-1:count6 Thread-2:count3 Thread-0:count4 Thread-2:count1 Thread-1:count2 Thread-0:count0
可以看出,接口可以共用资源,3个线程一共计了10个数。
总结一下:一个类继承Thread()类之后,实现run()方法之后。然后只需在主函数(或其他合适的地方)实例化并且调用start()方法即可。
而使用接口的情况,需要一个类实现该接口,然后实现run()方法。在主函数中实例化该类,然后将该实例化对象作为参数创建一个新的Thread()对象,最终调用start()方法。
其实所谓多线程就是指多个Thread()对象之间不一定按照顺序执行了!
二、后台线程和线程组
这两个概念似乎不经常出现,但是有必要在线程生命周期之前熟悉它们。
后台线程daemon thread 是相对于用户线程user thread来说的。有些称为守护线程或者精灵线程,当一个程序中只有后台线程时,程序会立刻退出。如果还存在其他用户线程那么程序不会中止。而我们可以通过isDaemon()方法判断一个线程是否是后台线程。两者之间可以相互切换,通过setDaemon(boolean on)如果参数是true,该线程将会被设置为后台线程,false则会被设置为用户线程。不过该方法的调用只能在start()之前,一旦线程启动,那么它的属性就无法再进行切换。
接下来是后台线程的一个简单是示例:
package base; public class DaemonThread extends Thread{ public void run() { for(int i=0;i<10;i++) { System.out.println("线程"+i); try { sleep(3000); } catch(Exception e) { e.printStackTrace(); } } } public static void main(String []args) { Thread a=new DaemonThread(); a.setDaemon(true); a.start(); if(a.isDaemon()) { System.out.println("IsDaemon"); } else { System.out.println("IsNotDaemon"); } System.out.println("XXX"); } }
结果:
IsDaemon
XXX
线程0
结果代表当主线程运行完毕后,即“XXX”打印完毕后,就只剩下后台线程了,仍然再暂停中,因此程序直接结束,至于打印出线程0是因为那是在程序结束之前已经运行完毕。
可以想象的,当我们把setDaemon()参数设置为false或者删掉这句代码,那么程序将会在上面结果的基础上每隔3000ms打印出一个线程i,直到结束。而如果把sleep代码去掉但是保留setDaemon的部分,那么结果也会打印出所有的线程的结果。猜想是后台线程运行较快,在主线程运行结束前就已经结束,只有使用挂起才能观察的效果,这也是为什么示例都会采取挂起的动作。
于是我就想到把10次循环改为无限循环,看在主线城结束时能打印多少?
经过实测,打印到了线程17,基本大致上验证了咱们之前的猜想(虽然每次结果并不相同)。
线程组ThreadGroup好像比较相对的不重要,不过倒是学到了一些方法的使用:
{ Thread t=Thread.currentThread(); System.out.println(t.getName()); ThreadGroup tg=t.getThreadGroup(); System.out.println(tg.getName()); System.out.println("当前线程组含有"+tg.activeCount()+"个线程"); int n=tg.activeCount(); Thread [] th=new Thread [n]; tg.enumerate(th); for (Thread a:th) System.out.println(a.getName()); tg=tg.getParent(); System.out.println(tg.getName()); System.out.println("当前线程组含有"+tg.activeCount()+"个线程"); int m=tg.activeCount(); Thread [] thr=new Thread [m]; tg.enumerate(thr); for (Thread b:thr) System.out.println(b.getName()); }
例如currentThread()获得当前线程,是静态方法,Thread类调用。getThreadGroup()获得当前线程组,不是静态方法,Thread对象调用。activeCount()表示线程组中的线程数量。还有enumerate(a)将线程组里的线程赋给线程数组a。getName()获得线程或者线程组的名称。
对了,贴上结果:
三、线程的生命周期和Java中的线程方法
当我们new一个新的Thread的对象时,线程就进入到了新生态,而调用start()方法就进入了就绪态(或可执行状态),JVM通过一定的调度规则使就绪态的线程变成运行态,运行态执行run()方法,执行结束后转变为死亡态。运行态的线程调用静态方法sleep(long millis)进入睡眠态,过了指定的睡眠时间(millis/1000)秒,线程重新变为运行态,接着运行。调用成员方法wait()方法进入等待态,需要别的线程调用Object的成员方法notify()或者notifyAll(),又或者时间到了,有可能(?)唤醒重新进入就绪态,重新等待JVM的调度。
介绍阻塞态,需要先介绍一个新的概念——线程的优先级。每个线程都有优先级,在Java中,线程的最小优先级为常量Thread.MIN_PRIORITY,其值为1。最大的优先级为Thread.MAX_PRIORITY,值为10。如果不设置优先级,那么线程的默认优先级为Thread.NORM_PRIORITY,值为5。通过getPriority()方法可以得到线程的优先级,而setPriority(int newPriority)则是设置优先级,当设置的参数大于所允许的最大优先级时,线程的优先级就变为所允许的最大优先级。
为什么会有最大优先级呢?因为这里有一个设置最大优先级的方法:setMaxPriority(int maxP)。同样的,也能查到最大优先级getMaxPriority()。但是线程的最大优先级不能超过父线程组的最大优先级,因此会在maxP和父线程最大优先级选择较小的那一个。注:maxP不能超过正常的优先级范围,否则不起作用。
当有多个处于就绪态线程时,优先级高的线程会优先执行,进入运行态。优先级一样则随机选择。如果线程共享资源,但是资源只能被有限个线程占用,JVM就会优先选取优先级高的线程进入运行态,而其余的就绪态线程由于资源短缺就会转换为阻塞态。当资源准备就绪,则阻塞态线程会自动重新进入就绪态。
小笔记:
mythread.start()会启动一个新线程,并在新线程中运行run()方法。
而mythread.run()则会直接在当前线程中运行run()方法,并不会启动一个新线程来运行run()。