首页 > 代码库 > 笔记:多线程

笔记:多线程

多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务,通常每个任务称为一个线程(thread),他是线程控制的简称,可以同时运行一个以上线程的程序称为多线程程序(multithreaded);多线程和多进程有哪些区别呢,本质的区别在于每个进程拥有自己的一整套变量,而线程则是共享数据,Java中启动一个线程的代码如下:

// 线程任务的具体实现接口

????public interface Runnable {

public abstract void run();

????}

// 启用新线程执行任务

????Runnable runnable = new MyRunnable();

????Thread newThread = new Thread(runnable);

??

????newThread.start();

  • 中断线程

    当线程的run方法执行方法体中最后一条语句后,并经由执行return语句返回时,或者出现了在方法中没有捕获的异常时,线程将终止;还有一种可以强制线程终止的方法,interrupt 方法可以用来请求终止线程,示例如下:

    while(Thread.currentThread().isInterrupted == false && more work to do){

    ????do more work

    }

    如果线程被阻塞,就无法检测中断状态,如果在一个阻塞的线程(sleep或wait)上调用interrupt方法时,阻塞调用将会抛出InterruptedException异常,因此标准的线程run方法应具有如下形式:

    public void run(){

    ????try{

    ????????...

    ????????while(Thread.currentThread().isInterrupted() == false && more work to do){

    ????????????// do more work

    ????????}

    ?????????

    ????????// 返回结果

    ????}

    ????catch(InterruptedException e){

    ????????// 线程被阻塞,需要中断线程返回

    ????}

    ????finally{

    ????????// 释放资源

    ????}

    }

    如果在每次工作迭代之后都调用sleep方法(或者其他可中断方法),isInterrupted检测将没有任何用处,如果在中断状态被置为时调用sleep方法,他不会休眠,相反他将清除这个状态,并抛出 InterruptedExcpetion。

  • 线程状态
  • 新创建(New)

    当使用New操作符创建一个新线程时,该线程还没有开始运行,这时他的状态是新创建(New)。

  • 可运行(Runnable)

    一旦调用start方法,线程将处于可运行(Runnable)状态,一个可运行的线程可能正在运行也可能没有运行,这取决与操作系统给线程提供运行的时间。

  • 被阻塞(Blocked)、等待(Waiting)和计时等待(Timed Waiting)

    当线程处于被阻塞或等待状态时,线程暂时不活动,不运行任何代码且消耗最小的资源,知道线程调度器重新激活他。

    • 被阻赛(Blocked):当一个线程试图获取一个内部的对象锁(不是 java.util.concurrent库的锁),而该锁被其他线程持有,则该线程进入阻塞状态,当所有线程释放该锁,并且线程调度器允许本线程持有该锁时,该线程将变成非阻塞状态。
    • 等待(Waiting):当线程等待另一个线程通知调度器一个条件时,该线程将进入等待状态,例如,在调用 Object.wait 方法或者Thread.join方法,或者等待 java.util.concurrent库的Lock或Condition时,就会出现这个情况。
    • 计时等待(Timed Waiting):有几个方法具有超时参数,调用他们导致线程进入计时等待(Timed Waiting),这一状态将一直保持到超时期满或者接收到适当通知,带有超时参数的方法有 Thread.sleep、Object.wait、Thread.join、Lock.tryLock以及 Condition.await方法。
  • 被终止(Terminated)

    当线程run方法正常退出而自然死亡;因为一个没有捕获的异常终止了run方法而意外死亡,此时线程状态为被终止。

  • 线程优先级

    在Java程序中,每个线程有一个优先级,默认情况下,一个线程继承他的父线程优先级,可以用 setPriority方法提高或降低任何一个线程优先级,可以设置为MIN_PRIORITY(Thread类中定义为1)与MAX_PRIORITY(Thread类中定义为10)之间的任何值,NORM_PRIORITY(默认优先级)被定义为5。每当线程调度器有机会选择新线程时,他首先选择具有高优先级的线程,但是线程优先级时依赖与系统的,Java的线程优先级被映射到宿主主机平台的优先级上,优先级个人数也许更多,也许更少。

  • 守护线程

    守护线程的唯一用途时为其他线程提供服务,如果只剩下守护线程,虚拟机都退出了,因此,守护线程应该永远不去访问固有资源,如文件、数据库,因为他任何时候甚至在一个操作的中间发生中断,可以通过调用Thread实例的 setDaemon(true) 来将线程转换为守护线程,必须在线程启动之前调用。

  • 未捕获异常处理器

    线程的run方法不能抛出任何被检测的异常,但是,不被检测的异常会导致线程终止,在这种情况下,线程就死亡了,不需要任何catch子句来处理可以被传播的异常,就在线程死亡之前,异常被传播到一个用于未捕获异常的处理器,该处理器必须实现 Thread.UncaughtExceptionHandler接口的类,该接口只有一个方法定义:

    ??public interface UncaughtExceptionHandler {

    ????????void uncaughtException(Thread t, Throwable e);

    ??}

    可以使用 setUncaughtExceptionHandler方法未任何线程安装一个处理器,也可以使用Thread类的静态方法 setDefaultUncaughtExceptionHandler 为所有线程安装一个默认处理器。

  • 同步锁对象

    有两种机制防止代码块受并发访问的干扰,Java语言提供一个synchronized 关键字达到这目的,并且在 Java SE5.0 引入了 ReentrantLock类(重入锁),synchronized关键字自动提供一个锁以及相关条件,使用 ReentrantLock类的代码示例如下:

    package org.drsoft.mybatisExamples.example_thread;

    ??

    import java.util.concurrent.locks.Lock;

    import java.util.concurrent.locks.ReentrantLock;

    ??

    public class Bank {

    ????private Lock bankLock = new ReentrantLock();

    ??

    ????public void transfer(int form, int to, int amount) throws InterruptedException {

    ????????bankLock.lock();

    ????????try {

    ????????????System.out.println("ThreadID=" + Thread.currentThread().getId() + "\tForm=" + form + "\tTo=" + to+ "\tAmount=" + amount);

    ????????????Thread.sleep((int) (10 * Math.random()));

    ????????} finally {

    ????????????bankLock.unlock();

    ????????}

    ????}

    }

  • 条件锁对象

    通常,线程进入临界区,却发现在某一条件满足之后才能执行,要使用一个条件对象来管理哪些已经获得了一个锁,当是却不能做有用工作的线程,一个锁对象可以有一个或者多个相关的条件对象,可以使用 newCondition方法获得一个条件,可以调用条件的 await方法阻塞线程,直到另一个线程的调用同一个条件上的 signalAll方法重新激活因为这个条件等待的所有线程,示例代码如下:

    package org.drsoft.mybatisExamples.example_thread;

    ??

    import java.util.concurrent.locks.Condition;

    import java.util.concurrent.locks.Lock;

    import java.util.concurrent.locks.ReentrantLock;

    ??

    public class Bank {

    ????private Lock bankLock = new ReentrantLock();

    ????private Condition toCondition;

    ??

    ????public Bank() {

    ????????toCondition = bankLock.newCondition();

    ????}

    ??

    ????public void transfer(int form, int to, int amount) throws InterruptedException {

    ????????bankLock.lock();

    ????????try {

    ????????????if (to == 2) {

    ????????????????System.out.println("ThreadID=" + Thread.currentThread().getId() + "\tawait");

    ????????????????toCondition.await();

    ????????????}

    ????????????System.out.println("ThreadID=" + Thread.currentThread().getId() + "\tForm=" + form + "\tTo=" + to+ "\tAmount=" + amount);

    ????????????toCondition.signalAll();

    ????????????Thread.sleep((int) (10 * Math.random()));

    ????????} finally {

    ????????????bankLock.unlock();

    ????????}

    ????}

    }

  • synchronized关键字

    使用 synchronized 关键字声明在方法中,将保护整个方法,也就是说,要调用该方法,线程必须获得内部得对象锁,代码示例如下:

    public synchronized void method(){

    ????//method body

    }

    等价与如下代码:

    public void method(){

    ????this.intrinsicLock.lock();

    ????try{

    ???????????// method body

    ????}

    ????finally{

    ?????????this.intrinsicLock.unlock();

    ????}

    ?}

    内部锁只有一个相关条件,wait方法添加一个线程到等待集,notifyAll或notify方法解除等待线程得阻塞状态,可以使用 synchronized关键字实现前面得代码:

    package org.drsoft.mybatisExamples.example_thread;

    ??

    public class Bank {

    ??

    ????public synchronized void transfer(int form, int to, int amount) throws InterruptedException {

    ????????if (to == 2) {

    ????????????System.out.println("ThreadID=" + Thread.currentThread().getId() + "\tawait");

    ????????????wait();

    ????????}

    ????????System.out.println(

    ????????????????"ThreadID=" + Thread.currentThread().getId() + "\tForm=" + form + "\tTo=" + to +

    "\tAmount=" + amount);

    ????????notifyAll();

    ????????Thread.sleep((int) (10 * Math.random()));

    ????}

    }

    将静态方法声明为 synchronized也是合法的,如果调用这种方法,该方法获得相关的类对象的内部锁,因此,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。

  • 同步阻塞

    每一个Java对象有一个锁,线程可以通过调用同步方法获得锁,还有另一种机制可以获得锁,通过进入一个同步阻塞,当线程进入如下形式的阻塞:

    synchronized(obj){

    ????//critical section

    }

    有时候程序员使用一个对象的锁来实现额外的原子操作,实际上称为客户端锁定。

  • volatile关键字

    如果向一个变量写入值,而这个变量接下来可能会被另一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另一个线程写入的,此时必须使用 volatile关键字标识,volatile关键字为实例域的同步访问提供了一种免锁机制,如果声明一个域为 volatile,那么编译器和虚拟机就知道这该域是可能被另一个线程并发更新的,注意:volatile关键字不能提供原子性操作。

  • final变量

    还有一种方法可以安全的访问一个共享域,即这个域声明为 final时,例如如下代码:

    final Map<String,Double> accountMap = new HashMap<String,Double>();

    如果不声明为 final,就不能保证其他线程看到的是实例化的值,有可能看到的只是 null,而不是新构造的HashMap。

  • 原子性

    java.util.concurrent.atomic包中有很多类使用了高效的机器指令来保证其他操作的原子性,例如,AtomicInteger类,提供了方法 incrementAndGet decrementAndGet方法,他们分别以原子方式将一个整数自增或自减,另外这个包中还提供了 AtomicBoolean、AtomicLong和AtomicReference等相关类。

  • 线程局部变量

    有时候可能避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例,示例代码如下:

    package org.drsoft.mybatisExamples.example_thread;

    ??

    import java.text.SimpleDateFormat;

    import java.util.Date;

    ??

    public class ThreadLocalExample {

    ????public static final ThreadLocal<SimpleDateFormat> dataFormat = new ThreadLocal<SimpleDateFormat>(){

    ????????protected SimpleDateFormat initialValue(){

    ????????????return new SimpleDateFormat("yyyy-MM-dd");

    ????????}

    ????};

    ?????

    ????public static void main(String[] args) {

    ????????String dateStamp = dataFormat.get().format(new Date());

    ????????System.out.println("dateStamp="+dateStamp);

    ????}

    }

    在一个指定的线程首次调用 get 方法时,会调用initialValue方法进行初始化,在此之后,get方法会返回属于当前线程的哪个实例。

  • 读写锁(ReentrantReadWriteLock)

    如果很多线程从一个数据结构读取数据,而很少的线程修改其中的数据的话,读写锁时十分有用的,在这种情况下,允许读数据线程访问共享时合适的,写线程依然必须时互斥访问的,下面是使用读写锁的必要步骤:

    • 构造一个ReentrantReadWriteLock对象:

      private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    • 抽取读锁和写锁:

      private Lock readLock = rwl.readLock();

      private Lock writeLock = rwl.writeLock();

    • 对所有的获取方法加读锁:

      ????????????public double getTotalBalance(){

      ????????????????realLock.lock();

      ????????????????try{

      ????????????????// 读取数据代码

      ????????????????}

      ????????????????finally{realLock.unlock();}

      ????????????}

    • 对所有修改方法加写锁:

      ????????????public void transfer(…){

      ????????????????writeLock.lock();

      ????????????????try{

      ????????????????// 写入数据代码

      ????????????????}

      ????????????????finally{writeLock.unlock();}

      ????????????}

  • Callable Future

    前面创建线程对象时,使用的是 Runnable接口,该接口封装异步运行的任务,是没有参数和返回类型的异步返回;Callable 和 Runnable类似,但Callable是有返回值,该接口只有一个方法 call,接口定义如下:

    public interface Callable<V> {

    ????????V call() throws Exception;

    }

    其类型参数是call方法的返回值的类型;Futrue保持异步计算的结果,可以启动一个计算,将Futrue对象交给线程,Futrue 对象的所有者在结果计算好之后就可以获取他,Futrue接口定义如下:

    public interface Future<V> {

    ????????boolean cancel(boolean mayInterruptIfRunning);

    ????????boolean isCancelled();

    ????????boolean isDone();

    ????????V get() throws InterruptedException, ExecutionException;

    ????????V get(long timeout, TimeUnit unit)

    ????????????????throws InterruptedException, ExecutionException, TimeoutException;

    }

    get方法的调用被阻塞,可以设置一个超时时间,如果计算完成则会获取计算结果,如果在超时时间到达后,则会抛出 TimeoutException 异常,如果调用方法被中断,则会抛出 InterruptedException 异常,调用实例如下:

    ????????LogCallable callable = new LogCallable("高优先级线程");

    ????????FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);

    ????????Thread newMaxPriorityThread = new Thread(futureTask);

    ????????newMaxPriorityThread.setPriority(Thread.MAX_PRIORITY);

    ????????newMaxPriorityThread.setDaemon(false);

    ????????newMaxPriorityThread.start();

    ??

    ????????int val = futureTask.get();

    ????????System.out.println("Thread value is " + val);

    ??

    ??

??

??

??

??

??

??

??

笔记:多线程