JAVA编程思想笔记--并发

xiaoxiao2021-02-28  140

1.底层机制是切分CPU时间,CPU将轮流给每个任务分配其占用时间。

**

2.定义任务

** (1).线程可以驱动任务,因此需要一种可以描述任务的方式,这可以由Runnable接口来提供。要定义任务,只需实现Runnable接口并编写run()方法

public class LiftOff implements Runnable{ protected int countDown = 10 ; private static int taskCount = 0 ; //标识符可以用来区分任务的多个实例,它是final的,因为它一旦被初始化之后就 //不希望被修改 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(); } } } //调用 public class MainThread{ public static void main(String[] args) { LiftOff launch = new LiftOff(); launch.run(); } }

Thread.yield()的调用是对线程调度器(Java线程机制的一部分,可以将CPU从一个线程转移给另一个线程)的一种建议。

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

public class ArrayMaker { private static LiftOff liftoff ; //使用Thread来驱动LiftOff对象 public static void main(String[] args) { Thread t = new Thread(new LiftOff()); t.start(); System.out.println("Waiting for LiftOff"); //程序会同时运行两个方法,main()和LiftOff.run()是程序中与其他线程 //同时执行的代码 } }

(3)使用Executor 执行器Executor将为你管理Thread对象,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层。

public class ArrayMaker { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); //线程池 for(int i = 0 ;i < 5; i++) exec.execute(new LiftOff()); exec.shutdown(); //对shutdown()方法的调用可以防止新任务被提交给这个Executor,当前 //线程(在本例中,即驱动main()的线程)将继续运行在shutdown()被调用 //之前提交的所有任务 } }

FixedTHreadPool可以一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量。

public class FixedThreadPool { public static void main(String[] args) { //限制线程数量 ExecutorService exec = Executors.newFixedThreadPool(5); for(int i = 0 ;i<5; i++) exec.execute(new LiftOff()); exec.shutdown(); } }

*在任何线程池中,现有线程有可能的情况下,都会被自动复用。 *CachedThreadPool在程序执行过程中通常会创建与所需相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选。 *SingleThreadExecutor就像是线程数量为1的FixedThreadPool,如果向SinleThreadExecutor提交多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有任务将使用相同的线程。

(4)从任务中产生返回值 Runnable是执行工作的独立任务,但是它不返回任何值。如果希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。Callabel是一种具有类型参数的泛型,它的类型参数表示的是从方法call()(而不是run())中返回的值,并必须使用ExecutorService.submit()方法调用它。

public class TaskWithResult implements Callable<String>{ private int id ; public TaskWithResult(int id){ this.id = id ; } @Override public String call() throws Exception { // TODO Auto-generated method stub 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++){ //submit()方法会产生Future对象,它用Callable返回结果的特定类型进行 //了参数化,可以用isDone()方法来查询Future是否已经完成,当任务完成时 //它具有一个结果,可以调用get()方法来获取该结果 results.add(exec.submit(new TaskWithResult(i))); } for(Future<String> fs :results) try { System.out.println(fs.get()); }catch(InterruptedException e) { System.out.println(e); return ; }catch(ExecutionException e ){ System.out.println(e); }finally { exec.shutdown(); } } }

(5)休眠 Java SE5引入了更加显式的sleep()版本,作为TimeUnit类的一部分。

TimeUnit.MILLISECONDS.sleep(100) ;

(6)优先级 线程的优先级将线程的重要性传递给了调度器,调度器将倾向于优先权最高的线程先执行。优先级较低的线程仅仅是执行频率较低。 * 可以在一个任务的内部,通过调用Thread.currentThread()来获得对驱动该任务的Thread对象的引用。

(7)让步 如果知道已经完成了在run()方法的循环的一次迭代过程中所需的工作,就可以给线程调度机制一个暗示:你的工作已经做得差不多了,可以让别的线程使用CPU,这个暗示将通过调用yieid()方法来作出(不过这只是一个暗示,没有任何机制保证它将会采纳),对于任何重要的控制或在调整应用时,都不能依赖与yieid()。

(8)后台线程 后台线程是指在程序运行的时候在后台提供一种通用的线程,只要有任何非后台线程还在运行,程序就不会终止。比如,执行main()的就是一个非后台线程。 *一旦main()完成其工作,就没有什么能阻止程序终止了。

Thread daemon = new Thread(new SimpleDaemons()); daemon.setDaemon(true); //设置为后台线程 daemon.start();

(9)编码的变体 继承Thread

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 + "),"; } public void run(){ while(true){ System.out.print(this); if(--countDown == 0 ) return ; } } }

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

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 + "),"; } @Override public void run(){ while(true){ System.out.print(this); if(--countDown == 0 ) return ; } } public static void main(String[] args) { for(int i = 0 ;i<5 ; i++) new SelfManaged(); } }

另可以通过内部类把线程代码隐藏在类中

(10).术语 在Java中,Thread类自身不执行任何操作,它只是驱动赋予它的任务。

(11)加入一个线程 一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。join方法的调用可以被中断,做法是在调用线程上调用interrupt()方法,这时需要用到try-catch子句。

(12)创建有响应的用户界面

//没有响应的界面 class UnresponsiveUI { private volatile double d = 1 ; public UNresponsiveUI() throws Exception { while(d>0) d = d + (Math.PI + Math.E) /d ; System.in.read(); //Never gets here } } //单独线程运行 public class ResponsiveUI extends Thread { private static volatile double d = 1 ; public RespinsiveUI(){ setDaemon(true); start(); } public void run(){ while(true) { d = d + (Math.PI + Math.E)/d; } } public static void main(String[] args) throws Exception{ // new UnresponsiveUI(); new ResponsiveUI(); System.in.read(); System.out.println(d); //Shows progress } }

(13)捕获异常 下面的任务总是会抛出一个异常,该异常会传播到其run()方法的外部,并且main()展示

public class ExceptionThread implements Runnable { public void run() { throw new RuntimeException(); } //异常逃出任务的run()方法,向外部传播到控制台 public static void main(String[] args) { ExecutorService exec = Excutors.newCachedThreadPool(); exec.execute(new ExceptionThread()); } }

为了解决这个问题,我们需要修改Executor产生线程的方式。Thread.UncauhtExceptionHandler允许你在每个Thread对象上都附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。

//任务 class ExceptionThread2 implements Runnable { public void run() { Thread t = Thread.currentThread(); System.out.println("run() by" + t ); System.out.println("eh = " + t.getUNcaughtExceptionHandler()); throw new RuntimeException(); //抛出异常 } } //异常处理器 class MyUncayghtExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t ,Throwable e) { System.out.println("caught" + e); } } //TheadFactory将在每个新创建的Thread对象上附着一个Thread.UNcaughtExceptionHandler. class HandlerThreadFactory implements ThreadFactory { public Thread new Thread(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 exec = Executors.newCachedThreadPool(new HandlerThreadFactory()); exec.execute(new ExceptionThread2()); } }

在代码中处处使用相同的异常处理器,更简单的方式是在Thread类中设置一个静态域,并将这个处理器设置为默认的未捕获异常处理器

public class SettingDefaultHandler { public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new ExceptionThread()); } } //这个处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用。

**

3.共享受限资源

** (1)Java以提供关键字synchronized的形式,为防止资源冲突提供了内置支持。

//下面是声明synchronized方法 synchronized void f() { /*...*/ } synchronized void g() { /*...*/ }

(2)在使用并发时,将域设置为private是非常重要的,否则synchronized关键字就不能防止其他任务直接访问域,这样就会产生冲突。

(3)使用显示的Lock对象 Lock对象必须被显示地创建,锁定和释放,因此与内建的锁形式相比,代码缺乏优雅性。

public class MutexEvenGenerator extends IntGenerator { private int currentEvenValue = 0 ; private Lock lock = new ReentrantLock(); public int next() { lock.lock(); try { ++currentEvenValue; Thread.yield(); +currentEvenValue; return currentEvenValue ; }finally { lock.unlock(); } }

(3)原子性和易变行性

(5)临界区 有时只是希望防止多个线程同时访问方法内部的代码而不是防止访问整个方法。通过这种方式分离出来的代码段被称为临界区。

synchonized(syncObject) { //This code can be accessed //by only one task at a time } //同步控制块 //通过同步控制块,而不是对整个方法进行同步控制,可以使多个任务访问对象的时间性能得到显著提高。 //synchronized块必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this),这种方式中,如果获得了synchronized块上的锁,那么该对象的其他的synchronized方法和临界区就不能被调用了,因此如果this上同步,临界区的效果就会直接缩小在同步的范围内。

(6)下面示例演示了两个任务可以同时进入同一个对象,只要这个对象上的房是在不同的锁上同步即可

class DualSynch { private Object syncObject = new Object(); public synchronized void f() { for(int i = 0;i < 5 ;i++) { print("f()"); Thread.yield(); } } public void g() { synchronized(syncObject) { for(int i = 0 ;i<5; i++) { print("g()"); Thread.yield(); } } } } //调用 public class SyncObject { public static void main(String[] args) { final DualSynch ds = new DualSynck(); new Thread() { public void run() { ds.f(); } }.start(); ds.g(); } }

(7)线程本地存储 防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享,线程本地存储是一种自动化机制,可以使用相同变量的每个不同的线程都创建不同的存储。

**

5.线程之间的协作

** 1.可以将其自身挂起,直到某些外部条件发生变化,表示是时候让这个任务向前开动。这种握手可以通过Object的方法wait()和notifu()来安全地实现。

2.调用sleep()的时候锁并没有被释放,调用yield()也属于这种情况,另一方面,当一个任务在方法里遇到了对wait()的调用的时候,线程的执行被挂起,对象上的锁被释放。

3.只能在同步控制方法或同步控制块里调用wait(),notify()和notifyAll()(因为不用操作锁,所以sleep()可以在非同步控制方法里调用)。调用wait(),notify()和notifyAll()的任务在调用这些方法前必须”拥有”对象的锁。

例子:为Car涂蜡,抛光。抛光任务在涂蜡任务之前是不能执行其工作。WaxOn和WaxOff都使用了Car对象,该对象在这些任务等待条件变化的时候,使用wait()和notifyAll()来挂起和重新启动这些任务

class Car { private boolean waxOn = false ; public synchronized void waxed(){//涂蜡 waxOn = true; notifyAll(); } public synchronized void buffed() {//抛光 waxOn = false ; notigyAll(); } public synchronized void waitFoxWaxing() throws InterruptedException{ while(waxOn == false) wait(); } public synchronized void waitForBuffing() throws InterruptedException { while(waxOn == true) wait(); } } //涂蜡任务 class WaxOn implements Runable { private Car car ; public WaxOn(Car c){car = c ;} public void run(){ try { while(!Thread.interrupted()) {//只能报告调用它的线程的中断状态,如果当前线程被中断,它会返回true printnb("Wax On"); TimeUnit.MILLISECONDS.sleep(200); car.waxed(); car.waitForBuffing(); } }catch(InterruptedException e) { print("Exinting via interrupe"); } print("Ending Wax On task"); } } //抛光任务 class WaxOff implements Runnable { private Car car ; public WaxOff(Car c) { car = c; } public void run(){ try { while(!Thread.interrupted()){ car.waitForWaxing(); printnb("Wax OFF!"); TimeUnit.MILLISECONDS.sleep(200); car.buffed(); } }catch(InterruptedException e) { print("Exiting via interrupt"); } print("Ending Wax OFf task"); } } //调用 public class WaxOMatic { public static void main(String[] args) throws Exception { Car car = new Car(); ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new WaxOFf(car)); exec.execute(new WaxOn(car)); TimeUnit.SECONDS.sleep(5); exec.shutdownNow(); } }

4.可能会有多个任务在单个Car对象上处于wait()状态,因此调用notifuAll(0比只调用notufy()要更安全。

5.生产者-消费者与队列 wait()和notifyAll()方法以一种非常低级的方式解决了任务互操作问题,即每次交互时都握手。可以瞄向更高的抽象级别,使用同步队列来解决任务协作问题,同步队列在任何时刻都只允许一个任务插入或移除元素。

P746

转载请注明原文地址: https://www.6miu.com/read-28272.html

最新回复(0)