Java多线程详解 联系客服

发布时间 : 星期六 文章Java多线程详解更新完毕开始阅读

224 Java面向对象程序设计

while(flag){

System.out.print(\ try{ Thread.sleep(1000);

}catch(InterruptedException e){} }

System.out.println(\ }

public void stopRun(){ flag = false; } }

public class ThreadStop{

public static void main(String args[]){ Timer timer = new Timer();

Thread thread = new Thread(timer); thread.setName(\ thread.start();

for(int i=0;i<100;i++){ System.out.print(\ try{ Thread.sleep(100);

}catch(InterruptedException e){} }

timer.stopRun(); } }

_____________________________________________________________________________▃ 该程序在Timer类中定义了一个布而变量flag,同时定义了一个stopRun()方法,在其中将该变量设置为false。在主程序中通过调用该方法,从而改变该变量的值,使得run()方法的while循环条件不满足,从而实现结束线程的运行。

说明 在Thread类中除了stop()方法被标注为不推荐(deprecated) 使用外,suspend()方

法和resume()方法也被标明不推荐使用,这两个方法原来用作线程的挂起和恢复。 9.4.2 线程阻塞条件

处于运行状态的线程除了可以进入死亡状态外,还可能进入就绪状态和阻塞状态。下面分别讨论这两种情况:

(1) 运行状态到就绪状态

处于运行状态的线程如果调用了yield()方法,那么它将放弃CPU时间,使当前正在运行的线程进入就绪状态。这时有几种可能的情况:如果没有其他的线程处于就绪状态等待运行,该线程会立即继续运行;如果有等待的线程,此时线程回到就绪状态状态与其他线程竞争CPU时间,当有比该线程优先级高的线程时,高优先级的线程进入运行状态,当没有比该线程优先级高的线程时,但有同优先级的线程,则由线程调度程序来决定哪个线程进入运行状态,因此线程调用yield()方法只能将CPU时间让给具有同优先级的或高优先级的线程

225 而不能让给低优先级的线程。

一般来说,在调用线程的yield()方法可以使耗时的线程暂停执行一段时间,使其他线程有执行的机会。

(2) 运行状态到阻塞状态

有多种原因可使当前运行的线程进入阻塞状态,进入阻塞状态的线程当相应的事件结束或条件满足时进入就绪状态。使线程进入阻塞状态可能有多种原因:

① 线程调用了sleep()方法,线程进入睡眠状态,此时该线程停止执行一段时间。当时间到时该线程回到就绪状态,与其他线程竞争CPU时间。

Thread类中定义了一个interrupt()方法。一个处于睡眠中的线程若调用了interrupt()方法,该线程立即结束睡眠进入就绪状态。

② 如果一个线程的运行需要进行I/O操作,比如从键盘接收数据,这时程序可能需要等待用户的输入,这时如果该线程一直占用CPU,其他线程就得不到运行。这种情况称为I/O阻塞。这时该线程就会离开运行状态而进入阻塞状态。Java语言的所有I/O方法都具有这种行为。

③ 有时要求当前线程的执行在另一个线程执行结束后再继续执行,这时可以调用join()方法实现,join()方法有下面三种格式:

? public void join() throws InterruptedException 使当前线程暂停执行,等待调用该方法

的线程结束后再执行当前线程。

? public void join(long millis) throws InterruptedException 最多等待millis毫秒后,当前

线程继续执行。

? public void join(long millis, int nanos) throws InterruptedException 可以指定多少毫

秒、多少纳秒后继续执行当前线程。 上述方法使当前线程暂停执行,进入阻塞状态,当调用线程结束或指定的时间过后,当前线程线程进入就绪状态,例如执行下面代码:

t.join();

将使当前线程进入阻塞状态,当线程t执行结束后,当前线程才能继续执行。

④ 线程调用了wait()方法,等待某个条件变量,此时该线程进入阻塞状态。直到被通知(调用了notify()或notifyAll()方法)结束等待后,线程回到就绪状态。

⑤ 另外如果线程不能获得对象锁,也进入就绪状态。 后两种情况在下一节讨论。

9.5 线程的同步与共享

前面程序中的线程都是独立的、异步执行的线程。但在很多情况下,多个线程需要共享数据资源,这就涉及到线程的同步与资源共享的问题。 9.5.1 资源冲突

下面的例子说明,多个线程共享资源,如果不加以控制可能会产生冲突。 程序9.7 CounterTest.java

class Num{

private int x=0; private int y=0; void increase(){ x++;

226 Java面向对象程序设计 y++; }

void testEqual(){

System.out.println(x+\ } }

class Counter extends Thread{ private Num num; Counter(Num num){ this.num=num; }

public void run(){ while(true){

num.increase(); } } }

public class CounterTest{

public static void main(String[] args){ Num num = new Num(); Thread count1 = new Counter(num); Thread count2 = new Counter(num); count1.start(); count2.start(); for(int i=0;i<100;i++){ num.testEqual(); try{ Thread.sleep(100); }catch(InterruptedException e){ } } } }

_____________________________________________________________________________▃ 上述程序在CounterTest类的main()方法中创建了两个线程类Counter的对象count1和count2,这两个对象共享一个Num类的对象num。两个线程对象开始运行后,都调用同一个对象num的increase()方法来增加num对象的x和y的值。在main()方法的for()循环中输出num对象的x和y的值。程序输出结果有些x、y的值相等,大部分x、y的值不相等。

出现上述情况的原因是:两个线程对象同时操作一个num对象的同一段代码,通常将这段代码段称为临界区(critical sections)。在线程执行时,可能一个线程执行了x++语句而尚未执行y++语句时,系统调度另一个线程对象执行x++和y++,这时在主线程中调用testEqual()方法输出x、y的值不相等。

这里可能出现x的值小于y的值的情况,为什么? 9.5.2 对象锁的实现

上述程序的运行结果说明了多个线程访问同一个对象出现了冲突,为了保证运行结果正确(x、y的值总相等),可以使用Java语言的synchronized关键字,用该关键字修饰方法。用synchronized关键字修饰的方法称为同步方法,Java平台为每个具有synchronized代码段的对象关联一个对象锁(object lock)。这样任何线程在访问对象的同步方法时,首先必须获得对象锁,然后才能进入synchronized方法,这时其他线程就不能再同时访问该对象的同步方法

227 了(包括其他的同步方法)。

通常有两种方法实现对象锁:

(1) 在方法的声明中使用synchronized关键字,表明该方法为同步方法。

对于上面的程序我们可以在定义Num类的increase()和testEqual()方法时,在它们前面加上synchronized关键字,如下所示:

synchronized void increase(){ x++; y++; }

synchronized void testEqual(){

System.out.println(x+\}

一个方法使用synchronized关键字修饰后,当一个线程调用该方法时,必须先获得对象锁,只有在获得对象锁以后才能进入synchronized方法。一个时刻对象锁只能被一个线程持有。如果对象锁正在被一个线程持有,其他线程就不能获得该对象锁,其他线程就必须等待持有该对象锁的线程释放锁。

如果类的方法使用了synchronized关键字修饰,则称该类对象是线程安全的,否则是线程不安全的。

如果只为increase()方法添加synchronized 关键字,结果还会出现x、y的值不相等的情况,请考虑为什么?

(2) 前面实现对象锁是在方法前加上synchronized 关键字,这对于我们自己定义的类很容易实现,但如果使用类库中的类或别人定义的类在调用一个没有使用synchronized关键字修饰的方法时,又要获得对象锁,可以使用下面的格式:

synchronized(object){ //方法调用

}

假如Num类的increase()方法没有使用synchronized 关键字,我们在定义Counter类的run()方法时可以按如下方法使用synchronized为部分代码加锁。

public void run(){ while(true){

synchronized (num){ num.increase(); } } }

同时在main()方法中调用testEqual()方法也用synchronized关键字修饰,这样得到的结果相同。

synchronized(num){ num.testEqual(); }

对象锁的获得和释放是由Java运行时系统自动完成的。

每个类也可以有类锁。类锁控制对类的synchronized static代码的访问。请看下面的例子:

public class X{ static int x, y;

static synchronized void foo(){ x++;

y++; } }