欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

21章 并发

程序员文章站 2022-05-07 08:37:51
...

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());
	}
}