七、Java多线程与并发

JAVA平台为程序员提供了并发编程所需的功能强大的API,呵呵,就像一块奶油蛋糕般刺激你的味蕾同时又挑战你将体重控制在某个水平的信念,该篇及后续文章展示Java 多线程与并发编程的诱人之处,帮助理解Java并发编程的模式,以及如何更精确地使用线程模型。

Java 线程基础

一、锁 

       锁提供两种主要特性:互斥(mutual exclusion)可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,这样一次就只有一个线程能够使用共享数据。可见性必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

1.悲观锁:指在本系统的其他事务和外部系统的事务   对数据处理过程中,将数据置于锁定状态。其实现通常基于数据库提供的锁机制,也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据。

2.乐观锁:指相对于悲观锁的更加宽松的加锁机制。其实现通常是基于数据版本记录机制。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库增加一个"version"字段来实现。读取数据时将此版本号一同读出,之后更新时讲此版本号+1。提交数据库时将此版本数据与数据库表中版本数据进行比较,如果提交的数据版本号大于数据库表中的版本,则更新,否则认为是过期数据。

悲观锁与乐观锁的优缺点:悲观锁大多情况下是依靠数据库的锁机制实现的,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样大的开销往往无法承受。而乐观锁在一定程度上解决了这个问题。

 

二、原子变量

原子变量保证对一个变量的操作是原子的。即在多线程并发处理的时候,使用原子变量可将对一个变量的复合操作(复合操作是指后一个操作依赖前一个操作的结果)合并为一个原子操作。起到简化同步处理的作用。

原理:使用同步synchronized的方法实现了对一个Long、Integer、对象的增、减、更新操作。

AtomicLong/AtomicInteger/AtomicReference

注意:a.对array atomic变量来说,一次只能有一个索引变量可以变动,并不是对整个array做原子化的变动。

            b.在对一个atomic变量执行两个或两个以上的操作时,或对两个或两个以上的atomic变量执行操作时,需要用synchronized来实现将多个操作当做一个原子操作的目的。

方法:

getAndSet() : 设置新值,返回旧值.

compareAndSet(expectedValue,newValue):如果当前值等于expectedValue,则更新指定值为newValue,如果更新成功返回true。否则返回false,即在当前线程得到这个变量的值之后已经有其他线程修改了这个值,和期望看到的值expectedValue不一样,那么更新失败。

下面是关于AtomicInteger的例子:

package com.taobao.app;

import java.util.concurrent.atomic.AtomicInteger;

public class CounterTest {
 AtomicInteger counter = new AtomicInteger(100);

 public int count() {
  int expectedValue;
  int newValue;
  boolean flag;
  do {
   expectedValue = counter.get();
   System.out.println("when start,"+Thread.currentThread().getName()+"'s expectedValue="+expectedValue);
   try {
    Thread.sleep(1000l);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
// 单线程下, compareAndSet返回永远为true
// 多线程下, 在与expectedValue进行比较时, counter可能被其他线程更改了值, 这时需要重新再取一遍再比较,如果还是没有拿到最新的值则一直循环下去, 直到拿到最新的那个值
   newValue = expectedValue + 1;
   flag = counter.compareAndSet(expectedValue, newValue);
   newValue = counter.get();
   System.out.println("when end  ,"+Thread.currentThread().getName()+"'s newValue="+newValue+" and flag="+flag);
  } while (!flag);

  return expectedValue;
 }

 public static void main(String[] args) {
  final CounterTest c = new CounterTest();
  new Thread() {
   @Override
   public void run() {
    c.count();
   }
  }.start();

  new Thread() {
   @Override
   public void run() {
    c.count();
   }
  }.start();

  new Thread() {
   @Override
   public void run() {
    c.count();
   }
  }.start();
 }
}

输出结果如下:

when start,Thread-0's expectedValue=100
when start,Thread-1's expectedValue=100
when start,Thread-2's expectedValue=100
when end  ,Thread-0's newValue=101 and flag=true
when end  ,Thread-1's newValue=101 and flag=false
when start,Thread-1's expectedValue=101
when end  ,Thread-2's newValue=101 and flag=false
when start,Thread-2's expectedValue=101
when end  ,Thread-1's newValue=102 and flag=true
when end  ,Thread-2's newValue=102 and flag=false
when start,Thread-2's expectedValue=102
when end  ,Thread-2's newValue=103 and flag=true

 

再来个AtomicReference使用的例子:

public class AtomicTest {     
    private int x, y;     
    
    private enum State {     
        NEW, INITIALIZING, INITIALIZED     
    };     
    
    private final AtomicReference<State> init = new AtomicReference<State>(State.NEW);     
         
    public AtomicTest() {     
    }     
         
    public AtomicTest(int x, int y) {     
        initialize(x, y);     
    }     
    
    private void initialize(int x, int y) {     
        if (!init.compareAndSet(State.NEW, State.INITIALIZING)) {     
            throw new IllegalStateException("initialize is error");     
        }     
        this.x = x;     
        this.y = y;     
        init.set(State.INITIALIZED);     
    }     
    
    public int getX() {     
        checkInit();     
        return x;     
    }     
    
    public int getY() {     
        checkInit();     
        return y;     
    }     
         
    private void checkInit() {     
        if (init.get() == State.INITIALIZED) {     
            throw new IllegalStateException("uninitialized");     
        }     
    }     
         
}

 

三、两种任务:Runnable和Callable

Runnable接口 ;  Callable接口和Future类、FutureTask类

    1.实现Runable接口或Callable接口的类都是可被其他线程执行的任务.

    2.Callable和Runnable的区别如下:

        1)Callable定义的方法是call,而Runnable定义的方法是run.

        2)Callable的call方法可以有返回值,而Runnable的run方法不能有返回值

        3)Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常

    3.Future表示异步计算的结果,它提供的方法可以用于检查计算是否完成,以等待计算的完成,并获取计算的结果.

       cancel方法取消任务的执行,有一个布尔参数,参数为true表示立即中断任务的执行,参数为false表示允许正在运行的任务运行完成.

       get方法等待计算完成,获取计算结果.

示例代码如下:

package com.taobao.thread;

import java.util.concurrent.Callable;

public class PrimeCallable implements Callable<String> {
 public String call(){
  try {
   Thread.sleep(3000l);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  return "我在执行一个费事的操作,太墨迹了,执行了3秒钟";
 }
}

import java.util.concurrent.FutureTask;

public class FutureDemo {

 public static void main(String[] args) {
  Callable<String> primeCallable = new PrimeCallable();
  FutureTask<String> primeTask = new FutureTask<String>(primeCallable);
  Thread t = new Thread(primeTask);
  t.start();

  try {
   // 假设现在做其他事情
   Thread.sleep(3001);
 
   // 回来看看primeTask执行好了吗
   if(primeTask.isDone()) {
    String str = primeTask.get();
    System.out.println(str);
   }
  } catch (InterruptedException e) {
   e.printStackTrace();
  } catch (ExecutionException e) {
   e.printStackTrace();
  }
 }
}

注意上面程序中sleep的时间只有在大于Callable中的执行时间时,才有可能进入到if(primeTask.isDone()) 中。

上面程序会在控制台打印出callable返回的结果,即

我在执行一个费事的操作,太墨迹了,执行了3秒钟

 

注:FutureTask用于要异步获取执行结果或取消执行任务的场景,通过传入Runnable或Callable的任务给FutureTask,直接调用其run方法或放入线程池执行,之后可在外部通过FutureTask的get异步获取执行结果。FutureTask可以确保即使调用了多次run方法,它都只会执行一次Runnable或Callable任务,或者通过cancel取消FutureTask的执行等。

 

再换个调用方式:

package com.taobao.thread;

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

public class ThreadPoolDemo {
 
 public static void main(String[] args) {
  
  PrimeCallable task0 = new PrimeCallable();
  PrimeCallable task1 = new PrimeCallable();
  PrimeCallable task2 = new PrimeCallable();

     //创建一个执行任务的服务
     ExecutorService es = Executors.newFixedThreadPool(3);
 
     try{
 
            //提交并执行任务,任务启动时返回了一个Future对象,如果想得到任务执行的结果或者是异常可对这个Future对象进行操作
            Future future0 = es.submit(task0);
            //获得第一个任务的结果,如果调用get方法,当前线程会等待任务执行完毕后才往下执行
            System.out.println(System.currentTimeMillis()+":task0: " + future0.get());
 
           
            //等待5秒后,再停止第二个任务,因为第二个任务进行的是无限循环
            Future future1 = es.submit(task1);
            Thread.sleep(5000);
            System.out.println(System.currentTimeMillis()+":task1 cancel: " + future1.cancel(true));
 
           
 
            //获取第三个任务的输出,因为执行第三个任务会引起异常,所以下面的语句将引起异常的输出
            Future future2 = es.submit(task2);
            System.out.println(System.currentTimeMillis()+":task2: " + future2.get());
 
     }catch(Exception e){
 
            System.out.println(e.toString());
 
     }

     es.shutdown();//立即停止任务执行服务
 }

}

 

输出如下:

1323607709980:task0: 我在执行一个费事的操作,太墨迹了,执行了3秒钟
1323607717980:task1 cancel: false
1323607717980:task2: 我在执行一个费事的操作,太墨迹了,执行了3秒钟

 

注:Task Submitter把任务提交给Executor执行,他们之间需要一种通讯手段,这种手段的具体实现,叫做Future。Future通常包括get(阻塞至任务完成),cancel,get(timeout)(等待一段时间)等等。Future也用于异步变同步的场景。

下面是关于FutureTask的例子:

应用场景:有一个带key的连接池,当key已经存在时,即直接返回key对应的对象;当key不存在是,则直接创建连接。使用一个Map对象来存储key和连接池对象的对应关系。

典型的实现代码如下:

Map<String,Connection> connectionPool= new HashMap<String,Connection>();

ReentrantLock reentrantLock = new ReentrantLock ();

public  Connection getConnection(String key){

      try{

            reentrantLock .lock();

            if(connectionPool.containsKey(key)){

                return connectionPool.get(key);

            }else{

                connectionPool.put(key,connection);

                return connection;

           }

     }finally{

          reentrantLock .unlock();

    }

}

改用ConcurrentHashMap的情况下,几乎可以避免加锁的操作,但在并发的情况下有可能出现Connection被创建多次的现象。这时最需要的解决方案就是当key不存在时,创建Connection的动作能放在connectionPool之后执行,这正是FutureTask发挥作用的时机,基于ConcurrentHashMap和FutureTask的改造代码如下:

Map<String,Connection> connectionPool = new ConcurrentHashMap<String,Connection>();

public Connection getConnection(String key){

       FutureTask<Connection> connectionTask = connectionPool.get(key);

       if(connectionTask != null){

             return connectionTask.get();

       }else{

             Callable<Connection> callable = new Callable<Connection>();

             FutureTask<Connection> newTask = new FutureTask<Connection>(callable);

             connectionTask = connectionPool.putIfAbsent(key,newTask);

             if(connectiontask == null){

                   connectionTask = new   Task();

                   connectionTask.run();

            }

            return connectionTask.get();

      }

}

经过这样的改造,可以避免由于并发带来的多次创建连接及锁的出现。

 

四、Synchronized和ReentrantLock

入锁ReentrantLock是一种递归无阻塞的同步机制。ReentrantLock具有与synchronized相同的一些基本行为和语义,但功能更强大。ReentrantLock将由最近成功获得锁定并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。

可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

 

示例如下:

package com.taobao.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantDemo {
 public static void main(String[] args) throws InterruptedException {

  final ExecutorService exec = Executors.newFixedThreadPool(4);

  final ReentrantLock lock = new ReentrantLock();

  final Condition con = lock.newCondition();

  final int time = 5;

  final Runnable add = new Runnable() {

   public void run() {
 
    System.out.println("Pre " + lock);
  
    lock.lock();
  
    try {
  
     con.await(time, TimeUnit.SECONDS);
  
    } catch (InterruptedException e) {
  
     e.printStackTrace();
  
    } finally {
  
     System.out.println("Post " + lock.toString());
  
     lock.unlock();
  
    }
 
   }

  };

  for(int index = 0; index < 4; index++)

   exec.submit(add);
 
   exec.shutdown();

  }

}

输出如下:

Pre java.util.concurrent.locks.ReentrantLock@157f0dc[Unlocked]
Pre java.util.concurrent.locks.ReentrantLock@157f0dc[Unlocked]
Pre java.util.concurrent.locks.ReentrantLock@157f0dc[Unlocked]
Pre java.util.concurrent.locks.ReentrantLock@157f0dc[Unlocked]
Post java.util.concurrent.locks.ReentrantLock@157f0dc[Locked by thread pool-1-thread-2]
Post java.util.concurrent.locks.ReentrantLock@157f0dc[Locked by thread pool-1-thread-3]
Post java.util.concurrent.locks.ReentrantLock@157f0dc[Locked by thread pool-1-thread-1]
Post java.util.concurrent.locks.ReentrantLock@157f0dc[Locked by thread pool-1-thread-4]

如将代码改成:

package com.taobao.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantDemo {
 public static void main(String[] args) throws InterruptedException {

  final ExecutorService exec = Executors.newFixedThreadPool(4);

  final ReentrantLock lock = new ReentrantLock();

  final Condition con = lock.newCondition();

  final int time = 5;

  final Runnable add = new Runnable() {

   public void run() {
 
    System.out.println("Pre " + lock);
  
    lock.lock();
  
    try {
  
//     con.await(time, TimeUnit.SECONDS);
     Thread.sleep(5000);
  
    } catch (InterruptedException e) {
  
     e.printStackTrace();
  
    } finally {
  
     System.out.println("Post " + lock.toString());
  
     lock.unlock();
  
    }
 
   }

  };

  for(int index = 0; index < 4; index++)

   exec.submit(add);
 
   exec.shutdown();

  }

}

则输出变为:

Pre java.util.concurrent.locks.ReentrantLock@12ac982[Unlocked]
Pre java.util.concurrent.locks.ReentrantLock@12ac982[Locked by thread pool-1-thread-1]
Pre java.util.concurrent.locks.ReentrantLock@12ac982[Locked by thread pool-1-thread-1]
Pre java.util.concurrent.locks.ReentrantLock@12ac982[Locked by thread pool-1-thread-1]
Post java.util.concurrent.locks.ReentrantLock@12ac982[Locked by thread pool-1-thread-1]
Post java.util.concurrent.locks.ReentrantLock@12ac982[Locked by thread pool-1-thread-3]
Post java.util.concurrent.locks.ReentrantLock@12ac982[Locked by thread pool-1-thread-2]
Post java.util.concurrent.locks.ReentrantLock@12ac982[Locked by thread pool-1-thread-4]

以上的对比说明线程在等待时(con.await),已经不在拥有该锁了,所以其他线程就可以获得重入锁了。

 

 

五、线程池ThreadPoolExecutor、Executor

ThreadPoolExecutor是java.util.concurrent包中提供的一个线程池服务,基于它可以很容易的将一个实现了Runnable接口的任务放入线程池中执行。其方法如下:

执行构造器:ThreadPoolExecutor(int,int,long,TimeUnit,BlockQueue,ThreadFactory,RejectedExecutionHandler)所做的动作仅为保存了这些入参的值。

execute(Runnable)负责将Runnable任务放入线程池中执行。

 

Executor提供了一些方便创建ThreadPoolExecutor的方法。主要有以下几个方法:

newFixedThreadPool(int)创建固定大小的线程池。线程keepAliveTime为0,启动的corePoolSize数量的线程启动后就一直运行,并不会由于keepAliveTime时间到达后仍没有任务需要执行就退出。缓冲任务的队列为LinkedBlockingQueue,大小为整形的最大值。在同时执行的task数量超过传入的线程池大小值后,将会放入LinkedBlockingQueue,在LinkedBlockingQueue中的task需要等待线程空闲后才执行,当放入LinkedBlockingQueue的task数量超过整形最大值时,抛出RejectedExecutionException。

newSingleThreadExecutor()创建大小为1的固定线程池。同时执行的task只有1,其他task都在LinkedBlockingQueue中。

newCachedThreadPool()创建corePoolSize为0,最大线程数为整形的最大数,线程keepAliveTime为1分钟即启动后的线程存活时间为1分钟,缓存任务的队列为SynchronousQueue的线程池。

newScheduledThreadPool(int)创建corePoolSize为传入参数,最大线程数为整形的最大值,线程keepAliveTime为0,缓存任务的队列为DelayedWorkQueue的线程池。在遇到需要定时或延迟执行的任务,或在异步操作时需要超时回调的场景,使用ScheduledThreadPool是不错的选择。

 

 

六、CountDownLatch和CyclicBarrier

CountDownLatch用于控制多个线程同时开始某动作,采用的方式为减计数的方式,当计数减至0时,位于await后的代码才会被执行。

CountDownLatch(int):将state设置为入参值。

await():判断count属性值是否等于0,如等于0则直接返回,如不等于0则等待直到count等于0为止或线程被interrupt。

countdown()将state值减。

CyclicBarrier是当await的数量达到了设定的数值后,才继续往下执行。

CyclicBarrier(int):设置parties、count及barrierCommand属性。

CyclicBarrier(Runnable):当await的数量到达了设定的数量后,会首先执行此Runnable对象。

await():count属性值减1后等于0则执行传入的Runnable对象。

 

 

七、CopyOnWriteArrayList、CopyOnWriteArraySet、ArrayBlockingQueue

CopyOnWriteArrayList是一个线程安全的并且在读操作时无锁的ArrayList。通过默认构造子创建的是大小为0的数组。

CopyOnWriteArraySet基于CopyOnWriteArrayList实现,与CopyOnWriteArrayList不同的是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法,创建一个新的大小+1的Object数组。

ArrayBlockingQueue是一个基于数组、先进先出、线程安全的集合类,特点是可实现指定时间的阻塞读写,并且容量是可限制的。

 

 

你可能感兴趣的:(七、Java多线程与并发)