zoukankan      html  css  js  c++  java
  • Thinking in Java from Chapter 21

    From Thinking in Java 4th Edition

    并发

    线程可以驱动任务,因此你需要一种描述任务的方式,这可由Runnable接口来提供。

    要想定义任务,只需要实现Runnable接口,并编写run()方法,使得该任务可以执行你的命令。

    public class LiftOff implements Runnable {
    	protected int countDown = 10;	// Default
    	private static int taskCount = 0;
    	private final int id = taskCount++;
    	
    	public LiftOff() {}
    	public LiftOff(int countDown){
    		this.countDown = countDown;
    	}
    	
    	public String status(){
    		return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") + "). ";
    	}
    	
    	public void run(){
    		while(countDown-- > 0){
    			System.out.println(status());
    			Thread.yield();
    		}
    	}
    }
    

    从Runnable导出一个类时,它必须具有run()方法,但是它不会产生任何内在的线程能力。要实现线程行为,你必须显式地将一个任务附着到线程上。

    将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器:

    public class BasicThread {
    	public static void main(String[] args){
    		Thread t = new Thread(new LiftOff());
    		t.start();
    		System.out.println("Waiting for LiftOff");
    	}
    } /* Output:
    Waiting for LiftOff
    #0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!), 
    */
    

    Thread构造器只需要一个Runnable对象。

    1. 调用Thread对象的start()方法为该线程执行必要的初始化操作。 

    2. 调用Runnable对象的run()方法,以便在这个新线程中启动该任务。

    从输出可以看出start()方法迅速返回了,因为"Waiting for LiftOff"消息在倒计时完成之前就出现了。

    实际上你产生的是对LiftOff.run()方法的调用,并且这个方法还没完成,但是因为LiftOff.run()是由不同的线程执行的,因此你仍旧可以执行main()线程中的其他操作。

    如果添加更多的线程去驱动更多的任务,就可以看到所有任务彼此之间是如何呼应的:

    public class MoreBasicThreads {
    	public static void main(String[] args){
    		for(int i = 0; i < 5; ++i)
    			new Thread(new LiftOff()).start();
    		System.out.println("Waiting for LiftOff");
    	}
    } /* Output:
    Waiting for LiftOff
    #3(9), #1(9), #3(8), #1(8), #3(7), #1(7), #1(6), #1(5), 
    #1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #4(9), #4(8), 
    #4(7), #4(6), #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), 
    #2(3), #2(2), #2(1), #2(LiftOff!), #0(9), #4(5), #3(6), 
    #4(4), #3(5), #4(3), #3(4), #4(2), #3(3), #4(1), #3(2), 
    #4(LiftOff!), #0(8), #3(1), #0(7), #3(LiftOff!), #0(6), 
    #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!),
    */
    

    使用Executor

    Executor在客户端任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。

    Executor用来代替MoreBasicThreads.java中显式创建Thread对象。

    ExecutorService知道如何构建上下文来执行Runnable对象。

    import java.util.concurrent.*;
    
    public class CachedThreadPool {
    	public static void main(String[] args){
    		ExecutorService exec = Executors.newCachedThreadPool();   // No space between new and Cached
    		for(int i = 0; i < 5; ++i)
    			exec.execute(new LiftOff());
    		exec.shutdown();
    	}
    } /* Output:
    #0(9), #2(9), #0(8), #1(9), #0(7), #0(6), #0(5), #0(4), 
    #0(3), #0(2), #0(1), #0(LiftOff!), #2(8), #1(8), #3(9), 
    #2(7), #1(7), #3(8), #2(6), #1(6), #3(7), #2(5), #2(4), 
    #4(9), #3(6), #1(5), #3(5), #1(4), #1(3), #1(2), #1(1), 
    #1(LiftOff!), #4(8), #2(3), #4(7), #4(6), #3(4), #4(5), 
    #3(3), #4(4), #3(2), #4(3), #3(1), #4(2), #2(2), #4(1), 
    #4(LiftOff!), #3(LiftOff!), #2(1), #2(LiftOff!),
    */
    

    上例中,CachedThreadPool将为每个任务都创建一个线程。

    ExecutorService对象是使用静态的Executor方法创建的,这个方法可以确定其Executor类型。

    常见的情况是,Executor被用来创建和管理系统中的所有的任务。

    对shutdown()方法的调用可以防止新任务被提交给这个Executor,当前任务(本例中为main的线程)将继续运行在shutdown()被调用之前提交的所有任务。

    下面的程序展示了CachedThreadPool替换为不同类型的Executor。FixedThreadPool使用了有限的线程集来执行所提交的任务

    import java.util.concurrent.*;
    
    public class FixedThreadPool {
    	public static void main(String[] args){
    		// Constructor argument is number of threads:
    		ExecutorService exec = Executors.newFixedThreadPool(5);
    		for(int i = 0; i < 5; ++i)
    			exec.execute(new LiftOff());
    		exec.shutdown();
    	}
    } /* Output:
    #0(9), #2(9), #4(9), #0(8), #2(8), #3(9), #1(9), #1(8), 
    #0(7), #4(8), #0(6), #4(7), #0(5), #4(6), #4(5), #4(4), 
    #4(3), #4(2), #4(1), #4(LiftOff!), #1(7), #1(6), #1(5), 
    #1(4), #1(3), #1(2), #1(1), #1(LiftOff!), #3(8), #2(7), 
    #0(4), #3(7), #2(6), #0(3), #3(6), #2(5), #0(2), #3(5), 
    #0(1), #3(4), #2(4), #0(LiftOff!), #3(3), #2(3), #3(2), 
    #2(2), #3(1), #2(1), #3(LiftOff!), #2(LiftOff!),
    */
    

    有了FixedThreadPool,你就可以一次性预先执行代价高昂的线程分配。这可以节省时间,因为你不用为每个任务都固定地付出创建线程的开销。 

    SingleThreadExecutor就像是线程数量为1的FixedThreadPool。如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前结束,所有的任务将使用相同的线程。

    下例中可以看到每个任务都是按照它们被提交的顺序、并且是在下一个任务开始之前完成的。因此SingleThreadExecutor会序列化所有提交给它们的任务,并会维护它自己(隐藏)的悬挂任务。

    import java.util.concurrent.*;
    
    public class SingleThreadExecutor {
    	public static void main(String[] args){
    		ExecutorService exec = Executors.newSingleThreadExecutor();
    		
    		for(int i = 0; i < 5; ++i)
    			exec.execute(new LiftOff());
    		exec.shutdown();
    	}
    } /* Output:
    #0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(LiftOff!), 
    #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(LiftOff!), 
    #2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(LiftOff!), 
    #3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(LiftOff!), 
    #4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(LiftOff!),
    */
    

    从任务中返回值

    Runnable是执行工作的独立任务,但是它不返回任何值。如果希望能够返回值,则必须实现Callable接口而不是Runnable接口。

    Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它:

    import java.util.concurrent.*;
    import java.util.*;
    
    class TaskWithResult implements Callable<String> {
    	private int id;
    	public TaskWithResult(int id){
    		this.id = id;
    	}
    	
    	public String call() {
    		return "result of TaskWithResult " + id;
    	}
    }
    
    public class CallableDemo {
    	public static void main(String[] args){
    		ExecutorService exec = Executors.newCachedThreadPool();
    		ArrayList<Future<String>> results = new ArrayList<Future<String>>();
    		
    		for(int i = 0; i < 10; ++i)
    			results.add(exec.submit(new TaskWithResult(i)));
    		for(Future<String> fs : results)
    			try {
    				// get() blocks until completion:
    				System.out.println(fs.get());
    			} catch(InterruptedException e){
    				System.out.println(e);
    				return;
    			} catch(ExecutionException e) {
    				System.out.println(e);
    			} finally {
    				exec.shutdown();
    			}
    	}
    } /* Output:
    result of TaskWithResult 0
    result of TaskWithResult 1
    result of TaskWithResult 2
    result of TaskWithResult 3
    result of TaskWithResult 4
    result of TaskWithResult 5
    result of TaskWithResult 6
    result of TaskWithResult 7
    result of TaskWithResult 8
    result of TaskWithResult 9
    */
    

    submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化 。

    1. 可以用isDone()方法来查看Future是否完成

    2. 任务完成时,可以调用get()方法来获取该结果

    也可以不用isDone()进行检查就直接调用get(),这种情况下,get()将阻塞直至结果准备就绪。

    休眠

    影响任务行为的一种简单方法是调用sleep(),这将使任务中止执行给定的时间。

    在LiftOff类中,把yield()的调用换成sleep()将得到:

    import java.util.concurrent.*;
    
    public class SleepingTask extends LiftOff {
    	public void run(){
    		try {
    			while(countDown-- > 0){
    				System.out.print(status());
    				// Old-style
    				// Thread.sleep(100);
    				// Java SE5/6-style:
    				TimeUnit.MILLISECONDS.sleep(100);
    			}
    		} catch(InterruptedException e) {
    			System.err.println("Interrupted");
    		}
    	}
    	
    	public static void main(String[] args){
    		ExecutorService exec = Executors.newCachedThreadPool();
    		for(int i = 0; i < 5; ++i)
    			exec.execute(new SleepingTask());
    		exec.shutdown();
    	}
    } /* Output:
    #0(9), #3(9), #1(9), #4(9), #2(9), 
    #0(8), #3(8), #1(8), #2(8), #4(8), 
    #0(7), #4(7), #2(7), #3(7), #1(7), 
    #4(6), #0(6), #3(6), #1(6), #2(6), 
    #4(5), #3(5), #2(5), #0(5), #1(5), 
    #4(4), #3(4), #2(4), #1(4), #0(4), 
    #4(3), #2(3), #3(3), #0(3), #1(3), 
    #4(2), #2(2), #0(2), #3(2), #1(2), 
    #4(1), #2(1), #0(1), #3(1), #1(1), 
    #4(LiftOff!), #2(LiftOff!), #0(LiftOff!), #3(LiftOff!), #1(LiftOff!),
    */
    

    sleep()调用可以抛出InterruptedException异常,并可以看到,它在run()中捕获。因为异常不能跨线程传播回main(),所以你必须在本地处理所有在任务内部产生的异常。

    优先级

    调度器将倾向于让优先权最高的线程先执行,然而,这并不意味着优先权较低的线程将得不到执行(也就是说,优先权不会导致死锁)。优先级较低的线程仅仅是执行的频率较低。

    绝大多数时间里,所有线程都应该按照默认的优先级运行。试图操纵线程的优先级通常是一种错误。

    可以用getPriority()来读取现有线程的优先级,并且在任何时候都可以通过setPriority()来修改它:

    import java.util.concurrent.*;
    
    public class SimplePriorities implements Runnable {
    	private int countDown = 5;
    	private volatile double d;	// No optimization
    	private int priority;
    	
    	public SimplePriorities(int priority){
    		this.priority = priority;
    	}
    	
    	public String toString(){
    		return Thread.currentThread() + ": " + countDown;
    	}
    	
    	public void run(){
    		Thread.currentThread().setPriority(priority);
    		while(true){
    			// An expensive, interruptable operation:
    			for(int i = 1; i < 100000; ++i){
    				d += (Math.PI + Math.E) / (double)i;
    				if(0 == i % 1000)
    					Thread.yield();
    			}
    			
    			System.out.println(this);
    			if(0 == --countDown) return;
    		}
    	}
    	
    	public static void main(String[] args){
    		ExecutorService exec = Executors.newCachedThreadPool();
    		
    		for(int i = 0; i < 5; ++i)
    			exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
    		exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
    		
    		exec.shutdown();
    	}
    }
    

    让步

    如果知道已经完成了在run()方法的循环的一次迭代过程中所需的工作,就可以给线程调度机制一个暗示:你的工作已经做得差不多了,可以让别的线程使用CPU了。这个暗示将通过yield()方法做出(不过这只是一个暗示,没有任何机制保证它将会被采纳)。

    当调用yield()时,你也在建议具有相同优先级的其他线程可以运行。

    后台线程

    后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可缺少的部分。

    当所有非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。

    反过来,只要有非后台线程还在运行,程序就不会终止:

    import java.util.concurrent.*;
    import static net.mindview.util.Print.*;
    
    public class SimpleDaemons implements Runnable {
    	public void run(){
    		try {
    			while(true) {
    				TimeUnit.MILLISECONDS.sleep(100);
    				print(Thread.currentThread() + " " + this);
    			}
    		} catch(InterruptedException e) {
    			print("sleep() interrupted");
    		}
    	}
    	
    	public static void main(String[] args) throws Exception {
    		for(int i = 0; i < 10; ++i){
    			Thread daemon = new Thread(new SimpleDaemons());
    			daemon.setDaemon(true);	// Must call before start()
    			daemon.start();
    		}
    		
    		print("All daemons started");
    		TimeUnit.MILLISECONDS.sleep(175);
    	}
    } /* Output:
    All daemons started
    Thread[Thread-4,5,main] SimpleDaemons@187a0b5
    Thread[Thread-2,5,main] SimpleDaemons@153c85c
    Thread[Thread-3,5,main] SimpleDaemons@23fc9c
    Thread[Thread-8,5,main] SimpleDaemons@b211be
    Thread[Thread-0,5,main] SimpleDaemons@11f65e4
    Thread[Thread-1,5,main] SimpleDaemons@15332f2
    Thread[Thread-9,5,main] SimpleDaemons@1da14a7
    Thread[Thread-7,5,main] SimpleDaemons@16b9e5b
    Thread[Thread-5,5,main] SimpleDaemons@1bbee70
    Thread[Thread-6,5,main] SimpleDaemons@1cbca8d
    */
    

    SimpleDaemons.java创建了显示的线程,以便可以设置它们的后台标志。通过编写定制的ThreadFactory可以定制由Executor创建的线程的属性(后台、优先级、名称):

    package net.mindview.util;
    import java.util.concurrent.*;
    
    public class DaemonThreadFactory implements ThreadFactory {
    	public Thread newThread(Runnable r) {
    		Thread t = new Thread(r);
    		t.setDaemon(true);
    		return t;
    	}
    }
    

    现在可以用一个新的DaemonThreadFactory作为参数传递给Executor.newCachedThreadPool():

    package net.mindview.util;
    import java.util.concurrent.*;
    
    public class DaemonThreadFactory implements ThreadFactory {
    	public Thread newThread(Runnable r) {
    		Thread t = new Thread(r);
    		t.setDaemon(true);
    		return t;
    	}
    }
    
    
    // Using a Thread Factory to create daemons.
    import java.util.concurrent.*;
    import net.mindview.util.*;
    import static net.mindview.util.Print.*;
    
    public class DaemonFromFactory implements Runnable {
    	public void run(){
    		try {
    			while(true){
    				TimeUnit.MILLISECONDS.sleep(100);
    				print(Thread.currentThread() + " " + this);
    			}
    		} catch(InterruptedException e){
    			print("Interrupted");
    		}
    	}
    	
    	public static void main(String[] args) throws Exception {
    		ExecutorService exec = Executor.newCachedThreadPool(new DaemonThreadFactory());
    		for(int i = 0; i < 10; ++i)
    			exec.execute(new DaemonFromFactory());
    		print("All daemons started");
    		TimeUnit.MILLISECONDS.sleep(500);	// Run for a while
    	}
    } /* Output
    All daemons started
    Thread[Thread-0,5,main] DaemonFromFactory@c01009
    Thread[Thread-9,5,main] DaemonFromFactory@1bd343
    Thread[Thread-7,5,main] DaemonFromFactory@7c321a
    Thread[Thread-5,5,main] DaemonFromFactory@c79e29
    Thread[Thread-3,5,main] DaemonFromFactory@279922
    Thread[Thread-1,5,main] DaemonFromFactory@1fcb7ef
    Thread[Thread-8,5,main] DaemonFromFactory@7e3dfb
    Thread[Thread-2,5,main] DaemonFromFactory@1415cb9
    Thread[Thread-4,5,main] DaemonFromFactory@334426
    Thread[Thread-6,5,main] DaemonFromFactory@15226d3
    Thread[Thread-0,5,main] DaemonFromFactory@c01009
    Thread[Thread-9,5,main] DaemonFromFactory@1bd343
    ....
    */
    

    每个静态的ExecutorService创建方法都被重载为接受一个ThreadFactory对象,而这个对象将被用来创建新的线程:

    package net.mindview.util;
    import java.util.concurrent.*;
    
    public class DaemonThreadPoolExecutor extends ThreadPoolExecutor {
    	public DaemonThreadPoolExecutor() {
    		super(0, Integer.MAX_VALUE, 60L, TimeUnit, SECONDS, new SynchronousQueue<Runnable>(), new DaemonThradFactory());
    	}
    }
    

    可以通过iDaemon()方法来确定线程是否是一个后台线程。如果是一个后台线程,那么它创建的任何线程将自动被设置成后台线程。 

    // Daemon threads spawn other daemon threads.
    import java.util.concurrent.*;
    import static net.mindview.util.Print.*;
    
    class Daemon implements Runnable {
    	private Thread[] t = new Thread[10];
    	
    	public void run() {
    		for(int i = 0; i < t.length; ++i){
    			t[i] = new Thread(new DaemonSpawn());
    			t[i].start();
    			printnb("DaemonSpawn " + i + " started, ");
    		}
    		
    		for(int i = 0; i < t.length; ++i)
    			printnb("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ", ");
    		
    		while(true)
    			Thread.yield();
    	}
    }
    
    class DaemonSpawn implements Runnable {
    	public void run(){
    		while(true)
    			Thread.yield();
    	}
    }
    
    public class Daemons {
    	public static void main(String[] args) throws Exception {
    		Thread d = new Thread(new Daemon());
    		d.setDaemon(true);
    		d.start();
    		printnb("d.isDaemon() = " + d.isDaemon() + ", ");
    		
    		// Allow the daemon threads to 
    		// finish their startup processes:
    		TimeUnit.SECONDS.sleep(1);
    	}
    } /* Output:
    d.isDaemon() = true, 
    DaemonSpawn 0 started, 
    DaemonSpawn 1 started, 
    DaemonSpawn 2 started, 
    DaemonSpawn 3 started, 
    DaemonSpawn 4 started, 
    DaemonSpawn 5 started, 
    DaemonSpawn 6 started, 
    DaemonSpawn 7 started, 
    DaemonSpawn 8 started, 
    DaemonSpawn 9 started, 
    t[0].isDaemon() = true, 
    t[1].isDaemon() = true, 
    t[2].isDaemon() = true, 
    t[3].isDaemon() = true, 
    t[4].isDaemon() = true, 
    t[5].isDaemon() = true, 
    t[6].isDaemon() = true, 
    t[7].isDaemon() = true, 
    t[8].isDaemon() = true, 
    t[9].isDaemon() = true,
    */
    

    应该意识到后台进程在不执行finally子句的情况下就会终止其run()方法:

    // Daemon threads don't run the finally clause
    import java.util.concurrent.*;
    import static net.mindview.util.Print.*;
    
    class ADaemon implements Runnable {
    	public void run() {
    		try {
    			print("Starting ADaemon");
    			TimeUnit.SECONDS.sleep(1);
    		} catch(InterruptedException e){
    			print("Exiting via InterruptedException");
    		} finally {
    			print("This should always run?");
    		}
    	}
    }
    
    public class DaemonsDontRunFinally {
    	public static void main(String[] args) throws Exception {
    		Thread t = new Thread(new ADaemon());
    		t.setDaemon(true);
    		t.start();
    	}
    } /* Output:
    Starting ADaemon
    */
    

    目前的示例中,都是实现了Runnable。在非常简单的情况下,你可能会希望使用直接从Thread继承这种可替换的方式:

    public class SimpleThread extends Thread {
    	private int countDown = 5;
    	private static int threadCount = 0;
    	
    	public SimpleThread() {
    		// Store the thread name:
    		super(Integer.toString(++threadCount));
    		start();
    	}
    	
    	public String toString() {
    		return "#" + getName() + "(" + countDown + "), ";
    	}
    	
    	public void run() {
    		while(true){
    			System.out.print(this);
    			if(0 == --countDown)
    				return;
    		}
    	}
    	
    	public static void main(String[] args){
    		for(int i = 0; i < 5; ++i)
    			new SimpleThread();
    	}
    } /* Output:
    #2(5), #2(4), #2(3), #2(2), #2(1), 
    #4(5), #4(4), #4(3), #4(2), #4(1), 
    #5(5), #5(4), #5(3), #5(2), #5(1), 
    #3(5), #3(4), #3(3), #3(2), #3(1), 
    #1(5), #1(4), #1(3), #1(2), #1(1),
    */
    

    你可以通过适当的Thread构造器为Thread对象赋予具体的名称,这个名称可以通过使用getName()从toString()中获得。

    另一种可能会看到的惯用法是自管理的Runnable:

    // A Runnable containing its own driver Thread.
    public class SelfManaged implements Runnable {
    	private int countDown = 5;
    	private Thread t = new Thread(this);
    	
    	public SelfManaged() {t.start();}
    	
    	public String toString() {
    		return Thread.currentThread().getName() + "(" + countDown + "), ";
    	}
    	
    	public void run() {
    		while(true){
    			System.out.print(this);
    			if(0 == --countDown)
    				return;
    		}
    	}
    	
    	public static void main(String[] args){
    		for(int i = 0; i < 5; ++i)
    			new SelfManaged();
    	}
    } /* Output:
    Thread-3(5), Thread-3(4), Thread-3(3), Thread-3(2), Thread-3(1), 
    Thread-5(5), Thread-5(4), Thread-5(3), Thread-5(2), Thread-5(1), 
    Thread-6(5), Thread-6(4), Thread-6(3), Thread-6(2), Thread-6(1), 
    Thread-4(5), Thread-4(4), Thread-4(3), Thread-4(2), Thread-4(1), 
    Thread-7(5), Thread-7(4), Thread-7(3), Thread-7(2), Thread-7(1),
    */
    

    这与从Thread继承并没有什么特别的差异,只是语法稍微晦涩一些。但是,实现接口使得你可以继承另一个不同的类,而从Thread继承将不行。

    注意,这个示例中的start()是在构造器中调用的。应该意识到,在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。这是优选Executor而不是显式地创建Thread对象的另一个原因。

    有时,通过使用内部类来讲线程代码隐藏在类中将会很有用:

  • 相关阅读:
    Git :版本控制工具进阶
    Git 提交本地代码
    Git创建代码仓库
    Git
    SQLlite数据库的增删改查
    Android学习第十天
    Android学习第九天
    Android学习第八天
    Android学习第七天
    【k8s】Deployment
  • 原文地址:https://www.cnblogs.com/kid551/p/4323287.html
Copyright © 2011-2022 走看看