Java编程思想17
第二十一章:并发
基本的线程机制
  并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务(也被称为子任务)中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,但是你的程序使得每个任务都好像有其自己的CPU一样。其底层机制是切分CPU时间,但通常你不需要考虑它。
  线程模型为编程带来了便利,它简化了在单一程序中同时交织在一起的多个操作的处理。在使用线程时,CPU将轮流给每个任务分配其占用时间“。每个任务都觉得自己在一直占用CPU,但事实上CPU时间是划分成片段分配给了所有的任务(例外情况是程序确实运行在多个CPU之上)。线程的一大好处是可以使你从这个层次抽身出来,即代码不必知道它是运行在具有一个还是多个CPU的机器上。所以,使用线程机制,是一种建立透明的、可扩展的程序的方法,如果程序运行的太慢,为机器增添一个CPU就很容易的加快程序的运行速度。多任务和多线程往往是使用多处理器系统的最合理方式。
定义任务
线程可以驱动任务,因此你需要一种描述任务的方式。
使用Runnable接口:要想定义任务,只需实现Runable接口并编写run()方法,使得该任务可以执行你的命令,例如,下面的LiftOff任务将显示发射之前的倒计时:
package concurrency;
/**
 * @author Mr.Sun
 * @date 2022年09月03日 10:14
 *
 * 演示通过实现Runnable接口定义任务
 *
 * <p>
 *     显示发射之前的倒计时
 * </p>
 */
public class LiftOff implements Runnable {
    protected int countDown = 10;
    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!") + "). ";
    }
    @Override
    public void run() {
        while (countDown-- > 0) {
            System.out.print(status());
            Thread.yield();
        }
    }
} ///:~
标识符id可以用来区分任务的多个实例,它是final的,因为它一旦被初始化之后就不希望被修改。任务的run()方法通常总会有某种形式的循环,使得任务一直运行下去直到不再需要,所以要设定跳出循环的条件(有一种选择是直接从run)返回)。通常,run()被写成无限循环的形式,这就意味着,除非有某个条件使得run()终止,否则它将永远运行下去(在本章后面将会看到如何安全地终止线程)。
  在run()中对静态方法Thread.yield()的调用是对线程调度器(Java线程机制的一部分,可以将CPU从一个线程转移给另一个线程)的一种建议,它在声明:"我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机。”这完全是选择性的,但是这里使用它是因为它会在这些示例中产生更加有趣的输出∶你更有可能会看到任务换进换出的证据。
在下面的实例中,这个任务的run()不是由单独的线程驱动的,它是在main()中直接调用的(实际上,这里仍旧使用了线程,即总是分配给main()的那个线程)∶
package concurrency;
/**
 * @author Mr.Sun
 * @date 2022年09月03日 10:23
 */
public class MainThread {
    public static void main(String[] args) {
        LiftOff launch = new LiftOff();
        launch.run();
    }
} /* Output:
#0(9). #0(8). #0(7). #0(6). #0(5). #0(4). #0(3). #0(2). #0(1). #0(LiftOff!).
*///:~
Thread类:将Runnable对象转变为工作任务的传统方式是把它交给一个Thread构造器,下面的示例展示了如何使用Thread来驱动LiftOff对象:
package concurrency;
/**
 * @author Mr.Sun
 * @date 2022年09月03日 10:29
 *
 * 将Runnable对象转变为工作任务的传统方式是把它交给一个Thread构造器
 */
public class BasicThreads {
    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!).
*///:~
使用Executor
  Java SE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。Executor允许你管理异步任务的执行,而无须显式地管理线程的生命周期。Executor在Java SE5/6中是启动任务的优选方法。
  我们可以使用Executor来代替在BasicThreads.java中显示地创建Thread对象。LiftOff 对象知道如何运行具体的任务,与命令设计模式一样,它暴露了要执行的单一方法。ExecutorService(具有服务生命周期的Executor,例如关闭)知道如何构建恰当的上下文来执行Runnable对象。在下面的示例中,CachedThreadPool将为每个任务都创建一个线程。注意,ExecutorService对象是使用静态的Executor方法创建的,这个方法可以确定其Executor类型∶
package concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @author Mr.Sun
 * @date 2022年09月03日 16:04
 *
 * 使用Executor
 */
public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            exec.execute(new LiftOff());
        }
        exec.shutdown();
    }
}/* Output:
#0(9). #1(9). #2(9). #3(9). #0(8). #4(9). #1(8). #2(8). #3(8). #0(7). #4(8). #1(7). #2(7). #3(7). #0(6). #4(7). #1(6). #2(6). #3(6). #0(5). #1(5). #4(6). #3(5). #1(4). #2(5). #4(5). #0(4). #1(3). #2(4). #3(4). #0(3). #4(4). #1(2). #2(3). #3(3). #0(2). #4(3). #1(1). #2(2). #3(2). #0(1). #4(2). #1(LiftOff!). #2(1). #3(1). #0(LiftOff!). #4(1). #2(LiftOff!). #3(LiftOff!). #4(LiftOff!). 
*///:~
 
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号