《Java编程思想》读书笔记(十)

Author Avatar
wshunli 3月 09, 2018
  • 在其它设备中阅读本文章

《Java编程思想》读书笔记 —— 并发。

第21章 并发

Java 是一种多线程语言,并且提出了并发问题。

并发解决的问题大致可分为两类:速度,设计的可管理性。

基本的线程机制

并发编程使我们可以将程序划分为多个分离的、独立运行的任务。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,但是你的程序使得每个任务都好像有其自己的 CPU 一样。其底层机制是切分 CPU 的时间。

1、定义任务

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

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.print(status());
      Thread.yield();
    }
  }
} ///:~

Thread.yield() 的调用是对线程调度器的一种建议,建议线程调度器切换任务。

当从 Runnable 导出一个类时,它必须具有run()方法,但是这个方法并无特殊之处—它不会产生任何内在的线程能力。要实现线程行为,你必须显式将一个线程附着在线程上。

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!),
*///:~

2、Thread 类

将 Runnable 对象转变为工作任务的传统方式是把它提交给一个 Thread 构造器。下面的示例展示了如何用 Thread 来驱动 LiftOff 对象。

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

3、使用 Executor

import java.util.concurrent.*;
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: (Sample)
#0(9), #0(8), #1(9), #2(9), #3(9), #4(9), #0(7), #1(8), #2(8), #3(8), #4(8), #0(6), #1(7), #2(7), #3(7), #4(7), #0(5), #1(6), #2(6), #3(6), #4(6), #0(4), #1(5), #2(5), #3(5), #4(5), #0(3), #1(4), #2(4), #3(4), #4(4), #0(2), #1(3), #2(3), #3(3), #4(3), #0(1), #1(2), #2(2), #3(2), #4(2), #0(Liftoff!), #1(1), #2(1), #3(1), #4(1), #1(Liftoff!), #2(Liftoff!), #3(Liftoff!), #4(Liftoff!),
*///:~

CachedThreadPool 将为每个任务都创建一个线程,是合理得 Executor 的首选。

FixedThreadPool 可以一次性预先执行代价高昂的线程分配,限制了线程数量。不用为每个任务都固定的付出创建线程的开销,所以省时间。限制线程数量的好处在于防止线程的滥用。

SingleThreadExecutor 用于希望在另一个线程中连续运行的任何事物(长期存活的任务)。例如监听进入的套接字连接的任务(他只有一个线程)。

4、从任务中产生返回值

在 Java SE5 中引入的 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
*///:~

5、休眠
影响任务行为的一种简单方法是调用 sleep(),这将使任务中止执行给定的时间。
sleep会使得线程睡眠(即阻塞),这使得线程调度器可以切换到另一个线程,进而驱动另一个任务。

6、优先级
调度器将倾向于优先权更高的线程先执行(执行的频率高),但 CPU 处理线程集的顺序还是不确定的。
可以通过 Thread.currentThread().setPriority(int) 设置优先级,getPriority()获取优先级。

7、让步
让步通过调用yield()方法来做出(不过这只是一个暗示,没有任何机制保证它将会被采纳)。
对于任何重要的控制或在调整应用时,都不能依赖于yield()。

8、后台线程
当所有非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止。

必须在线程启动之前调用 setDaemon() 方法,才能把它设置为后台线程。即:

Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true);
daemon.start();

共享受限资源

关键字 synchronized 为防止资源冲突提供内置支持。

参考资料
1、java编程思想(读书笔记):21.并发 - CSDN博客
http://blog.csdn.net/he_world/article/details/52902701
2、《Java编程思想》——并发读书笔记 - CSDN博客
http://blog.csdn.net/qq_35362055/article/details/78135854
3、Java编程思想读书笔记一:并发 - CSDN博客
http://blog.csdn.net/jiankunking/article/details/54799830

如果本文对您有所帮助,且您手头还很宽裕,欢迎打赏赞助我,以支付网站服务器和域名费用。 https://paypal.me/wshunli 您的鼓励与支持是我更新的最大动力,我会铭记于心,倾于博客。
本文链接:https://www.wshunli.com/posts/96840e7.html