JAVA编程思想学习总结:第21章第4节终结与协作

一、线程状态

一个纯种可以处于以下四种状态之一:

1)新建:当线程被创建时,它只会短暂地处于这种状态,此时它已经分配了必需的系统资源,并执行了初始化。

2)就绪:在这种状态下,只要高度器将时间片分配给线程,线程就可以运行。

3)运行:线程调度程序从就绪状态的线程中选择一个线程作为当前线程时线程所处的状态。

3)阻塞:当某个条件阻止它运行时线程进入的状态。当线程处于阻塞状态时,高度器将忽略线程,不会分配给线程任何CPU时间。直到线程重新进入了就绪状态,它才有可能执行操作。

4)死亡:处于死亡或终止状态的线程将进入该状态,并且再也不会得到CPU时间。

线程进入阻塞状态的原因:

1)通过调用sleep(milliseconds)使任务进入休眠状态。在这种情况下,任务在指定的时间内不会运行。注意,此时线程并不会让出对象锁。

2)通过调用yield()建议系统调度该任务进入休眠状态。注意,同 sleep()一样,此时线程并不会让出对象锁。

3)调用wait()使线程扶起。直到线程得到了notiyf()或notifyAll()消息。

4)任务在等待某个输入/输出完成。

5)任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了这个锁。

import java.util.concurrent.*;
import java.util.*;
/*
 * P692-693
 */
class Count {
	private int count=0;
	private Random rand =new Random(47);
	public synchronized int increment(){
		int temp =count;
		if(rand.nextBoolean())	Thread.yield();
		return (count=++temp);
	}
	public synchronized int value(){return count;}
}
class Entrance implements Runnable{
	private static Count count = new Count();
	private static List entrances =new ArrayList();
	private int number =0;
	//Doesn't need synchronization to read;
	private final int id;
	private static volatile boolean canceled =false;
	//Atomic operation on a volatile field;
	public static void cancel(){canceled =true;}
	public Entrance(int id){
		this.id=id;
		//Keep this task in a list.Also prevents
		//garbage collection of dead tasks;
		entrances.add(this);
	}
	public void run(){
		while(!canceled){
			synchronized(this){
				++number;
			}
			System.out.println(this +" Total: "+count.increment());
			//该语句执行在synchronized块外,会出现count.increment()执行完成,CPU被抢占,导致先输出的total值大于后输出的total值
			try{
				TimeUnit.MILLISECONDS.sleep(100);
			}catch(InterruptedException e){
				System.out.println("sleep interrupted");
			}
		}
		System.out.println("Stopping " +this);
	}
	public synchronized int getValue(){return number;}
	public String toString(){
		return "Entrance " + id +" : "+getValue();
	}
	public static int getTotalCount(){
		return count.value();
	}
	public static int sumEntrances(){
		int sum =0;
		for(Entrance entrance : entrances)
			sum += entrance.getValue();
		return sum;
	}
}
public class OrnamentalGarden {
	public static void main(String[] args)throws Exception{
		ExecutorService exec =Executors.newCachedThreadPool();
		for(int i =0 ;i<5;i++)
			exec.execute(new Entrance(i));
		//Run for a while ,then stop and collect the data;
		TimeUnit.SECONDS.sleep(3);
		Entrance.cancel();
		exec.shutdown();
		if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
			System.out.println("Some tasks were not terminated!");
		System.out.println("Total: " +Entrance.getTotalCount());
		System.out.println("sum of Entrances: "+ Entrance.sumEntrances());
	}
} 
二、中断

Thread类包含interrupt()方法,如果一个线程已经被阻塞,或者试图执行阻塞操作,那么设置这个线程的中断状态将抛出InterruptedException。当抛出该异常或者该任务调用Thread.interrypted()时,中断状态将被复位。Thread.interrupted()提供了离开run()循环而不抛出异常的第二种方式。

在Executor上调用shutdownNow(),那么它将发送一个interrupt()调用给它启动的所有线程,以关闭该Executor的所有任务。如果只是希望中断某一个任务,则可以使用submit()而不是executor()来启动任务,该方法返回一个泛型Future。持有这个泛型可以调用cancel()方法,传值为true时,则在该纯种上调用interrupt()以停止这个线程的权限。

import java.util.concurrent.*;
import java.io.*;
/*
 * P695-696
 */
class SleepBlocked implements Runnable{
	public void run(){
		try {
			TimeUnit.SECONDS.sleep(100);
		}catch(InterruptedException e){
			System.out.println("InterruptedException");
		}
		System.out.println("Exiting SleepBlocked.run()");
	}
}
class IOBlocked implements Runnable{
	private InputStream in;
	public IOBlocked(InputStream is){
		in=is;
	}
	public void run(){
		try{
			System.out.println("Waiting for read():");
			in.read();
		}catch(IOException e){
			if(Thread.currentThread().isInterrupted()){
				System.out.println("Interrupted from blicked I/O");
			}else{
				throw new RuntimeException(e);
			}
		}
		System.out.println("Exiting IOBlocked.run()");
	}
}
class SynchronizedBlocked implements Runnable{
	public synchronized void f(){
		while(true){//Never releases lock
			Thread.yield();
		}
	}
	public SynchronizedBlocked(){
		new Thread(){
			public void run(){
				f();//Lock acquired(获得) by this thread
			}
		}.start();
	}
	public void run(){
		System.out.println("Trying to call f()");
		f();
		System.out.println("Exiting SynchronizedBlocked.run()");
	}
}

public class Interrupting {
	private static  ExecutorService exec=Executors.newCachedThreadPool();
	static void test(Runnable r) throws InterruptedException{
		Future f =exec.submit(r);
		TimeUnit.MILLISECONDS.sleep(100);
		System.out.println("Interrupting " + r.getClass().getName());
		f.cancel(true);//Interrupts if running
		System.out.println("Interrupt sent to "+ r.getClass().getName());
	}
	public static void main(String[] args)throws Exception{
		test(new SleepBlocked());
		test(new IOBlocked(System.in));
		test(new SynchronizedBlocked());
		TimeUnit.SECONDS.sleep(3);
		System.out.println("Aborting with System.exit(0)");
		System.exit(0);//since last 2 interrupts failed
	}
}
输出结果:

Interrupting concurrent.SleepBlocked
InterruptedException
Exiting SleepBlocked.run()
Interrupt sent to concurrent.SleepBlocked
Waiting for read():
Interrupting concurrent.IOBlocked
Interrupt sent to concurrent.IOBlocked
Trying to call f()
Interrupting concurrent.SynchronizedBlocked
Interrupt sent to concurrent.SynchronizedBlocked
Aborting with System.exit(0)

这其中,只有sleep()的调用会抛出中断异常。这说明只能够中断sleep()或者任何要求抛出InterruptedException的调用。但是不能中断正在试图获取synchronized锁或者试图执行I/O操作的线程。这意味着I/O具有锁住多线程程序的潜在可能。

对于这个问题,有一个略显笨拙但是有时确实行之有效的解决方案是关闭任务在其上发生阻塞的底层资源。

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/*
 * P697
 */
public class CloseResource {
	public static void main(String[] args)throws Exception{
		ExecutorService exec =Executors.newCachedThreadPool();
		ServerSocket server =new ServerSocket(8080);
		InputStream socketInput=new Socket("localhost",8080).getInputStream();
		exec.execute(new IOBlocked(socketInput));
		exec.execute(new IOBlocked(System.in));
		TimeUnit.MILLISECONDS.sleep(100);
		System.out.println("Shutting down all threads");
		exec.shutdown();
		TimeUnit.SECONDS.sleep(1);
		System.out.println("Closing "+ socketInput.getClass().getName());
		socketInput.close();//Releases blocked thread
		TimeUnit.SECONDS.sleep(1);
		System.out.println("Colsing "+ System.in.getClass().getName());
		System.in.close();//Release blocked thread
	}
}
很可惜,在我的环境下,程序运行结果和书中并不一样。

程序运行抛出Exception in thread "pool-1-thread-1" java.lang.RuntimeException: java.net.SocketException异常。好吧,我现在并不知道怎么回事。

import java.util.concurrent.*;
import java.util.concurrent.locks.*;
class BlockedMutex{
	private Lock lock=new ReentrantLock();
	public BlockedMutex(){
		//Acquire it right away, to demonstrate interruption
		//of a task blocked on a ReentrantLock;
		lock.lock();
	}
	public void f(){
		try{
			//This will never be available to a second task
			lock.lockInterruptibly();//Special call
			// 线程在请求lockInterruptibly并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。
			System.out.println("lock acquired in f()");
		}catch(InterruptedException e){
			System.out.println("Interrupted from lock acquisition in f()");
		}
	}
}
class Blocked2 implements Runnable{
	BlockedMutex blocked =new BlockedMutex();
	public void run(){
		System.out.println("Waiting for f() in BlockedMutex");
		blocked.f();
		System.out.println("Broken out of blocked call");
	}
}
public class Interrupting2 {
	public static void main(String[] args )throws Exception{
		Thread t =new Thread(new Blocked2());
		t.start();
		TimeUnit.SECONDS.sleep(1);
		System.out.println("Issuing t.interrupt");
		t.interrupt();
	}
}
以上方法通过ReentrantLock实现阻塞的任务具备可以被中断的能力。主要是通过lockInterruptibly这个方法来实现的。当它被打断时,是会抛出 InterruptedException异常的。

三、检查中断

通过调用interrupted()来检查中断状态,这不仅可以知道interrupt()是否被调用过,而且还可以清除中断状态。清除中断状态可以确保并发结构不会就某个任务被中断这个问题通知你两次。

import java.util.concurrent.*;

class NeedsCleanup{
	private final int id; 
	public NeedsCleanup(int ident){
		id=ident;
		System.out.println("NeedsCleanup " +id);
	}
	public void cleanup(){
		System.out.println("Cleaning up "+id);
	}
}
class Blocked3 implements Runnable{
	private volatile double d=0.0;
	public void run(){
		try{
			while(!Thread.interrupted()){
				//point1
				NeedsCleanup n1=new NeedsCleanup(1);
				//Start try-finally immediately after definition
				//of n1,to guarantee proper cleanup of n1;
				try{
					System.out.println("Sleeping");
					TimeUnit.SECONDS.sleep(1);
					//point2
					NeedsCleanup n2 = new NeedsCleanup(2);
					//Guarante proper cleanup of n2;
					try{
						System.out.println("Calculation");
						//A time-consuming ,non-blocking operation;
						for(int i=1;i<2500000;i++){
							d=d+(Math.PI+Math.E)/d;
						}
						System.out.println("Finished time-consuming operation");
					}finally{
						n2.cleanup();
					}
				}finally{
					n1.cleanup();
				}
			}
			System.out.println("Exiting via while() test");
		}catch(InterruptedException e){
			System.out.println("Exiting via EnterruptedException");
		}
	}
}
public class InterruptingIdiom{
	public static void main(String[] args)throws Exception{
		if(args.length!=1){
			System.out.println("usage : java InterruptingIdiom delay-in-mS");
			System.exit(1);
		}
		Thread t=new Thread(new Blocked3());
		t.start();
		TimeUnit.MILLISECONDS.sleep(1050);//输入1000,1050和1100会有不同的结果,主要是由于从不同的point1和point2退出循环。
		t.interrupt();
	}
}

四、线程协作

wait()提供一种在任务之间对活动同步的方式。wati()有两种形式,第一种版本接受毫秒数作为参数,其意义与sleep()的参数意思相同,指在此期间暂停。但是与sleep()不同的是,对于wait()而言:1)wait期间对象锁是释放的。2)可以通过notify(),notifyAll,或者时间到期,从wait()中恢复执行。
对于不带参数的形式,wait()会无限等待下去,直到纯种收到notify()或者nofifyAll()消息。
实际上,只能在同步控制方法或同步控制块里调用wait(),notify和notifyAll()。
import java.util.concurrent.*;
class Car{
	private boolean waxOn=false;
	public synchronized void waxed(){
		waxOn=true;//Ready to buff
		notifyAll();
	}
	public synchronized void buffed(){
		waxOn=false;//Ready for another coat of wax
		notifyAll();
	}
	public synchronized void waitForWaxing() throws InterruptedException{
		while(waxOn==false)	wait();
	}
	public synchronized void waitForBuffing() throws InterruptedException{
		while(waxOn==true)	wait();
	}
}
class WaxOn implements Runnable{
	private Car car;
	public WaxOn(Car c){ car =c;}
	public void run(){
		try{
			while(!Thread.interrupted()){
				System.out.println("Wax on");
				TimeUnit.MILLISECONDS.sleep(200);
				car.waxed();
				car.waitForBuffing();
			}
		}catch(InterruptedException e){
			System.out.println("Exiting via interrupt");
		}
		System.out.print("Ending wax on task");
	}
}
class WaxOff implements Runnable{
	private Car car;
	public WaxOff(Car c){ car =c;}
	public void run(){
		try{
			while(!Thread.interrupted()){
				car.waitForWaxing();
				System.out.println("Wax off!");
				TimeUnit.MILLISECONDS.sleep(200);
				car.buffed();
			}
		}catch(InterruptedException e){
			System.out.println("Exiting via interrupt");
		}
		System.out.println("Ending Wax Off task");
	}
}
public class WaxOMatic {
	public static void main(String[] args)throws Exception{
		Car car = new Car();
		ExecutorService exec =Executors.newCachedThreadPool();
		exec.execute(new WaxOff(car));
		exec.execute(new WaxOn(car));
		TimeUnit.SECONDS.sleep(5);
		exec.shutdownNow();
	}
}

使用notify()和notifyAll()进行协作时,应该注意这种情况。即在notify()方法已经调用过后,线程才调用wait()方法,导致该线程一直处于等待状态。
notify()与notifyAll()的区别:使用notify()时,在众多等待同一个锁的任务中只有一个会被唤醒,因此如果希望使用 notify(),就必须保证被唤醒的是恰当的任务。别外,为了使用notify(),所有任务必须等待相同的条件,因为如果有多余任务在等待不同的条件,那么就不会知道是否唤醒了恰当的任务。如果使用notify(),当条件发生变化时,必须只有一个任务能够从中受益。最后,这些限制对所有可能存在的子类都必须总是起作用的。如果这些规则中有任何一条不满足,那么你就必须使用notifyAll()而不是notify();
import java.util.concurrent.*;
import java.util.*;
class Blocker{
	synchronized void waitingCall(){
		try{
			while(!Thread.interrupted()){
				wait();
				System.out.println(Thread.currentThread()+" ");
			}
		}catch(InterruptedException e){
			System.out.println("oh to exit this way");
		}
	}
	synchronized void prod(){notify();}
	synchronized void prodAll(){notifyAll();}
}
class Task implements Runnable{
	static Blocker blocker =new Blocker();
	public void run() { blocker.waitingCall();}
}
class Task2 implements Runnable{
	// A separate Blocker object;
	static Blocker blocker =new Blocker();
	public void run() {blocker.waitingCall();}
}
public class NotifyVsNotifyAll {
	public static void main(String[] args)throws Exception{
		ExecutorService exec =Executors.newCachedThreadPool();
		for(int i=0;i<5;i++){
			exec.execute(new Task());
		}
		exec.execute(new Task2());
		Timer timer=new Timer();
		timer.scheduleAtFixedRate(new TimerTask(){
			boolean prod =true;
			public void run(){
				if(prod){
					System.out.println("\nnotify() " );
					Task.blocker.prod();
					prod=false;
				}else{
					System.out.println("\nnotifyAll() ");
					Task.blocker.prodAll();
					prod=true;
				}
			}
		}, 400, 400);//run every 0.4 second
		TimeUnit.SECONDS.sleep(5);
		timer.cancel();
		System.out.println("\n Timer cancaled");
		TimeUnit.MILLISECONDS.sleep(500);
		System.out.println("Task2.blicker.prodAll() ");
		Task2.blocker.prodAll();
		TimeUnit.MILLISECONDS.sleep(500);
		System.out.println("\nShutting down");
		exec.shutdownNow();//Interrupt all tasks
	}
}
由于异常而离开循环和通过检查interrupted()标志离开循环是没有任何区别的——在两种情况下都要执行相同的代码,但是,事实上,这个示例选择了检查interrupted(),因为存在着两种离开循环的方式。如果没有覆盖从循环中退出的这两条路径,就会产生引入错误的风险。

五、示例代码

生产者-消费者队列
package concurrent;
/*
 * P714-715
 */
import java.util.concurrent.*;
import java.io.*;
class LiftOffRunner implements Runnable{
	private BlockingQueue rockets;
	public LiftOffRunner(BlockingQueue queue){
		rockets =queue;
	}
	public void add(LiftOff lo){
		try{
			rockets.put(lo);
		}catch(InterruptedException e){
			System.out.println("Interrupted during put()");
		}
	}
	public void run(){
		try{
			while(!Thread.interrupted()){
				LiftOff rocket =rockets.take();
				rocket.run();//Use this thread
			}
		}catch(InterruptedException e){
			System.out.println("Waking form take()");
		}
		System.out.println("Exiting LiftOffRunner");
	}
}

public class TestBlockingQueues {
	static void getkey(){
		try{
			//Compensate for Windows/Linux difference in the length of the result produced by the Enter key;
			new BufferedReader(new InputStreamReader(System.in)).readLine();
		}catch(java.io.IOException e){
			throw new RuntimeException(e);
		}
	}
	static void getkey(String message){
		System.out.println(message);
		getkey();
	}
	static void test(String msg,BlockingQueue queue){
		System.out.println(msg);
		LiftOffRunner runner =new LiftOffRunner(queue);
		Thread t =new Thread(runner);
		t.start();
		for(int i=0;i<5;i++)
			runner.add(new LiftOff(5));
		getkey("press 'Enter' ("+msg+")");
		t.interrupt();
		System.out.println("Finished " + msg + " test");
	}
	public static void main(String[] args){
		test("LinkedBlickingQueue", new LinkedBlockingQueue());
		test("ArrayBlockingQueue", new ArrayBlockingQueue(2));
		test("SynchronousQueue",new SynchronousQueue());
	}
}

任务间使用管道进行输入/输出
/*
 * P717-718
 */
import java.util.concurrent.*;
import java.io.*;
import java.util.*;
class Sender implements Runnable{
	private Random rand =new Random(47);
	private PipedWriter out =new PipedWriter();
	public PipedWriter getPipedWriter(){ return out;}
	public void run(){
		try{
			while(true){
				for(char c='A';c<='z';c++){
					out.write(c);
					TimeUnit.MILLISECONDS.sleep(rand.nextInt(500));
				}
			}
		}catch(IOException e){
			System.out.println(e+" Sender write exception");
		}catch(InterruptedException e){
			System.out.println(e +" sender sleep inpterupted");
		}
	}
}
class Receiver implements Runnable{
	private PipedReader in;
	public Receiver(Sender sender)throws IOException{
		in = new PipedReader(sender.getPipedWriter());
	}
	public void run(){
		try{
			while(true){
				//Blocks until characters are there;
				System.out.println("Read: " + (char)in.read()+ ", ");
			}
		}catch(IOException e){
			System.out.println(e + " Receiver read exception");
		}
	}
}
public class PipedIO {
	public static void main(String[] args)throws Exception{
		Sender sender = new Sender();
		Receiver receiver = new Receiver(sender);
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(sender);
		exec.execute(receiver);
		TimeUnit.SECONDS.sleep(4);
		exec.shutdownNow();
	}
}
PipedReader的建立必须在构造器中与一个PipedWriter相关联。Sender把数据放进Writer,然后休眠一段时间(随机数)。然而,Receiver没有sleep()和wait()。但当它调用read()时,如果没有更多的数据,管道将自动阻塞。
在shutdownNow()被调用时,可以看到PipedReader与普通I/O之间最重要的差异——PipedReader是可中断的。如果你将in,read调用修改为System.in.read(),那么interrupt()将不能打断read()调用。

你可能感兴趣的:(JAVA,concurrent,JAVA,多线程)