《Think In Java》读书笔记(二)多线程

一、多线程基本概念
 独占:通过阻止多个并发行为间的有害干扰来维护状态的一致性。通常使用异步方法sychronized
 状态依赖:触发,阻止,延迟,恢复,某些行为是由一些对象是否处在这些行为可能成功或者已经
 成功的状态决定的。主要通过监视器(monitor)实现 object.wait object.notify, object.notifyAll

 客户Client: Client Object 期待某个动作的执行
 服务Serverce :包括执行这个动作的代码
 主体:客户和服务都可以是一个对象,这个对象就是一个主体。而又他调用的对象通常就是辅助对象(help)

 Synchronized: 防止并法导致数据不一致,可以修饰一个方法或者代码块。
 1.Object 或子类的每一个实例都会在进入这个synchronized方法前加锁,并在离开这个方法时自动释放锁,
 使用synchronized关键字来定义方法就会锁定类中所有使用synchronzied关键字定义的静态方法或非静态方法
 不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
 对于修饰静态的方法它可以对类的所有对象实例起作用。
 2.对代码块的加锁也是,只是需要指明对那个对象加锁。在静态代码块中使用类.class来指明,
 在非静态代码块中多数是用this
 3.synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法
 规则:
 1)永远只是在更新对象的成员变量是加锁
 2)永远只是在访问有可能被更新对象的成员变量时加锁
 3)永远不要在调用其它对象的方法时加锁


二、 java.util.concurent 包介绍
 1、执行器(Executor) 用来管理Thread 对象,下面介绍三种Executor
  (1) newCachedThreadPool  
  ExecutorService exec = Executors.newCachedThreadPool();//创建一个可根据需要创建新线程的线程池
  for(int  = 0 ; i<5; i++){
   exec.execute(new LiftOff());//LiftOff implements Runnable接口的类
   exec.shutdown()
  }
  (2) newFixedThreadPool
  ExecutorService exec = Executors.newFixedThreadPool(5);//创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
  for(int  = 0 ; i<5; i++){
   exec.execute(new LiftOff());//LiftOff implements Runnable接口的类
   exec.shutdown()
  }
  (3) newSingleThreadPool
  SingleThreadPool 就好象是线程数量为1的FixedThreadPool,如果向SingleThreadPool提交多个任务,那么这些任务都将排列,每个任务都会在下一个任务开始之前运行结束。
  ExecutorService exec = Executors.newSingleThreadPool();//创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
  for(int  = 0 ; i<5; i++){
   exec.execute(new LiftOff());//LiftOff implements Runnable接口的类
   exec.shutdown()
  }

 2、从任务中产生返回值
  Runnable是执行工作的独立任务,但是不返回任何值,如果实现Callable 接口,则Callable接口方法call()可以返回一个值。
  package cn.com.cdl.chen;
  /**
   * @ Class CallableDemo.java
   * @ Description 
   * @ Company OpenData
   * @ Author Chenlly E-mail: Chenlly99@Gmail.com
   * @ Version 1.0
   * @ Date Mar 13, 2010
   */
  import java.util.ArrayList;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.Future;
  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)));//这里使用submit 方法会产生Future 对象
    }
   
    for(Future<String> fs:results){
     try{
      if(fs.isDone()){//检查Future是否已经完成,或者不用isDone()来检查,那么get()将会阻塞,直到结果准备就绪。
       System.out.println(fs.get());//通过get()得到call()方法返回的值,
      }
     } catch(Exception ex){
      ex.printStackTrace();
     } finally{
      exec.shutdown();
     }
    }
   }
  }
  class TaskWithResult implements Callable<String>{
   private int id;
  
   public TaskWithResult(int id){
    this.id = id;
   }
  
   public String call(){
    return "result of TaskWithResult" + id;
   }
  
  
  }
  //结果:
  result of TaskWithResult0
  result of TaskWithResult1
  result of TaskWithResult2
  result of TaskWithResult3
  result of TaskWithResult4
  result of TaskWithResult5
  result of TaskWithResult6
  result of TaskWithResult7
  result of TaskWithResult8
  result of TaskWithResult9
  sleep() 和wait()方法的区别
  sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行,其控制范围是由当前线程块
  wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者
  sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定.好比如说,我要做的事情是 "点火->烧水->煮面",而当我点完火之后我不立即烧水,我要休息一段时间再烧.对于运行的主动权是由我的流程来控制.
  而wait(),首先,这是由某个确定的对象来调用的,将这个对象理解成一个传话的人,当这个人在某个线程里面说"暂停!",也是 thisOBJ.wait(),这里的暂停是阻塞,还是"点火->烧水->煮饭",thisOBJ就好比一个监督我的人站在我旁边,本来该线 程应该执行1后执行2,再执行3,
  而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但正个流程并没有结束,我一直想去煮饭,但还没被允许, 直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisOBJ.notify()的时候,那么我就可以煮饭了,这个被暂停的线程就会从暂停处 继续执行.
  本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题.
 
 3、t.join();方法
  package cn.com.cdl.chen;
  /**
   * @ Class Joining.java
   * @ Description 
   * @ Company OpenData
   * @ Author Chenlly E-mail: Chenlly99@Gmail.com
   * @ Version 1.0
   * @ Date Mar 14, 2010
   */
  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(InterruptedException ex){
     System.out.println(this.getName()+" was interrupted."+"isInterrupted():"+this.isInterrupted());
    }
   
    System.out.println(this.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();//线程Sleeper在线程Joiner上执行join(),则Joiner 线程将被挂起,直到sleeper 线程结束(isAlive()==false)。才恢复
    } catch(InterruptedException ex){
     System.out.print("Interrupted");
    }
   
    System.out.println(this.getName()+" join completed");
   }
  }

  public class Joining {
   public static void main(String []args){
    Sleeper sleepy = new Sleeper("Sleepy",1500);
    Sleeper grumpy = new Sleeper("Grumpy",1500);
   
    Joiner dopey = new Joiner("Dopey",sleepy);
    Joiner doc = new Joiner("Doc",grumpy);
   
    grumpy.interrupt();//grumpy 中断,doc 将继续
   }
  }

  out put:
  Grumpy was interrupted.isInterrupted():false//当线程在该线程上调用interrupt()时,将给该线程设定一个标志,然而,异常在捕获时会清理这个标志,所以在catch 代码块里调用interrupt()总是false
  Grumpy has awakened
  Doc join completed
  Sleepy has awakened
  Dopey join completed

 4、显示加锁,java.util.concurrent.locks
  public synchronized int next(){
   ...
   try{
    return
   }catch(){
  
   }
  }
  private Lock lock = new ReentrantLock();
  public  int next(){
   try{
    lock.lock();
    ...
    return
   } catch(){
    lock.unlock();
   }
  
  使用synchronized关键字和显示Lock都可以解决资源同步的问题,并且synchronized 关键字比显示加锁代码量少更少,但是当synchronized关键字不
  能尝试获取锁一段时间,然后放弃它。

 5、线程本地化:java.lang.ThreadLocal
  它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量
  通常通过get(),set()方法来访问该对象的内容,get()方法返回与其线程相关联的对象副本,而set()方法将参数值插入到为其线程存储的对象里。
  package cn.com.cdl.chen;
  import java.util.Random;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.TimeUnit;
  class Accessor implements Runnable{
   private final int id;
   public Accessor(int idn){id = idn;}
   public void run(){
    while(!Thread.currentThread().isInterrupted()){
     ThreadLocalVariableHolder.increment();
     System.out.println(this);
     Thread.yield();
    }
   }
   public String toString(){
    return "#"+id+":"+ThreadLocalVariableHolder.get();
   }
  
  }
  public class ThreadLocalVariableHolder{
   private static ThreadLocal<Integer> value=
    new ThreadLocal<Integer>(){
    private Random rand = new Random(47);
    protected synchronized Integer initialValue(){
     return rand.nextInt(10000);
    }
   };
  
   public static void increment(){
    value.set(value.get() + 1);
   }
  
   public static int get(){return value.get();}
  
   public static void main(String []args) throws Exception{
    ExecutorService exec = Executors.newCachedThreadPool();
    for(int i = 0; i < 5; i++){
     exec.execute(new Accessor(i));
    }
    TimeUnit.SECONDS.sleep(3);
    exec.shutdownNow();
   }
  }
  //output:
  #2:35601
  #4:30424
  #0:37948
  #2:35602
  #4:30425
  #0:37949
  #2:35603
  #4:30426
  #0:37950
 
 6、线程中捕获异常
  任务的run()方法通常总会有某种形式的循环,使得任务一直运行下去直到不再需要,所以要设定跳出循环的条件。
  通常,它被写成无限循环的形式这就意味着,除非有个条件使用它终止,否则它将永远运行下去。
  由于线程的本质特性,所以我们不能捕获到从线程中逃逸的异常,比如:

  package cn.com.chenlly;

  public class ThreadException implements Runnable {

   public void run() {
    throw new RuntimeException("I'm throw Exception");
   }

   public static void main(String []args){
    try{
     Thread t = new Thread(new ThreadException());
     t.start();
     
    } catch(Exception ex){
     System.out.println("Exception has been handled!");
     ex.printStackTrace();
    }
   }
   
  }

  /* output
  Exception in thread "Thread-0" java.lang.RuntimeException: I'm throw Exception
   at cn.com.chenlly.ThreadException.run(ThreadException.java:6)
   at java.lang.Thread.run(Unknown Source)
  也就是说tyr catch 无法捕获到从run 方法里逃逸的异常。
  Thread.UncaughtExceptionHandler这个接口就是当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。
  它允许你在每个Thread对象上都附着一个异常处理器。
  package cn.com.chenlly;

  public class ThreadException implements Runnable {

   public void run() {
    throw new RuntimeException("I'm throw Exception ");
   }

   public static void main(String[] args) {
    Thread t = new Thread(new ThreadException());
    t.setUncaughtExceptionHandler(new MyUncaughtException());//添加一个异常处理器
    t.start();
   }

  }

  //异常处理器
  class MyUncaughtException implements Thread.UncaughtExceptionHandler {
   public void uncaughtException(Thread t, Throwable e) {
    System.out.println(t + " : " + e.getMessage());
   }
  }


  /* output
  Thread[Thread-0,5,main] : I'm throw Exception


 7、线程之间的协助:
  多个任务,可以通过使用锁或者synchronized(互斥)的方法来同步两个线程的行为,从而使的一个任务不会干涉另外一个任务的资源。
  那么如何使得多个任务可以在一起彼此协作,去解决某个问题。使用相同的互斥基础:互斥。互斥能够确保只有一个任务可以响应某个信号。

  eg:一个是将蜡涂到Car上,一个是抛光它。抛光任务在涂蜡完成之前是不能操作的,而涂蜡任务在涂另外一层蜡之前,必须等待抛光结束。
  car 对象通过wait,和notifyAll来挂起和重新启动这些任务。
  package cn.com.chenlly;

  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Executors;
  import java.util.concurrent.TimeUnit;

  class Car{
   private boolean waxOn = true;//默认为抛光状态,同步信号
   
   //打蜡完成,设置状态,并且唤醒打蜡
   public synchronized void waxed(){
    waxOn = false;
    notifyAll();
   }
   
   //抛光完成,设置状态,并且唤醒打蜡
   public synchronized void buffing(){
    waxOn = true;
    notifyAll();
   }
   
   //是否抛光结束
   public synchronized void waitForWaxed() throws InterruptedException{
    while(!waxOn){
     wait();
    }
   }
   
   //是否打蜡结束
   public synchronized void waitForBuffing() throws InterruptedException{
    while(waxOn){
     wait();
    }
   }
   
  }

  //打蜡
  class WaxOn  implements Runnable{
   private Car car;
   public WaxOn(Car car){
    this.car = car;
   }
   public void run() {
    try{
     while(!Thread.interrupted()){
      car.waitForWaxed();
      System.out.println("Wax On!");
      TimeUnit.SECONDS.sleep(2);
      car.waxed();
     }
    } catch (Exception ex){
     ex.printStackTrace();
    }
   }
  }
  //抛光
  class Buffing  implements Runnable{
   private Car car;
   public Buffing(Car car){
    this.car = car;
   }
   public void run() {
    try{
     while(!Thread.interrupted()){
      car.waitForBuffing();
      System.out.println("Buffing On!");
      TimeUnit.SECONDS.sleep(2);
      car.buffing();
     }
    } catch (Exception ex){
     ex.printStackTrace();
    }
   }
  }
  public class WaxOMatic {
   public static void main(String []args) {
    try{
    Car car = new Car();
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.execute(new WaxOn(car));
    exec.execute(new Buffing(car));
    TimeUnit.SECONDS.sleep(10);
    exec.shutdownNow();
    } catch (InterruptedException ex){
     ex.printStackTrace();
    }
   }
  }

  /* output
  Wax On!
  Buffing On!
  Wax On!
  Buffing On!
  Wax On!
  Buffing On!

  打蜡和抛光相继进行,控制权在这两者之间相互传递时,这两个步骤过程在不断地重复。5秒后中断这两个任务。


 8、线程死锁问题

  1.死锁:某个任务在等待另外一个任务,而后者又在等待别的任务,这样一直下去,任务之间连续的循环就是死锁问题
  典型的哲学家就餐就是死锁问题
  
  2.死锁的四个必要条件:
   (1) 互斥条件:资源不能被共享,只能由一个进程使用。
   (2) 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
   (3) 非剥夺条件:经分配的资源不能从相应的进程中被强制地剥夺
   (4) 循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
  
  3.处理死锁的策略
  通过破除死锁四个必要条件之一,来防止死锁产生。

  哲学家就餐问题:该问题涉及到五个哲学家,他们交替地进行思考和进餐。
  他们分别坐在位于一个圆形餐桌周围的五把椅子上,圆桌中央是一碗米饭,餐桌上共有五根筷子,
  分别摆放在每两个相邻座位的中间。
  当哲学家思考时,他不与其他人交谈。当哲学家饥饿时,他将拿起和他相邻的两根筷子进行进餐,
  但他很可能仅拿到一根,此时旁边的另一根正在他邻居的手中。
  只有他同时拿到两根筷子时他才能开始进餐。完成进餐后,
  他将两根筷子分别放回原位,然后再次开始思考。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值