Java并发编程学习笔记

时间:2023-03-18 16:21:56

Java编程思想,并发编程学习笔记.

一.基本的线程机制
 1.定义任务:Runnable接口
 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现Runnable接口并编写run方法,使得该任务可以执行你的命令.
 
  class MyTask implements Runnable {
 
   private String mName;
  
   public MyTask(String name) {
    mName = name;
   }
   
   @Override
   public void run() {
    System.out.println(mName + " run in thread pid is " + Thread.currentThread().getId());
   }
  }

2.使用Thread
 将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器.
  Thread thread = new Thread(new MyTask("Thread"));
  thread.start();
  // Thread run in thread pid is 9
  
 3.使用Executor
 java.util.concurrent包中的执行器将为你管理Thread对象,从而简化并发编程.Executor允许管理异步任务的执行,而无须管理线程的生命周期.是启动任务的优选方法.
  ExecutorService cachedExecutor = Executors.newCachedThreadPool();
  for (int i = 0; i < 3; i++)
   cachedExecutor.execute(new MyTask("Cached executor " + i));
  cachedExecutor.shutdown();
  ExecutorService exec = Executors.newFixedThreadPool(2);
  for (int i = 0; i < 3; i++)
   exec.execute(new MyTask());
  ExecutorService singleExcutor = Executors.newSingleThreadExecutor();
  for (int i = 0; i < 3; i++)
   singleExcutor.execute(new MyTask("Single executor " + i));
  singleExcutor.shutdown();
  输出结果:
   Cached executor 0 run in thread pid is 10
   Cached executor 1 run in thread pid is 11
   Cached executor 2 run in thread pid is 12
   Fixed executor 0 run in thread pid is 13
   Fixed executor 1 run in thread pid is 14
   Fixed executor 2 run in thread pid is 13
   Single executor 0 run in thread pid is 15
   Single executor 1 run in thread pid is 15
   Single executor 2 run in thread pid is 15
  
  newCachedThreadPool:将为每一个任务分配一个线程.通常在程序执行过程中会创建与所需任务相同的线程,然后在它回收旧线程时停止创建新线程,因此它是Executor的首选.
  newFixedThreadPool:将一次性预先执行线程分配,预先分配需要高昂的代价,但可以限制线程的数量,不用为每个任务都付出创建线程的开销.
  newSingleThreadExecutor:如同数量为1的newFixedThreadPool.每一个任务都按照提交给它的顺序执行,因此它会序列化提交给他的任务,并维护自己的悬挂任务队列.
  shutdown方法将防止新任务被提交给这个Executor,当前任务将继续运行shutdown被调用之前提交的所有任务.程序将在Executor中所有的任务完成之后退出.
  
 4.从任务中产生返回值
 使用Callable接口代替Runable接口,可取得返回值.Callable是一种具有参数类型的泛型,它的参数类型表示的是从call方法中返回的值,并且需要使用ExecutorService的submit方法调用它.
  class TaskWithResult implements Callable<String> {

private String mName;
   
   public TaskWithResult(String name) {
    mName = name;
   }
   
   @Override
   public String call() throws Exception {
    return "Result -> " + mName + " " + Thread.currentThread().getId();
   }
  }
  
  ExecutorService exec = Executors.newCachedThreadPool();
  Future<String> future = exec.submit(new TaskWithResult("t"));
  System.out.println(future.get());
  exec.shutdown();
  
 5.休眠
 Thread类的sleep方法,将暂停给定的时间(ms).
 TimeUnit类允许延迟指定时间单位的时间.如
  TimeUnit.SECONDS.sleep(1);
  TimeUnit.MILLISECONDS.sleep(1);
 使用休眠方法,使得线程调度器从一个线程切换到另一个线程,进而驱动另一个任务.
 但是这种顺序依赖于底层的线程机制,因此顺序执行的任务不能依赖于此类方法的调用,应该手动编写自己的协作例程,这些例程按照指定的顺序在相互之间传递控制权.
 
 6.优先级
 线程的优先级该线程的重要性传递给了调度器,调度器优先让优先级最高的线程优先执行,但这并不是意味着优先级较低的线程得不到执行,即优先级不会导致死锁.优先级低的线程仅仅只是执行的频率较低.
 可以使用getPriority和setPriority方法来获取和修改优先级.如,Thread.currentThread().getPriority()
 
 7.让步
 调用yield方法让具有相同优先级的线程可以运行.这个操作只是建议上的.
 但是对于重要的控制或在调整应用时都不能依赖于yield方法.原理上同使用sleep方法一样.
 
 8.后台线程
 后台(Daemon)线程,是指程序运行的时候在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的一部分.
 当所有非后台线程结束时,程序也就终止了,此时会杀掉所有的后台线程.
 必须在线程启动之前(调用start方法前)调用setDaemon方法,才能将一个线程设置为后台线程.
 一个后台线程创建的任何线程都将被自动设置为后台线程.可调用isDaemon方法来确定线程是否是一个后台线程.
 
 9.编码的变体
 直接使用Thread类创建任务.
 Thread类实现了Runable接口,可直接继承自Thread类,重写run方法,实现任务.
  class SimpleThread extends Thread {
   @Override
   public void run() {
    super.run();
    System.out.println("haha pid:" + getId());
   }
  }
 另一种惯用法是自管理的Runable.
  class SelfManage implements Runnable {

private Thread thread = new Thread(this);
   public SelfManage() { thread.start(); }
   
   @Override
   public void run() {
    System.out.println("self manage thread pid is " + Thread.currentThread().getId());
   }
  }
 或者使用内部类或匿名内部类将线程隐藏在代码中.
  public void startThread() {
   Thread t = new Thread() {
    @Override
    public void run() {
     super.run();
     System.out.println("inner thread tid is " + Thread.currentThread().getId());
    }
   };
   t.start();
  }
 
 10.加入一个任务
 一个线程可以调用其它线程的join方法,其效果是该线程将等待被调用join方法的线程结束,才继续运行.
  class Sleeper extends Thread {
   @Override
   public void run() {
    System.out.println("Sleeper run begin");
    try {
     TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
    System.out.println("Sleeper run out");
   }
  }

class Joiner extends Thread {
   private final Thread joinerThread;
   public Joiner(Thread joiner) { joinerThread = joiner; }
   @Override
   public void run() {
    System.out.println("joiner run begin");
    try {
     joinerThread.join();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println("joiner run out");
   }
  }
  
  Thread t = new Sleeper();
  t.start();
  new Joiner(t).start();
 join方法有带参数的版本,参数表示超时的时间,如果被调用的线程超时未返回,将停止等待.
  public final synchronized void join(long millis) throws java.lang.InterruptedException;
  public final synchronized void join(long millis, int nanoseconds) throws java.lang.InterruptedException;
  public final void join() throws java.lang.InterruptedException;
  其中millis为毫秒,nanoseconds为纳秒.
  
 11.线程异常捕获
 Thread类的setUncaughtExceptionHandler方法,允许设置一个异常处理器,在发生未捕获的异常时,调用异常处理器的uncaughtException方法.
  方法声明 public void setUncaughtExceptionHandler(java.lang.Thread$UncaughtExceptionHandler);
  class ExceptionThread extends Thread {
   @Override
   public void run() {
    System.out.println("Thread " + getId() + " will throw excetion.");
    throw new RuntimeException("This is a exception!");
   }
  }
  ExceptionThread eThread = new ExceptionThread();
  eThread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
   @Override
   public void uncaughtException(Thread thread, Throwable arg1) {
    System.out.println("catch exception! thead id is " + thread.getId());
   }
  });
  eThread.start();
  
二.共享受限资源(同步)
 基本上并发模式在解决线程冲突问题时,都是采用序列化访问共享资源的方案.通常是通过在代码前面加上一条锁语句来实现,这就使得在一段时间内只有一个任务可以运行这段代码.因为锁语句产生了一种互斥效果,所以这种机制常常称作互斥量(Mutex).
 共享资源一般是以对象存在的内存片段,或者是文件,输入输出端口,打印机等.要控制对共享资源的访问,首先需要把它包装进一个对象.
 
 1.同步规则
 Brian的同规则:如果正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么此时必须使用同步,并且读写的代码都必须使用相同的监视器锁进行同步.
 
 2.synchronized
 java以synchronized提供内置支持以防止资源冲突.当任务要执行被synchronized保护的资源时,它将检查锁是否可用,然后获取锁,执行代码,释放锁.
 所有对象都含有单一的锁,当在对象上调用sychronized方法时,此对象都会被加锁,该对象上的其它synchronized方法只有等到前一个方法调用完毕并释放了锁以后才能被调用.
 一个任务可以多次获得对象的锁.最开始对象的计数为0;每当这个任务在这个对象上获得锁时,计数增加1;离开synchronized方法时减1.计数为0时,锁被释放,其它任务就可以使用此资源.
 针对每个类也有一个锁,作为类的Class对象的一部分,所有synchronized static方法也可以在类的范围内防止对static数据的并发访问.
 
 3.使用显示的Lock对象
  private java.util.concurrent.locks.Lock lock = new java.util.concurrent.locks.ReentrantLock();
  public void lockFunction() {
   lock.lock();
   try {
    System.out.println("locked");
   } finally {
    lock.unlock();
   }
  }
 如果使用synchronized同步一个方法,那么在方法在某些情况下失败了,可能抛出了一个异常,此时将没有任何机会去做清理工作,以恢复状态.如果使用Lock对象,就可以在finally中将系统的状态正确恢复了.
 如果要实现尝试获取锁,或者尝试在一段时间内获取锁,则必须使用Lock对象,synchronized不能实现此类功能.
  public void test() {
   untimed();
   timed();
   new Thread() {
    @Override
    public void run() {
     lock.lock();
     System.out.println("lock but not unlock it.");
    }
   }.start();
   Thread.yield();
   untimed();
   timed();
  }
  
  private java.util.concurrent.locks.Lock lock = new java.util.concurrent.locks.ReentrantLock();
  public void untimed() {
   boolean captured = lock.tryLock();
   try {
    System.out.println("tryLock(): " + captured);
   } finally {
    if (captured)
     lock.unlock();
   }
  }
  
  public void timed() {
   boolean captured = false;
   try {
    captured = lock.tryLock(1, TimeUnit.SECONDS);
    System.out.println("tryLock(): " + captured);
   } catch (InterruptedException e) {
    e.printStackTrace();
   } finally {
    if (captured)
     lock.unlock();
   }
  }
 
 4.原子性和易变性
 原子操作是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生的上下文切换(切换到其它线程)之前执行完毕.
 原子性保证,对于除long和double以外的所有基本类型变量,读取和写入这样的操作,它们会被当作不可分(原子)的操作来操作内存.Java SE5开始,当你定义long或double时,使用关键字volatile,就会获得原子性(简单的赋值和返回操作).
 volatile确保了应用中的可视性.一个域如果被声明为volatile,那么只要产生了写操作,那么所有的读操作都可以看到这个修改.
 volatile使用规则:如一个域可能被多个任务同时访问,就应当是volatile的;如果一个域由同步块来保护,那么就不需要声明为volatile.如果一个域只需要在某个任务内可见,那么就不需要声明为volatile.
 volatile无法工作的情况:一个域的值依赖于它之前的值(如递增计数器);某个域的值受到其它域的值限制(如Range类的).
 使用volatile而不是synchronized的唯一安全选择是,类中只有一个可变的域.因此应尽量使用synchronized进行保护.
 原子操作:对域中的值作赋值或返回操作.java中自增运算或+=都不是原子操作.
 
 5.临界区
 使用多个线程同时访问方法内的一部分代码而不是整个方法,通过这种方法分离出来的代码段就是临界区.它也是使用synchronized建立.也可以显示的使用Lock对象建立.
  synchronized(syncObject) {
   // ...
  }
  
三.终止任务
 1.线程的状态
 新建(new):当一个线程被创建时短暂的处于这个状态,此时已经为它分配了必须的系统资源并进行了初始化,有资格获得时间片,此后调度器将把它转为可运行状态或阻塞状态.
 就绪(Runable):只要调度器分配时间片就可以运行的状态,即此时可运行或不运行.
 阻塞(Blocked):能够运行但有某个条件阻止它运行的状态.调度器不会给他分配时间片直到进入了就绪状态.
 死亡(Dead):死亡或终止状态不再可调度,不可能再获得时间片.任务已结束,或不再可运行,通常是由于run返回,或任务的线程被中断.
 
 2.进入阻塞状态
 进入阻塞状态通常有以下原因
 调用sleep方法,这种情况下任务在指定的时间内不能运行.
 调用wait方法,还没有收到notify或notifyAll的通知(或者SE5中的signal或signalAll)而进入就绪状态.
 进入同步代码,但未获得锁.
 等待输入输出完成.
 注意:使用suspend和resume也会阻塞和唤醒线程,但会导致死锁,因此被废弃了.stop方法也被废弃了,因为他不能释放锁.
 
 3.中断
 (1)对Thread对象,调用interrupt方法,可以终止被阻塞的任务,并设置中断状态.如果一个线程已经被阻塞,或试图执行一个阻塞操作,那么设置这个中断状态将抛出InterruptException异常.当抛出该异常或调用Thread.interrupted方法时,将清除该状态.
 (2)对Executor对象,调用shotdownNow方法,那么将给Executor启动的所有线程调用interrupt方法.如果使用的是submit方法启动的线程,使用submit返回的Future对象来调用cancel方法,传入true,可以中断该任务.cancel是中断Executor启动的单个线程的方式.
 
四.线程之间的协作
 线程之间的协作,指多个任务一起工作去解决某个问题.这样,问题不是相互干涉,而是彼此之间的协调,因为这类问题中,某些部分必须在其它部分解决之后解决.
 1.wait()和notifyAll()方法
 wait提供了任务之间对活动同步的方式.wait等待某个条件发生变化,这种条件由别的任务来提供.wait只有当收到notify或notifyAll时,才被唤醒.
 调用wait时,线程的执行被挂起,对象上的锁被释放.
  public final void wait() throws InterruptedException;
  public final void wait(long timeout) throws InterruptedException;
 在wait期间,对象上的锁被释放; notify或notifyAll时,从wait中恢复执行.执行带参数版本的wait时,超时后从wait中恢复执行.
 注意:wait(),notify(),notifyAll()方法只能在同步代码块中调用.否则,运行时将抛出IllegalMonitorStateException异常,这表示,调用这些方法必须拥有对象的锁.
 
 2.await()和signalAll()方法
 通过Lock对象的newCondition()方法,可创建一个Condition对象,Condition类使用互斥并允许任务挂起.
 Condition对象的awati和signalAll方法,可用来挂起和唤醒这个Condition对象上的任务.与notifyAll相比,signalAll是更安全的方式.
 
 一个线程协作的例子,演示一直开关灯的事件:
   interface Light {
    void open();
    void close();
    void waitForOpen() throws InterruptedException;
    void waitForClose() throws InterruptedException;
   }

class SyncLight implements Light {
    private boolean mIsOpen;

public synchronized void open() {
     mIsOpen = true;
     notifyAll();
    }

public synchronized void close() {
     mIsOpen = false;
     notifyAll();
    }

public synchronized void waitForOpen() throws InterruptedException {
     while (mIsOpen == false)
      wait();
    }

public synchronized void waitForClose() throws InterruptedException {
     while (mIsOpen == true)
      wait();
    }
   }

class LockLight implements Light {
    private boolean mIsOpen;
    private Lock mLock = new ReentrantLock();
    private Condition mCondition = mLock.newCondition();

public void open() {
     mLock.lock();
     try {
      mIsOpen = true;
      mCondition.signalAll();
     } finally {
      mLock.unlock();
     }
    }

public void close() {
     mLock.lock();
     try {
      mIsOpen = false;
      mCondition.signalAll();
     } finally {
      mLock.unlock();
     }
    }

public void waitForOpen() throws InterruptedException {
     mLock.lock();
     try {
      while (mIsOpen == false)
       mCondition.await();
     } finally {
      mLock.unlock();
     }
    }

public void waitForClose() throws InterruptedException {
     mLock.lock();
     try {
      while (mIsOpen == true)
       mCondition.await();
     } finally {
      mLock.unlock();
     }
    }
   }

class OpenLight implements Runnable {
    Light mLight;

public OpenLight(Light light) {
     mLight = light;
    }

@Override
    public void run() {
     try {
      while (!Thread.interrupted()) {
       TimeUnit.MILLISECONDS.sleep(10);
       System.out.print("Open light! ");
       mLight.open();
       mLight.waitForClose();
      }
     } catch (InterruptedException e) {
      System.out.println("exit...open");
     }
    }
   }

class CloseLight implements Runnable {
    Light mLight;

public CloseLight(Light light) {
     mLight = light;
    }

@Override
    public void run() {
     try {
      while (!Thread.interrupted()) {
       mLight.waitForOpen();
       TimeUnit.MILLISECONDS.sleep(10);
       System.out.print("Close light! ");
       mLight.close();
      }
     } catch (InterruptedException e) {
      System.out.println("exit...close");
     }
    }
   }

public class ThreadCooperation {

public static void main(String[] args) throws InterruptedException {
     // Light light = new LockLight();
     Light light = new SyncLight();
     ExecutorService service = Executors.newCachedThreadPool();
     service.execute(new CloseLight(light));
     service.execute(new OpenLight(light));
     TimeUnit.SECONDS.sleep(1);
     service.shutdownNow();
    }
   }

3.解决带循环的语句,不同步的问题:将循环写到同步块中.
  synchronized(object) {
   while(condition) {
    // do something;
   }
  }
 
 
 
五.其它类及其方法
    Thread类
        Thread类自身不执行任何操作,它只是驱动赋予它的任务.创建线程可能是一个高昂的代价,因此必须保存并管理它们.
       
        1.start方法: 用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源.
            public synchronized void start();
           
        2.yield方法: 切换线程函数,调用此方法建议线程调度器可以将CPU从一个线程转移给另一个线程.
            public static native void yield();
       
        3.interrupt方法: 中断一个线程.调用此方法时,将给线程设置一个标志,表明该线程已被中断.然而异常捕获时将清理这个标志.因此在异常被捕获后,再调用isInterrupted将返回false,而异常代码处理中则为true.
          isInterrupted方法: 判断一个线程是否被中断.
          interrupted方法: 检查中断状态,并清除中断状态.
            public void interrupt();
            public boolean isInterrupted();
        注:interrupt方法,可以终止被阻塞的任务,并设置中断状态.如果一个线程已经被阻塞,或试图执行一个阻塞操作,那么设置这个中断状态将抛出InterruptException异常.
           
        4.isAlive方法:判断线程是否已运行.调用start方法后,返回true; run结束后返回false.如果被强制中断,仍返回true
            public final native boolean isAlive();
           
    ExecutorService类
        awaitTermination方法:等待每一个任务结束,如果所有任务在超时时间内结束,则返回true,否则返回false,表示不是所有任务都结束了.
            public abstract boolean awaitTermination(long, java.util.concurrent.TimeUnit) throws java.lang.InterruptedException;
        shutdownNow方法,那么将给Executor启动的所有线程调用interrupt方法
            public abstract java.util.List<java.lang.Runnable> shutdownNow();