21章 并发
1、 基本线程机制
并发编程使我们可以将程序划分为多个分离的,独立运行的任务。通过使用多线程机制,这些独立的任务中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,但是你的程序使得每个任务都好像有其自己的cpu一样。其底层机制是切分cpu时间,但通常你不需要考虑它。
线程模型为编程带来了便利,它简化了在单一程序中同时交织在一起的多个操作的处理。在使用线程时候,cpu将轮流给每个任务分配其占用时间。每个任务都觉得自己在一直占用cpu,但实时上cpu时间是划分成片段分配给了所有的任务(例外情况程序确实运行在多个cpu上)。线程的一大好处是可以使你从这个层次抽身出来,既代码不必知道它是运行在具有一个还是多个cpu的机器上。所以,使用线程机制是一种建立透明,可扩展的程序的方法,如果程序运行太慢,为机器增添了一个cpu就能很容易地加快程序的运行速度。多任务和多线程往往是使用多处理器系统的最合理方式
2、 定义任务
需要实现Runnable接口,然后提交给Thread构造器,调用start方法。
public class LiftOff implements Runnable {
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount ++;
public LiftOff(int countDown){
this.countDown = countDown;
}
public LiftOff() {
// TODO Auto-generated constructor stub
}
public String status(){
return "#" + id +"(" + (countDown >0 ? countDown :"Liftoff!") + "),";
}
@Override
public void run() {
while (countDown -- > 0) {
System.out.println(status());
Thread.yield();//让步,将cpu给其他线程。
}
}
}
public class BasicThreads {
public static void main(String[] args) {
Thread t = new Thread(new LiftOff());
t.start();
System.out.println("Waiting for LiftOff");
}
}
3、使用Executor
java.util.concurrent 包中的执行器(Executor)将为你管理Thread对象,从而简化并发编程
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();//结束这个Executor,防止新的任务提交给这个Executor.
}
}
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(5); //提前分配5个线程
for(int i =0 ; i< 5; i++)
exec.execute(new LiftOff());
exec.shutdown();//结束这个Executor,防止新的任务提交给这个Executor.
}
}
public class SingleThreadPool {
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadPool(); //创建单个线程
for(int i =0 ; i< 5; i++)
exec.execute(new LiftOff());
exec.shutdown();//结束这个Executor,防止新的任务提交给这个Executor.
}
}
CachedThreadPool 在程序执行过程中通常会创建与所需要数据相同的线程,然后在它回收旧线程时停止创建新的线程,因此它是合理的Executor的首选。SingleThreadExecutor就像是线程为1的FixedThradPool,如果向它提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有的任务将使用相同的线程。
4.从任务中产生返回值
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult( int id) {
this.id = id;
}
public String call(){
return "reusult of TaskWithrResult" + id;
}
}
public class CallableDemo{
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<>();
for (int i = 0; i < 10; i++)
results.add(exec.submit(new TaskWithResult(i)));
for (Future<String> future : results) {
try {
System.out.println(future.get());
} catch (Exception e) {
System.out.println(e);
}finally {
exec.shutdown();
}
}
}
}
如果需要返回一个值,则需要实现Callable接口,然后调用ExecutorService的submit方法,返回一个Future对象,然后用get方法获取返回值。
5、休眠、优先级、让步
影响任务行为的一种简单方法是调用sleep(),这将使任务中止执行给定的时间。
Thread.sleep(毫秒);
线程的优先级将线程的重要性传递给了调度器。尽管cpu处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权最高的线程先执行。
Thread.currentThread().setPriority(优先级); //修改当前线程的优先级。
当程序已经完成了最重要的工作,可以给线程调度机制一个暗示,你的工作已经做的差不多了,可以让别的线程使用cpu了。这个暗示通过调用yield()方法来作出,
Thread.yield();//让步,将cpu给其他线程。
6 后台线程(守护线程)
所谓的后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时候,程序也就终止了,同时会杀死进程中所有的后台线程,必须在start之前设置为守护线程。
public class SimpleDaenoms implements Runnable{
@Override
public void run() {
try {
while(true){
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread() + "" + this);
}
} catch (Exception e) {
System.out.println(e);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread daemon = new Thread(new SimpleDaenoms());
daemon.setDaemon(true); //设置线程为后台线程。且必须在start之前设置
daemon.start();
}
}
将线程转换为守护线程(后台线程)可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
7、编码的变体
可以直接继承Thread类从而实现run方法开启线程
public class SimpleThread extends Thread{
private int countDown = 5;
private static int threadCount = 0;
public SimpleThread() {
super(Integer.toString(++ threadCount));
start();
}
public String toString() {
return "#" +getName() + "(" + countDown + "),";
}
@Override
public void run() {
while (true) {
System.out.println(this);
if (--countDown == 0) {
return ;
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new SimpleThread();
}
}
}
8 加入一个线程
一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行,如果某个线程在另为一个线程t上调用t.join ,此线程将被挂起,直到目标线程t结束才恢复。
class Sleeper extends Thread{
private int duration;
public Sleeper(String name,int sleepTime){
super(name);
duration = sleepTime;
start();
}
public void run(){
try {
sleep(duration);
} catch (Exception e) {
System.out.println(getName() + "was interrupted." + "isInterrupted()" + isInterrupted());
return ;
}
System.out.println(getName() + "has awakened");
}
}
class Joiner extends Thread{
private Sleeper sleeper;
public Joiner(String name,Sleeper sleeper){
super(name);
this.sleeper = sleeper;
start();
}
public void run(){
try {
sleeper.join();
} catch (Exception e) {
System.out.println("Interrupted");
}
System.out.println(getName() + " join completed");
}
}
public class Joining {
public static void main(String[] args){
Sleeper
sleepy = new Sleeper("Sleepy", 1500),
grumppy = new Sleeper("Grumpy", 1500);
Joiner
dopey = new Joiner("Dopey", sleepy),
doc = new Joiner("Doc", grumppy);
grumppy.interrupt();
}
9 捕获异常
由于线程的本质特性,使得你不能捕获从线程中逃逸的异常,一旦异常逃出run方法,它就会向外传播到控制台,可以用Thead.UncaughtExceptionHandler.uncaughException() 来捕获线程中的异常。
class ExeceptionThread2 implements Runnable{
public void run(){
Thread thread = Thread.currentThread();
System.out.println("run() by" + thread);
System.out.println("eh =" + thread.getUncaughtExceptionHandler());
throw new RuntimeException();
}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught" + e);
}
}
class HandlerThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r){
System.out.println(this + "creating new Thread");
Thread t = new Thread(r);
System.out.println("created " + t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println("eh =" + t.getUncaughtExceptionHandler());
return t;
}
}
public class CaptureUncaughtException {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool(new HandlerThreadFactory());
executorService.execute(new ExeceptionThread2());
}
}