java并发包(上)

一、 java5原子性操作类的应用

如果变量是线程共享的。例如成员变量,且有多个线程对这个变量进行访问,才做同步。如果是局部变量,则不存在共享问题。线程私有,所以也就不存在安全问题


1.1 案例


class Programmer implements Runnable {
    @Override
    public void run() {
        AtomicInteger li = new AtomicInteger(0);
        // for(int i = 0; i < 10; i++) {}
        for (;  li.get() < 10; li.incrementAndGet()) {
            System.out.println("打印了" + li);
        }
    }
}

注意: i是局部变量,不是线程共享的; 像这样就是完全没有必要的。

请直接使用 for(int i = 0; i < 10; i++) {} 即可。


一定要知道什么情况才需要线程同步; 像下面的代码,是不会出现线程不安全的。

public class StaticProxyStyleThread {

    public static void main(String[] args) {
        Programmer programmer = new Programmer();

        for(int i = 0; i < 10; i++) {
            new Thread(programmer).start();
        }
    }

}

class Programmer implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            //线程私有,不用同步都是线程安全的。会正确打印 i 值
            System.out.println(Thread.currentThread().getName() + " -- " + i);
        }
    }
}

二、java5线程并发库的应用

系统启动一个新线程的成本还是比较高的,因为其涉及与操作系统交互。使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程。


线程池在系统启动时,即创建大量空闲的线程,程序将一个Runnable对象获取Callable对象传给线程池,线程池就会启动一个线程来执行他们的run()或者call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态。


当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JV崩溃,而线程池的最大线程数参数可以控制系统中并发线程数不会超过次数。

2.1 线程池的概念与java.util.concurrent.Executors类的应用

/*
 * 一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
 * 
 * 二、线程池的体系结构:
 *  java.util.concurrent.Executor : 负责线程的使用与调度的根接口
 *      |--ExecutorService 子接口: 线程池的主要接口
 *          |--ThreadPoolExecutor 线程池的实现类
 *          |--ScheduledExecutorService 子接口:负责线程的调度
 *              |--ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现 ScheduledExecutorService
 * 
 * 三、工具类 : Executors 
 * ExecutorService newFixedThreadPool() : 创建固定大小的线程池
 * ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
 * ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
 * 
 * ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
 */

2.2 实际案例

package myThreadPool;

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

/**
 *  线程
 */
public class ThreadPoolTest {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);//启动三个线程
        //ExecutorService threadPool = Executors.newCachedThreadPool();
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();
        for (int i = 1; i <= 10; i++) {//10个任务,用三个线程去处理
            final int task = i;//必须要使用final变量,在匿名内部类中; 用一个中间的final变量 task 来替换 i
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    for (int j = 1; j <= 2; j++) {
                        try {
                            Thread.sleep(20);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + " is looping of " + j + " for  task of " + task);
                    }
                }
            });
        }

    }
}

运行结果

pool-1-thread-3 is looping of 1 for  task of 3
pool-1-thread-2 is looping of 1 for  task of 2
pool-1-thread-1 is looping of 1 for  task of 1
pool-1-thread-3 is looping of 2 for  task of 3
pool-1-thread-2 is looping of 2 for  task of 2
pool-1-thread-1 is looping of 2 for  task of 1
pool-1-thread-2 is looping of 1 for  task of 4
pool-1-thread-3 is looping of 1 for  task of 5
pool-1-thread-1 is looping of 1 for  task of 6
pool-1-thread-2 is looping of 2 for  task of 4
pool-1-thread-3 is looping of 2 for  task of 5
pool-1-thread-1 is looping of 2 for  task of 6
pool-1-thread-2 is looping of 1 for  task of 7
pool-1-thread-3 is looping of 1 for  task of 8
pool-1-thread-1 is looping of 1 for  task of 9
pool-1-thread-3 is looping of 2 for  task of 8
pool-1-thread-2 is looping of 2 for  task of 7
pool-1-thread-1 is looping of 2 for  task of 9
pool-1-thread-3 is looping of 1 for  task of 10
pool-1-thread-3 is looping of 2 for  task of 10

只有三个任务被拿出来服务了。等现场结束完后,后面的任务才能进来。池子中有三个线程,所有的任务都提交到池子中,然后尽最大能力去服务,一次服务三个。其他的任务在池子中进行排队


2.3 关闭API

如果所有任务都执行完了。就可以调用函数让线程结束

API 描述
void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
List shutdownNow() 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

2.4 .newSingleThreadExecutor()

使用ExecutorService threadPool = Executors.newSingleThreadExecutor();来创建一个只包含一个线程的线程池,就像单独开启了一个线程一样。但是和一般的单独线程不同的是,如果该线程池的线程死掉了,单一线程池会重新创建一个替补线程来保证线程池中始终有一个线程。

也就是说,单一线程池的好处就是,可以实现线程死掉之后重新启动的需求


2.5 定时器

public class ThreadPoolTimer {
    public static void main(String[] args) {

        Executors.newScheduledThreadPool(3).scheduleAtFixedRate(
                new Runnable(){
                    @Override
                    public void run() {
                        System.out.println("bombing!" + System.currentTimeMillis() / 1000);

                    }},
                6,
                2,
                TimeUnit.SECONDS);
    }
}

java并发包(上)_第1张图片


三、Callable与Future的应用

3.1 应用

public class CallableAndFuture {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future future =
                threadPool.submit(
                        new Callable() {
                            public String call() throws Exception {
                                Thread.sleep(2000);
                                return "hello";
                            }

                            ;
                        }
                );
        System.out.println("等待结果");
        try {
            System.out.println("拿到结果:" + future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.2 CompletionService

用于提交一组Callable任务,其take方法返回已经完成的一个Callable任务对应的Future对象。

package myThreadPool;


import java.util.Random;
import java.util.concurrent.*;

public class CompletionServiceTest {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        CompletionService completionService = new ExecutorCompletionService(threadPool);
        for (int i = 1; i <= 10; i++) {
            final int seq = i;
            completionService.submit(new Callable() {
                @Override
                public Integer call() throws Exception {
                    Thread.sleep(new Random().nextInt(5000));
                    return seq;
                }
            });
        }
        for (int i = 0; i < 10; i++) {
            try {
                System.out.println(
                        completionService.take().get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}


四、java5的线程锁技术

java并发包(上)_第2张图片

    Lock lock = new ReentrantLock();
        public void output(String name){
            int len = name.length();
            lock.lock();
            try{
                for(int i=0;iout.print(name.charAt(i));
                }
                System.out.println();
            }finally{
                lock.unlock();
            }
        }

Lock l = ...; 
 l.lock();
 try {
     // access the resource protected by this lock
 } finally {
     l.unlock();
 } 

锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用try-finallytry-catch 加以保护,以确保在必要时释放锁


五、 java5读写锁技术的妙用

ReadWriteLock维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的

接口 描述
Lock readLock() 返回用于读取操作的锁
Lock writeLock() 返回用于写入操作的锁。

5.1 案例

三个写,三个读


/**
 *ReentrantReadWriteLock  读写互斥,
 *如果读操作被上锁,写操作就不能进行,
 *如果写操作被上锁,读操作就不能进行,
 *
 *读操作上锁后,需要解锁后, 写才能上锁。
 * 如果读没有解锁,还调用了写的锁,就会造成堵塞,让线程卡在哪里。
 * 反之却是可以的,即在写没有解锁,读操作上锁是可以的。(叫做降级锁)
 * 
 */
public class ReadWriteLockTest {

    public static void main(String[] args) {
        final Queue q3 = new Queue();
        // 弄3个读的线程, 弄3个写的线程
        for (int i = 1; i <= 3; i++) {
            new Thread() {
                public void run() {
                    while (true) {
                        q3.put(new Random().nextInt(10000));
                    }
                }
            }.start();

            new Thread() {
                public void run() {
                    while (true) {
                        q3.get();
                    }
                }
            }.start();
        }
    }
}

class Queue {
    private Object data = null;
    // hibernate load的方法实现就是 ReentrantReadWriteLock 放在代理对象中
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    public void get() {
        rwl.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始取");
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + "取完毕" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        rwl.readLock().unlock();
    }

    public void put(Object obj) {
        rwl.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始写");
            Thread.sleep((long) (Math.random() * 1000));
            this.data = obj;
            System.out.println(Thread.currentThread().getName() + "写结束" + obj);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        rwl.writeLock().unlock();
    }
}

5.1 读写锁实现缓存

下面的代码展示了如何利用重入来执行升级缓存后的锁降级

class CachedData {
   Object data;
   volatile boolean cacheValid;
   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        // Recheck state because another thread might have acquired
        //   write lock and changed state before we did.
        if (!cacheValid) {
          data = ...
          cacheValid = true;
        }
        // Downgrade by acquiring read lock before releasing write lock
        //锁降级
        rwl.readLock().lock();
        rwl.writeLock().unlock(); // Unlock write, still hold read
     }
     use(data);
     rwl.readLock().unlock();
   }
 }

在使用某些种类的 Collection时,可以使用 ReentrantReadWriteLock 来提高并发性。通常,在预期 collection 很大,读取者线程访问它的次数多于写入者线程,并且 entail 操作的开销高于同步开销时,这很值得一试。例如,以下是一个使用 TreeMap 的类,预期它很大,并且能被同时访问。

class RWDictionary {
    private final Map m = new TreeMap();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data get(String key) {
        r.lock();
        try { return m.get(key); }
        finally { r.unlock(); }
    }
    public String[] allKeys() {
        r.lock();
        try { return m.keySet().toArray(); }
        finally { r.unlock(); }
    }
    public Data put(String key, Data value) {
        w.lock();
        try { return m.put(key, value); }
        finally { w.unlock(); }
    }
    public void clear() {
        w.lock();
        try { m.clear(); }
        finally { w.unlock(); }
    }
 }

模拟缓存

public class Cache {

    private Map cache = new HashMap();

    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    public  Object getData(String key){
        rwl.readLock().lock();
        Object value = null;
        try{
            value = cache.get(key);
            if(value == null){
                rwl.readLock().unlock();
                rwl.writeLock().lock();
                try{
                    if(value==null){
                        value = "aaaa";//实际失去queryDB();
                    }
                }finally{
                    rwl.writeLock().unlock();
                }
                rwl.readLock().lock();
            }
        }finally{
            rwl.readLock().unlock();
        }
        return value;
    }
}

六、 java5条件阻塞Condition的应用

6.1 阻塞队列

作为一个示例,假定有一个绑定的缓冲区,它支持puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

ArrayBlockingQueue类提供了这项功能,因此没有理由去实现这个示例类

6.2 案例

a过后b,b过后c,c过后是a

public class ThreeConditionCommunication {

    /**
     * @param args
     */
    public static void main(String[] args) {

        final Business business = new Business();
        new Thread(
                new Runnable() {

                    @Override
                    public void run() {

                        for(int i=1;i<=50;i++){
                            business.sub2(i);
                        }

                    }
                }
        ).start();

        new Thread(
                new Runnable() {

                    @Override
                    public void run() {

                        for(int i=1;i<=50;i++){
                            business.sub3(i);
                        }

                    }
                }
        ).start();      

        for(int i=1;i<=50;i++){
            business.main(i);
        }

    }

    static class Business {
            Lock lock = new ReentrantLock();
            Condition condition1 = lock.newCondition();
            Condition condition2 = lock.newCondition();
            Condition condition3 = lock.newCondition();
          private int shouldSub = 1;
          public  void sub2(int i){
              lock.lock();
              try{
                  while(shouldSub != 2){
                      try {
                        condition2.await();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                  }
                    for(int j=1;j<=10;j++){
                        System.out.println("sub2 thread sequence of " + j + ",loop of " + i);
                    }
                  shouldSub = 3;
                  condition3.signal();
              }finally{
                  lock.unlock();
              }
          }

          public  void sub3(int i){
              lock.lock();
              try{
                  while(shouldSub != 3){
                      try {
                        condition3.await();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                  }
                    for(int j=1;j<=20;j++){
                        System.out.println("sub3 thread sequence of " + j + ",loop of " + i);
                    }
                  shouldSub = 1;
                  condition1.signal();
              }finally{
                  lock.unlock();
              }
          }       

          public  void main(int i){
              lock.lock();
              try{
                 while(shouldSub != 1){
                        try {
                            condition1.await();
                        } catch (Exception e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    for(int j=1;j<=100;j++){
                        System.out.println("main thread sequence of " + j + ",loop of " + i);
                    }
                    shouldSub = 2;
                    condition2.signal();
          }finally{
              lock.unlock();
          }
      }

    }
}

后记

java并发包下,将分小结讲解


参考

  1. 张孝祥-Java多线程与并发库高级应用
  2. 《java疯狂讲义》–李刚

你可能感兴趣的:(java多线程)