最近接手一个新项目,有点小忙。线程写了一半还要重新缕缕思路!
这次说点线程重要的东西吧,线程的控制。
Java自动调度没有逻辑约束的线程时,其执行顺序是没有保障的。但是可以通过编程调用一些调度线程的方法,来实现一定程度上对线程的调度。但要注意的是,这些调度线程的方法,有些是有保障的,有些只是影响线程进入执行状态的几率。
一、睡眠
在线程执行的过程中,调用sleep方法可以让线程睡眠一段指定的时间,等指定时间到达后,该线程则会苏醒,并进入准备状态等待执行。这是使正在执行的线程让出CPU的最简单方法之一。
public static void sleep(long millis)throws InterruptedException
public static void sleep(long millis,int nanos)throws InterruptedException
sleep()方法被重载了,但上述两个方法都可以使线程进入睡眠状态。参数millis为指定线程将睡眠的毫秒数,参数nanos为指定线程额外将睡眠的纳秒数。但要注意的是,纳秒级的计时是十分不准确的,不能用做时间基准。两个方法都有可能抛出InterruptedException捕获异常,因此在调用此方法时需要进行异常处理。两个方法都为静态方法,所以这两个方法不是与某个线程对象相关联的,其可以出现在任何位置,当执行到该方法时,让执行此方法的线程进入睡眠状态。也就是说,哪个线程执行了sleep方法哪个线程去睡眠,并不是调用特定线程对象的sleep方法。
要特别注意,线程醒来将进入准备状态,并不能保证立刻执行,因此指定的时间是线程暂停执行的最小时间。
class MyRunnable1 implements Runnable{
public void run() {
for(int i=0;i<5;i++) {
System.out.println("["+i+"] 我是线程1!!!");
//使此线程进入睡眠状态
try {
Thread.sleep(100);
} catch(InterruptedException ie) {
ie.printStackTrace();
}
}
}
}
//定义另外一个实现Runnable接口的类
class MyRunnable2 implements Runnable {
public void run(){
for(int i=0;i<5;i++){
System.out.println("<"+i+"> 我是线程2!!!");
//使此线程进入睡眠状态
try{
Thread.sleep(100);
}catch(InterruptedException ie){
ie.printStackTrace();
}
}
}
}
public class Sample{
public static void main(String[] args){
//创建实现Runnable接口的类
MyRunnable1 mr1=new MyRunnable1();
MyRunnable2 mr2=new MyRunnable2();
//创建线程Thread对象,并指定各自的target
Thread t1=new Thread(mr1);
Thread t2=new Thread(mr2);
//启动线程t1
t1.start();
//使主线程进入睡眠状态
try{
Thread.sleep(5);
}catch(InterruptedException ie){
ie.printStackTrace();
}
//启动线程t2
t2.start();
}
}
通过睡眠可以在一定程度上对线程进行调度,但要注意的是,这种方法不是绝对准确的,因为一方面计时不可能做到绝对准确,另一方面如果两个线程同时醒来进入准备状态,不能保证哪个线程被调度执行,所以不能依赖sleep来提供非常精确的定时操作。
二、线程的优先级
很多系统在对进程进行调度时,会采用优先级调度策略。Java中在对线程进行调度时,也采用了优先级调度策略,具体策略为:“优先级高的线程应该有更大的获取CPU资源而执行的概率,优先级低的线程并不是总不能执行”。也就是说,当前正在执行的线程优先级一般不会比正在准备状态等待执行的线程优先级低。(这是我语文能力发挥的极限了)
从Java的优先级策略中可以看出,哪个线程先执行是没有保障的。因此,优先级策略不适合对线程进行细节调度,而只是适合进行宏观调控(刚看完政府工作报告)。例如,运算量大的计算线程优先级设置的低一些,响应用户输入的线程优先级高一些,以改善程序的响应性能。
Java中线程的优先级用1到10之间的整数表示,数值越大优先级越高,默认优先级为5。例如,在没有特别指定的情况下,主线程的优先级别为5。另外,对于子线程,其初始优先级与其父线程的优先级相同。也就是说,若父线程优先级为8,则其子线程的初始优先级为8。Java中的线程优先级是依赖于本地平台的,在实际运行时会将线程在Java中的优先级映射到本地的某个优先级。这样,如果本地提供的优先级比10个要少,则Java中不同的优先级可能会映射成相同的本地优先级,而具有基本相同的执行概率。
public final void setPriority(int newPriority)
Thread类中也有三个常量用于描述线程优先级:MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY。
class MyThread1 extends Thread{
public void run(){
for(int i=0;i<=49;i++){
System.out.print("<Min"+i+"> ");
}
}
}
//定义另外一个继承Thread的类
class MyThread2 extends Thread{
public void run(){
for(int i=0;i<=49;i++){
System.out.print("[Max"+i+"] ");
}
}
}
//主类
public class Sample{
public static void main(String[] args){
//创建两个线程对象
MyThread1 t1=new MyThread1();
MyThread2 t2=new MyThread2();
//设置两个线程的优先级
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
//启动两个线程
t1.start();
t2.start();
}
}
还得墨迹句,当前正在执行的线程优先级一般不会比正在准备状态等待执行的线程优先级低,但要特别注意,真的没有人能保证一定是那样的。
三、线程的让步
实际运行中有时需要使当前运行的线程让出CPU,使其他线程得以执行,这时就需要使用线程让步的操作。线程让步包括两种方式:
1、线程只是让出当前的CPU资源,具体将CPU让给谁不确定。
2、线程将给指定的线程让步,指定的线程没有完成,其绝不恢复执行。
第一种方式是使用yield()方法
调用yield()方法可以使当前正在运行的线程让出CPU,回到准备状态,进而使其他线程有进入运行状态的机会。但要注意的是,该操作是没有保障的,很可能线程回到准备状态后又立刻被调度再次进入运行状态,也就是说yield让步不一定成功。因此,使用yield方法真正保证做到的是使正在运行的线程回到准备状态。该方法为静态方法,所以其与某个线程对象无关,也就是说当正在执行的线程运行了此方法,其将回到准备状态。
class MyRunnable implements Runnable{
//声明标识线程的两个字符串
private String flagl;
private String flagr;
//MyRunnable的构造器
public MyRunnable(String flagl,String flagr){
this.flagl=flagl;
this.flagr=flagr;
}
public void run(){
for(int i=0;i<30;i++){
System.out.print(flagl+i+flagr);
//调用yield方法使当前正在执行的线程让步
Thread.yield();
}
}
}
public class Sample{
public static void main(String[] args){
//创建两个实现Runnable接口的类的对象
MyRunnable mr1=new MyRunnable("[","] ");
MyRunnable mr2=new MyRunnable("<","> ");
//创建两个线程Thread对象,并指定执行的target
Thread t1=new Thread(mr1);
Thread t2=new Thread(mr2);
//启动线程t1、t2
t1.start();
t2.start();
}
}
两个线程在打印完一个数字后都会执行yield方法让步,所以两个线程是交替执行的。但要特别注意,虽然大部分情况下可以做到交替执行,但还是那句话这是没有保障的。
第二种方法使用join方法
当一个线程必须等待另一个线程执行完毕才恢复执行时,可以使用join方法。顾名思义,join方法可以达到将两个线程合并的效果,下面列出了该方法的签名。
public final void join()throws InterruptedException
public final void join(long millis)throws InterruptedException
public final void join(long millis,int nanos)throws InterruptedException
三个方法均为final的方法,在继承Thread类时不能对他们进行重写。同时,他们在执行时都有可能抛出InterruptedException捕获异常,因此在调用时必须进行异常处理。对于没有入口参数的join方法,将使得调用此方法的线程一直等待到此方法所在的线程执行完毕才恢复执行,效果上就像两个线程合并为了一个线程。参数millis为指定等待的毫秒数,参数nanos为额外的纳秒数。对于有入口参数的join方法,调用join方法的线程会等待指定的时间,如果在指定的时间内join方法所属的线程没有执行完毕则解除等待关系。
class MyThread extends Thread{
public void run(){
for(int i=0;i<30;i++){
System.out.print("["+i+"] ");
}
System.out.print("{子线程执行结束} ");
}
}
public class Sample{
public static void main(String[] args){
Thread t=new MyThread();
t.start();
//在主线程内打印数字
for(int i=0;i<30;i++){
if(i==10){
//调用join方法使主线程进行让步
try{
System.out.print("{使用了Jion方法} ");
t.join();
}
catch(InterruptedException ie){
ie.printStackTrace();
}
}
System.out.print("<"+i+"> ");
}
}
}
主线程在执行了了子线程t的join方法后,就暂停了执行,一直等到t线程执行完毕才恢复执行。用join方法进行两个线程之间的让步是有保证的,被让步的线程没有执行完毕,让步的线程不会恢复执行。
四、守护线程
其实在Java中根本没有单线程程序,就算开发人员只开发了主线程,后台还有很多辅助线程,比如线程调度、内存管理等。这些在后台运行的线程一般称之为守护线程。如何开发自己的守护线程?哈哈,其实很简单,开发守护线程与普通线程没有很大的区别,只要调用线程对象的setDaemon方法进行设置即可。
public final void setDaemon(boolean on)
class MyCommon implements Runnable{
public void run()
{
for(int i=0;i<10;i++)
{
System.out.print("["+i+"] ");
}
System.out.print("{前台用户线程执行完毕} ");
}
}
//定义守护线程的target类
class MyDaemon implements Runnable{
public void run(){
for(int i=0;i<100000;i++){
System.out.print("<"+i+"> ");
}
System.out.print("{后台守护线程执行完毕} ");
}
}
//主类
public class Sample{
public static void main(String[] args){
//创建实现Runnable接口的类的对象
Runnable mc=new MyCommon();
Runnable md=new MyDaemon();
//创建线程Thread对象,并指定target
Thread tc=new Thread(mc);
Thread td=new Thread(md);
//将t2设置为守护线程
td.setDaemon(true);
//启动线程
tc.start();
td.start();
}
}
上面代码中定义了两个实现Runnable接口的类,各自的对象分别作为前台与守护线程的target。前台线程打印0-9,守护线程打印0-99999,每个线程执行完毕都打印提示信息。
在主方法中,分别创建了两个线程对象,并调用setDaemon方法将td线程设置为守护线程,最后启动了两个线程。
从运行结果中可以看出,前台用户线程是保证执行完毕的,而后台守护线程并没有运行结束(只打印到157,离99999还差很远)。这是因为Java运行时环境判断程序是否结束的标准是:“是否所有的前台用户线程都执行完毕了,如果所有的前台线程完毕,程序退出”。(这里说明一点:根据机器的快慢,核心数量的多少,在自己机器上运行时,可能后台线程会打印到比较大的数值整个程序才退出,但含义是相同的)
如有不足希望大家多多指出。