Java里的线程控制

这篇文章接着上篇文章<<java 线程简介>> 写的.

http://blog.csdn.net/nvd11/article/details/19118683


上一篇文章提到,  java程序猿可以利用类Thread或其接口Runnable开启一条新线程.

但开启一条新线程之后, 不能任由它不管啊.

其实java有很多方法让程序猿控制线程的执行过程.



一, 线程的三种状态切换

一个线程由线程类Thread或其派生类的start()启动.

启动后的线程具有3种状态.


1.1 就绪状态

注意, 1个线程t被t.start()函数启动后, 并不是立即可以被cpu执行. 而是进入了就绪状态.

在就绪状态中的线程代表了有了cpu执行的资格. 由于想抢占cpu被执行的线程有很多. cpu并不一定立即执行t对应的线程.


实际上, 一般情况下在1个时间点, cpu只会执行1个线程, 但是这个cpu会不断跳到另1个线程去执行它. 前提是这个"另1个进程是"就绪状态.


1.2 运行状态

相对地, cpu当前执行进程所在的状态就是执行状态.

一般来讲, 一个程序运行中同1个时间点只会有1条线程处于运行状态.    

但是cpu会不断地切换所执行的线程.  一旦cpu切换到另1个线程执行. 原来运行的线程就会从运行状态切换到就绪状态. 

这种行为我们就称为cpu的调度.


1.3 阻塞状态.

一旦1个线程遇到阻塞事件(例如sleep()函数), (无论它原来是在运行状态还是就绪状态),就会进入阻塞状态.

处于阻塞状态的线程无法被cpu调度, 也就是无法被cpu执行(进入运行状态).

直接阻塞接触后, 该线程返回就绪状态.



简单图示如下:


1.4 现实例子


假如哟有A, B, C 三个人都想使用另1个人D都电脑上网,  但是D只有1台电脑。

然后D就让A B C 三人首先坐在厅里都长沙发上, 然后D挑选1个人进房间使用电脑。

每隔一段时间D会把使用电脑都人赶回厅里都沙发上, 然后再从沙发上挑1个人进去使用电脑。


那么3个人就都有机会使用电脑, 令网络上觉得A B C3个人有电脑都假象。


1.那么在房间使用电脑都人就相对于多线程的运行状态。

2.坐在沙发的人就相当于多线程的就绪状态, 他们都有机会被D选中使用电脑.

3.如过A突然肚子痛(阻塞事件)离开了沙发, 那么A就相当于进入了线程的阻塞状态, 除非A再次返回沙发上, 才会有资格被D选中。



二, 线程的优先级设置

一般来讲, 假如有两条线程再执行, cpu是会随机切换执行的。

但是实际上线程也有重要性的区别,我们可以利用优先级别设置来控制某1个线程更有机会抢占cpu资源。


Java提供1个线程调度器来监控程序中启动后进入就绪状态都所有线程。

线程调度器挑选执行线程的机制受到线程优先级的影响。


2.1 类Thread 的3个静态常量成员

线程的优先级用数字表示, 范围从1到10, 1个线程都默认优先级是5。

这个数字越大表示线程都优先级越高


类Thread还提供了3个静态常量成员, 提供给程序猿,可以代替数字来使用。

他们分别是


Thread.MIN_PRIORITY=1

Thread.MAX_PRIORITY=10

Thread.NORM_PRIORITY=5


注意, 这个3个成员都是static final的. 代表都是它们都属于类本身,而且不能被赋值。


2.2 线程对象获取和设置优先级的方法。

2.2.1 int getPriority();

调用这个方法可以获得对应线程对象都优先级。 例如 t.getPriority() 返回都就是线程对象t都当前优先级。


2.2.2 void setPriority(int newPriority)

线程对象可以调用这个方法来改变本身都优先级.

注意,参数范围必须再1-10, 否则会抛出java.lang.IllegalArgumentException 异常。

2.3 设置优先级都一个例子

package Thread_kng.Td_priority_kng;

class M_thrd_6 implements Runnable{
	public void run(){
		int i;
		Thread cur_thd = Thread.currentThread();
		for (i=1; i<101; i++){
			System.out.printf("Thread %s: priority is %d, i is %d\n", cur_thd.getName(),cur_thd.getPriority(),i);
		}
	}
}
public class Td_priority_1{
	public static void f(){
		M_thrd_6 s = new M_thrd_6();
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		t1.setName("T1");
		t2.setName("T2");

		t1.start();

		t1.setPriority(Thread.MIN_PRIORITY);  //set the priority to 1
		t2.setPriority(Thread.NORM_PRIORITY + 3); //set the priority to 8
		t2.start();

	}
}

看上面的例子:

我利用实验Runnable 接口的1个对象构造两个子线程。

在run方法里。

这两个子线程(T1, T2)循环100次把i输出到屏幕上, 并顺便输出线程的名字,和线程都当前优先级(getPriority())都输出到屏幕。


看例子下面的f()函数, 首先执行的是t1. 再执行的是t2.

但是t1的优先级别调低了, t2的优先级别调高了。


结果就是T2比T1先执行完成。

执行结果:



2.4 优先级对线程调度的真正影响

见到结果中,虽然T2的优先级比T1高得多, 但是T2和T1实际上还是不断被切换执行的。


2.4.1 时间片论算法。

所谓时间片论算法就是指java中1个线程处于执行状态的单次时间是一定的。

所以即使T2的优先级比T1高, 并不是指T2处于单次执行状态的时间比较长。


假如单次执行状态的时间是x毫秒,


那么无论T2还是T1,被执行x毫秒后,都会被强制退回就绪状态, cpu再从就绪状态的线程选1个使其进入执行状态。

而优先级别高的线程被选中的机率相对更高


那么单位时间内, T2进入执行状态的次数会比T1高。 


实际上, 这个单次执行时间比输出100次i的时间小得多, 所以上面运行例子会见到T1 和 T2不断切换执行。



2.4.2 实际开发中,java并不单纯依赖优先级别来决定线程执行次序。

也就是讲, 优先级别只是影响cpu调度的其中1个因数。


还有如下其他因数:

1. 突发事件, 例如打印机的打印线程发现打印机的纸张用完, 那么系统就会马上执行打印线程的警告机制。

2. 线程执行时间, 通常会把消耗资源小,执行快的线程放在前面运行。

3. 最长等待时间, 一个线程即使优先级别很少, 但是超过了一段等待时间后, 会被强制进入执行状态。


2.4 现实例子

举回上面A B C三个人上网的时间, 优先级别就是A B C三个人跟D的个人关系了。

高优先级别的人每次再就绪状态中被D选中的机率更大, 但是每次进入房间上网的时间还是一样的。





三, 线程控制的常用方法

下面开始介绍线程的常用方法, 也是面试中问得比较多的地方:


3.1 sleep()

sleep(int n) 是1个常用的线程函数,参数s代表的是毫秒数字,他的作用是令线程停止执行并进入阻塞状态n毫秒。

在这n毫秒内, 这个线程不能被cpu执行。

过了n毫秒后, 这个线程重新进入就绪状态,但是不代表这个线程能马上被cpu执行。

上面说过了, 就绪状态的线程想要执行还需要取决于cpu的调度。


它在线程基类Thread中定义如下:


public static void sleep(long millis,
                         int nanos)
                  throws InterruptedException


    在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。

    参数:
        millis - 以毫秒为单位的休眠时间。
        nanos - 要休眠的另外 0-999999 纳秒。
    抛出:
        IllegalArgumentException - 如果 millis 值为负或 nanos 值不在 0-999999 范围内。
        InterruptedException - 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除



需要注意的是: sleep函数会抛出异常, 而且这个异常是非RuntimeException, 所以必须进行捕捉。

关于异常的文章里讲过, 捕捉要不进行try catch, 要不在函数定义中使用throws.

但是sleep()函数最终都是卸载线程类的run()方法里面的。 而基类Thread的run()方法是不抛出任何异常的。 所以由于多态的存在,重写的run()的方法不能throws任何异常.

所以一般在使用sleep函数时,必须使用try catch


下面是1个例子:

package Thread_kng.Td_ctrl;

class M_thrd_7 implements Runnable{
	public void run(){
		java.text.DateFormat d1 = java.text.DateFormat.getDateTimeInstance();
		java.util.Date now;
		Thread cur_thd = Thread.currentThread();

		int i;
		for (i=0; i<10; i++){
			now = new java.util.Date();
			System.out.printf("Thread %s: i is %d, time is %s\n",cur_thd.getName(), i, d1.format(now));

			try{
				cur_thd.sleep(3000);   //sleep 3 seconds, must be put into the try{}
			}
			catch(Exception e){

			}
		}

	}
}

public class Td_ctrl_1{
	public static void f(){
		M_thrd_7 s = new M_thrd_7();
		Thread t1 = new Thread(s);
		t1.setName("T1");
		t1.start();

		Thread t2 = new Thread(s);
		t2.setName("T2");
		t2.start();

	}
}

这个例子的线程业务很简单, 就是把i从9输出到0, 但是每输出1次暂停3000毫秒。

需要注意的是,sleep()函数是类Thread的一个成员方法, 所以使用sleep()方法的前提是1个Thread类或其派生类的一个对象。

在下面的f()方法中,利用1个对象创建了两个线程t1.t2并启动。

结果就是t1 和 t2两条线程都是每3秒在屏幕输出一次信息:


输出结果:

Thread T1: i is 0, time is Feb 17, 2014 2:42:10 PM
Thread T2: i is 0, time is Feb 17, 2014 2:42:10 PM
Thread T1: i is 1, time is Feb 17, 2014 2:42:13 PM
Thread T2: i is 1, time is Feb 17, 2014 2:42:13 PM
Thread T1: i is 2, time is Feb 17, 2014 2:42:16 PM
Thread T2: i is 2, time is Feb 17, 2014 2:42:16 PM
Thread T1: i is 3, time is Feb 17, 2014 2:42:19 PM
Thread T2: i is 3, time is Feb 17, 2014 2:42:19 PM
Thread T1: i is 4, time is Feb 17, 2014 2:42:22 PM
Thread T2: i is 4, time is Feb 17, 2014 2:42:22 PM
Thread T1: i is 5, time is Feb 17, 2014 2:42:25 PM
Thread T2: i is 5, time is Feb 17, 2014 2:42:25 PM
Thread T1: i is 6, time is Feb 17, 2014 2:42:28 PM
Thread T2: i is 6, time is Feb 17, 2014 2:42:28 PM
Thread T1: i is 7, time is Feb 17, 2014 2:42:31 PM
Thread T2: i is 7, time is Feb 17, 2014 2:42:31 PM
Thread T1: i is 8, time is Feb 17, 2014 2:42:34 PM
Thread T2: i is 8, time is Feb 17, 2014 2:42:34 PM
Thread T1: i is 9, time is Feb 17, 2014 2:42:37 PM
Thread T2: i is 9, time is Feb 17, 2014 2:42:37 PM
gateman@TPEOS classes $ 


sleep() 是1个静态方法, 一般来讲直接调用Thread.sleep(x),  就可以令当前执行的线程暂停x毫秒. 并不需要获得当前的线程对象.

3.2 yield()

yield方法是基类Thread的另1个成员方法。

在中文JDK的解析是这样的:

public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。


但是这个解释并不准确。

我们一般把yield方法简称为线程让步。


准确的解析如下:

当t线程执行t.yield() 后,会马上停止t的执行状态, 并且令t退回到就绪状态。


1. yield方法没有参数。

2. yield 会令线程的执行状态终止。

3. yield 会令线程退回到就绪状态。

4. 然后cpu还重新调度, 选择一个线程进入执行状态,  注意这个线程有可能是刚才yield退回就绪状态的线程。

5. 所以java jdk api 中午的解析是不准确的。并不是暂停线程, 并执行其他线程, 而是暂停线程, 重新让cpu调度。


也就是说1个线程执行yield后被退回就绪状态, 如果它的优先级别高, 它是有可能马上被cpu重新执行进行执行状态的。


那么yield()的意义是什么呢, 本屌也不是很sure,  但是如果1个线程,不断循环执行1个包含yield的方法, 那么每一次循环它都会让步一次。

间接地令到这个线程比其他不含yield的线程更低。


下面是1个例子:

package Thread_kng.Td_ctrl;

class M_thrd_8 extends Thread{
	public M_thrd_8(String name){
		super(name);
	}

	public void run(){
		int i;
		for (i=0; i<1000; i++){
			System.out.printf("Thread %s: i is %d",this.getName(), i);
			this.yield();
		}
	}
}

class M_thrd_9 extends Thread{
	public M_thrd_9(String name){
		super(name);
	}

	public void run(){
		int i;
		for (i=0; i<1000; i++){
			System.out.printf("\nThread %s: i is %d\n",this.getName(), i);
			//this.yield();
		}
	}
}

public class Td_yield_1{
	public static void f(){
		M_thrd_8 t1 = new M_thrd_8("T1");
		t1.start();

		M_thrd_9 t2 = new M_thrd_9("T2");
		t2.start();

	}
}

上面例子定义两个基本相同的线程类, 都是循环把i 从0 输出到 999。

但是t1 线程每1个循环都yield让步一次, t2 线程没有。

执行时, t2会比t1得到更多cpu资源。

如果这个两个线程属于不同进程, 那么具有yield方法的进程cpu占用率会稍低。


对于这个例子来说, 结果就是t2执行得比t1快, 而且比单纯地设置优先级别明显得多.

执行结果:

Thread T2: i is 956

Thread T2: i is 957

Thread T2: i is 958

Thread T2: i is 959

Thread T2: i is 960

Thread T2: i is 961

Thread T2: i is 962
Thread T1: i is 349
Thread T2: i is 963

Thread T2: i is 964

Thread T2: i is 965
Thread T1: i is 350
Thread T2: i is 966

Thread T2: i is 967

Thread T2: i is 968
Thread T1: i is 351
Thread T2: i is 969

Thread T2: i is 970
Thread T1: i is 352
Thread T2: i is 971


3.3 sleep() 和 yield()的区别

我们还是举回上面A B C三人用D的电脑上网的例子:


sleep()函数必须有个时间参数, 加入线程A执行了sleep(n),就相当于A有事去厕所, 时间是n。 这段时间内A处于阻塞状态, 无法被D选中去上网的。

过了n时间后, A回来到沙发上进入就绪状态, 但是还是需要D的调度才能去上网。


而yield方法就相当于正在上网的A被D拉回到沙发上, 然后重新等待D的调度, 但是有可能D还是选中A的, 也就是说A被拉回到沙发上, 但是马上又可以去上网。



3.4 join()

join() 是基类Thread的另1个成员方法。


JDK API 是如此定义的:

public final void join()
                throws InterruptedException

等待该线程终止

详细的定义是:

当线程t 调用t.join() 时, 暂停当前线程的执行, 除非t执行完成了, 当前线程继续执行。

注意当前线程是执行t.join()的线程。


现实例子:

又是A B C三人上网的例子, 假如B线程执行A.join() , 也相当于B告诉D,我离开一会, 等A上完网时我就才回来上网。

注意,C不受影响哦, 实际上就是B 1个人暂时退出, A和C两人简单切换上网, 直至A上完网, B才回来就绪状态!



值得注意的是, join()类似sleep()方法, 都会抛出异常。


下面是1个join()方法的例子:


package Thread_kng.Td_ctrl;

class M_thrd_10 extends Thread{
	public M_thrd_10(String name){
		super(name);
	}也就是说t3必须等t1完成才能执行

	public void run(){
		int i;
		for (i=0; i<1000; i++){
			System.out.printf("Thread %s: i is %d\n",this.getName(), i);
		}
	}
}

class M_thrd_11 extends Thread{
	private M_thrd_10 t_join;

	public M_thrd_11(String name, M_thrd_10 t_join){
		super(name);
		this.t_join = t_join;
	}

	public void run(){
		int i;
		for (i=0; i<501; i++){
			System.out.printf("Thread %s: i is %d\n",this.getName(), i);
		}

		try{
			t_join.join();	
		}
		catch(Exception e){

		}

		for (; i<1000; i++){
			System.out.printf("Thread %s: i is %d\n",this.getName(), i);
		}
	}
}

public class Td_join_1{
	public static void f(){
		M_thrd_10 t1 = new M_thrd_10("T1"); 
		M_thrd_10 t2 = new M_thrd_10("T2"); //will not impacted by t1.join()
		M_thrd_11 t3 = new M_thrd_11("T3",t1);

		t1.start();
		t3.start();
		t2.start();
	}
}

上面定义了两个线程类, 实例化了3个对象t1, t2, t3, 而t3的run()方法里, 调用了t1.join()。

也就是说t1. t2都把i从0输出到999

但是t3先输出到500 就必须等t1完成, 才继续输出501 到 999

也就是说t3必须等t1完成才能执行

但是t2是不受影响的。



执行结果:

Thread T1: i is 981
Thread T1: i is 982
Thread T1: i is 983
Thread T1: i is 984
Thread T1: i is 985
Thread T1: i is 986
Thread T1: i is 987
Thread T1: i is 988
Thread T1: i is 989
Thread T1: i is 990
Thread T1: i is 991
Thread T1: i is 992
Thread T1: i is 993
Thread T1: i is 994
Thread T1: i is 995
Thread T1: i is 996
Thread T1: i is 997
Thread T1: i is 998
Thread T1: i is 999
Thread T2: i is 899
Thread T3: i is 501
Thread T3: i is 502
Thread T3: i is 503
Thread T3: i is 504
Thread T3: i is 505
Thread T3: i is 506
Thread T3: i is 507
Thread T3: i is 508
Thread T3: i is 509
Thread T3: i is 510
Thread T3: i is 511
Thread T3: i is 512

3.5 wait() notify() notifyAll()

这个3个方法涉及同步的问题,  我会在以后介绍java同步的博文里再讲






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值