java.util.concurrent包API学习笔记

一.newFixedThreadPool

newFixedThreadPool:创建一个固定大小的线程池。

shutdown():用于关闭启动线程,如果不调用该语句,jvm不会关闭。
awaitTermination():用于等待子线程结束,再继续执行下面的代码。该例中我设置一直等着子线程结束。

  实例:Java主线程启动N个子线程,等子线程都结束后再执行主线程

复制代码
package com.bijian.study;

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

public class NewFixedThreadPoolTest {

    public static void main(String[] args) throws IOException, InterruptedException {

        ExecutorService service = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 4; i++) {
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    System.out.println("thread start");
                }
            };
            service.execute(run);
        }
        service.shutdown();
        service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("all thread complete");
    }
}
复制代码

  运行结果:

thread start
thread start
thread start
thread start
all thread complete

 

二.newScheduledThreadPool
  这个先不说,我喜欢用spring quartz.

 

三.CyclicBarrier
  假设有只有的一个场景:每个线程代表一个跑步运动员,当运动员都准备好后,才一起出发,只要有一个人没有准备好,大家都等待。

复制代码
package com.bijian.study;

import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Runner implements Runnable {

    private CyclicBarrier barrier;

    private String name;

    public Runner(CyclicBarrier barrier, String name) {
        super();
        this.barrier = barrier;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000 * (new Random()).nextInt(8));
            System.out.println(name + " 准备OK.");
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(name + " Go!!");
    }
}

public class Race {

    public static void main(String[] args) throws IOException, InterruptedException {

        CyclicBarrier barrier = new CyclicBarrier(3);

        ExecutorService executor = Executors.newFixedThreadPool(3);
        executor.submit(new Thread(new Runner(barrier, "zhangsan")));
        executor.submit(new Thread(new Runner(barrier, "lisi")));
        executor.submit(new Thread(new Runner(barrier, "wangwu")));

        executor.shutdown();
    }
}
复制代码

  运行结果:

wangwu 准备OK.
zhangsan 准备OK.
lisi 准备OK.
lisi Go!!
wangwu Go!!
zhangsan Go!!

 

四.ThreadPoolExecutor

  newFixedThreadPool生成一个固定的线程池,顾名思义,线程池的线程是不会释放的,即使它是Idle。这就会产生性能问题,比如如果线程池的大小为200,当全部使用完毕后,所有的线程会继续留在池中,相应的内存和线程切换(while(true)+sleep循环)都会增加。如果要避免这个问题,就必须直接使用ThreadPoolExecutor()来构造。可以像Tomcat的线程池一样设置“最大线程数”、“最小线程数”和“空闲线程keepAlive的时间”。

  ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
  corePoolSize:池中所保存的线程数,包括空闲线程(非最大同时干活的线程数)。如果池中线程数多于 corePoolSize,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。
  maximumPoolSize:线程池中最大线程数
  keepAliveTime:线程空闲回收的时间
  unit:keepAliveTime的单位
  workQueue:保存任务的队列,可以如下选择:
  a.无界队列: new LinkedBlockingQueue<Runnable>();
  b.有界队列: new ArrayBlockingQueue<Runnable>(8);你不想让客户端无限的请求吃光你的CPU和内存吧,那就用有界队列
  handler:当提交任务数大于队列size会抛出RejectedExecutionException,可选的值为:
  a.ThreadPoolExecutor.CallerRunsPolicy 等待队列空闲
  b.ThreadPoolExecutor.DiscardPolicy:丢弃要插入队列的任务
  c.ThreadPoolExecutor.DiscardOldestPolicy:删除队头的任务
  关于corePoolSize和maximumPoolSize,Java官方Docs写道:

当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求(即使存在空闲线程)。
如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列(queue)满时才创建新线程。
如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。
如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。
复制代码
package com.bijian.study;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();容量大小默认为Integer.MAX_VALUE,也就是说这个队列(queue)不会满,导致线程池永远达不到maximumPoolSize大小
 */
public class ThreadPoolExecutorTest {

    public static void main(String[] args) {
        
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(3);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.DAYS, queue);

        for (int i = 0; i < 20; i++) {
            final int index = i;
            executor.execute(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(4000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + String.format("thread %d finished", index));
                }
            });
        }
        executor.shutdown();
    }
}
复制代码

  运行结果:

复制代码
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.bijian.study.ThreadPoolExecutorTest$1@27385846 rejected from java.util.concurrent.ThreadPoolExecutor@5fb7a531[Running, pool size = 6, active threads = 6, queued tasks = 3, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.reject(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.execute(Unknown Source)
    at com.bijian.study.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:20)
pool-1-thread-4thread 6 finished
pool-1-thread-1thread 0 finished
pool-1-thread-2thread 1 finished
pool-1-thread-6thread 8 finished
pool-1-thread-5thread 7 finished
pool-1-thread-3thread 2 finished
pool-1-thread-1thread 4 finished
pool-1-thread-2thread 5 finished
pool-1-thread-4thread 3 finished
复制代码

 

五.原子变量(Atomic )

  并发库中的BlockingQueue是一个比较好玩的类,顾名思义,就是阻塞队列。该类主要提供了两个方法put()和take(),前者将一个对象放到队列中,如果队列已经满了,就等待直到有空闲节点;后者从head取一个对象,如果没有对象,就等待直到有可取的对象。
  下面的例子比较简单,一个读线程,用于将要处理的文件对象添加到阻塞队列中,另外四个写线程用于取出文件对象,为了模拟写操作耗时长的特点,特让线程睡眠一段随机长度的时间。另外,该Demo也使用到了线程池和原子整型(AtomicInteger),AtomicInteger可以在并发情况下达到原子化更新,避免使用了synchronized,而且性能非常高。由于阻塞队列的put和take操作会阻塞,为了使线程退出,在队列中添加了一个“标识”,算法中也叫“哨兵”,当发现这个哨兵后,写线程就退出。

复制代码
package com.bijian.study;

import java.io.File;
import java.io.FileFilter;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicTest {

    static long randomTime() {
        return (long) (Math.random() * 1000);
    }

    public static void main(String[] args) {
        // 能容纳100个文件
        final BlockingQueue<File> queue = new LinkedBlockingQueue<File>(100);
        // 线程池
        final ExecutorService exec = Executors.newFixedThreadPool(5);
        final File root = new File("D:\\dist\\blank");
        // 完成标志
        final File exitFile = new File("");
        // 读个数
        final AtomicInteger rc = new AtomicInteger();
        // 写个数
        final AtomicInteger wc = new AtomicInteger();
        // 读线程
        Runnable read = new Runnable() {
            public void run() {
                scanFile(root);
                scanFile(exitFile);
            }

            public void scanFile(File file) {
                if (file.isDirectory()) {
                    File[] files = file.listFiles(new FileFilter() {
                        public boolean accept(File pathname) {
                            return pathname.isDirectory() || pathname.getPath().endsWith(".log");
                        }
                    });
                    for (File one : files)
                        scanFile(one);
                } else {
                    try {
                        int index = rc.incrementAndGet();
                        System.out.println("Read0: " + index + " " + file.getPath());
                        queue.put(file);
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        exec.submit(read);
        // 四个写线程
        for (int index = 0; index < 4; index++) {
            // write thread
            final int num = index;
            Runnable write = new Runnable() {
                String threadName = "Write" + num;

                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(randomTime());
                            int index = wc.incrementAndGet();
                            File file = queue.take();
                            // 队列已经无对象
                            if (file == exitFile) {
                                // 再次添加"标志",以让其他线程正常退出
                                queue.put(exitFile);
                                break;
                            }
                            System.out.println(threadName + ": " + index + " " + file.getPath());
                        } catch (InterruptedException e) {
                        }
                    }
                }

            };
            exec.submit(write);
        }
        exec.shutdown();
    }
}
复制代码

  运行结果:

Read0: 1 D:\dist\blank
Read0: 2 
Write3: 1 D:\dist\blank

 

六.CountDownLatch

  从名字可以看出,CountDownLatch是一个倒数计数的锁,当倒数到0时触发事件,也就是开锁,其他人就可以进入了。在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。
  CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
  一个CountDouwnLatch实例是不能重复使用的,也就是说它是一次性的,锁一经被打开就不能再关闭使用了,如果想重复使用,请考虑使用CyclicBarrier。
  下面的例子简单的说明了CountDownLatch的使用方法,模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。

复制代码
package com.bijian.study;

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

public class CountDownLatchTest {

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

        // 开始的倒数锁  
        final CountDownLatch begin = new CountDownLatch(1);

        // 结束的倒数锁  
        final CountDownLatch end = new CountDownLatch(10);

        // 十名选手  
        final ExecutorService exec = Executors.newFixedThreadPool(10);

        for (int index = 0; index < 10; index++) {
            final int NO = index + 1;
            Runnable run = new Runnable() {
                public void run() {
                    try {
                        begin.await();
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out.println("No." + NO + " arrived");
                    } catch (InterruptedException e) {
                    } finally {
                        end.countDown();
                    }
                }
            };
            exec.submit(run);
        }
        System.out.println("Game Start");
        begin.countDown();
        end.await();
        System.out.println("Game Over");
        exec.shutdown();
    }
}
复制代码

  运行结果:

复制代码
Game Start
No.1 arrived
No.3 arrived
No.9 arrived
No.5 arrived
No.2 arrived
No.4 arrived
No.6 arrived
No.8 arrived
No.10 arrived
No.7 arrived
Game Over
复制代码

 

七.使用Callable和Future实现线程等待和多线程返回值

  假设在main线程启动一个线程,然后main线程需要等待子线程结束后,再继续下面的操作,我们会通过join方法阻塞main线程,代码如下:

Runnable runnable = ...;
Thread t = new Thread(runnable);
t.start();
t.join();
......

  通过JDK1.5线程池管理的线程可以使用Callable和Future实现(join()方法无法应用到在线程池线程)

例一:

复制代码
package com.bijian.study;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class CallableFutureTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        
        System.out.println("start main thread");
        final ExecutorService exec = Executors.newFixedThreadPool(5);

        Callable<String> call = new Callable<String>() {
            public String call() throws Exception {
                System.out.println("  start new thread.");
                Thread.sleep(1000 * 5);
                System.out.println("  end new thread.");
                return "some value.";
            }
        };
        Future<String> task = exec.submit(call);
        Thread.sleep(1000 * 2);
        task.get(); // 阻塞,并待子线程结束,  
        exec.shutdown();
        exec.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("end main thread");
    }
}
复制代码

  运行结果:

start main thread
  start new thread.
  end new thread.
end main thread

例二:

复制代码
package com.bijian.study;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/** 
* 多线程返回值测试 
*/
public class CallableFutureTest02 {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("start main thread");
        int threadCount = 5;
        final ExecutorService exec = Executors.newFixedThreadPool(threadCount);

        List<Future<Integer>> tasks = new ArrayList<Future<Integer>>();
        for (int i = 0; i < threadCount; i++) {
            Callable<Integer> call = new Callable<Integer>() {
                public Integer call() throws Exception {
                    Thread.sleep(1000);
                    return 1;
                }
            };
            tasks.add(exec.submit(call));
        }
        long total = 0;
        for (Future<Integer> future : tasks) {
            total += future.get();
        }
        exec.shutdown();
        System.out.println("total: " + total);
        System.out.println("end main thread");
    }
}
复制代码

  运行结果:

start main thread
total: 5
end main thread

 

八.CompletionService

  这个东西的使用上很类似上面的example,不同的是,它会首先取完成任务的线程。下面的参考文章里,专门提到这个,大家有兴趣可以看下,例子:

复制代码
package com.bijian.study;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CompletionServiceTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        
        ExecutorService exec = Executors.newFixedThreadPool(10);
        CompletionService<String> serv = new ExecutorCompletionService<String>(exec);
        for (int index = 0; index < 5; index++) {
            final int NO = index;
            Callable<String> downImg = new Callable<String>() {
                public String call() throws Exception {
                    Thread.sleep((long) (Math.random() * 10000));
                    return "Downloaded Image " + NO;
                }
            };
            serv.submit(downImg);
        }
        Thread.sleep(1000 * 2);
        System.out.println("Show web content");
        for (int index = 0; index < 5; index++) {
            Future<String> task = serv.take();
            String img = task.get();
            System.out.println(img);
        }
        System.out.println("End");
        // 关闭线程池  
        exec.shutdown();
    }
}
复制代码

   运行结果:

Show web content
Downloaded Image 2
Downloaded Image 0
Downloaded Image 1
Downloaded Image 3
Downloaded Image 4
End

 

九.Semaphore信号量

  拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。下面的例子只允许5个线程同时进入执行acquire()和release()之间的代码。

复制代码
package com.bijian.study;

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

public class SemaphoreTest {

    public static void main(String[] args) {
        
        // 线程池  
        ExecutorService exec = Executors.newCachedThreadPool();
        // 只能5个线程同时访问  
        final Semaphore semp = new Semaphore(5);
        // 模拟20个客户端访问  
        for (int index = 0; index < 20; index++) {
            final int NO = index;
            Runnable run = new Runnable() {
                public void run() {
                    try {
                        // 获取许可  
                        semp.acquire();
                        System.out.println("Accessing: " + NO);
                        Thread.sleep((long) (Math.random() * 10000));
                        // 访问完后,释放  
                        semp.release();
                    } catch (InterruptedException e) {
                    }
                }
            };
            exec.execute(run);
        }
        // 退出线程池  
        exec.shutdown();
    }
}
复制代码

  运行结果:

复制代码
Accessing: 0
Accessing: 3
Accessing: 2
Accessing: 1
Accessing: 4
Accessing: 5
Accessing: 7
Accessing: 6
Accessing: 8
Accessing: 9
Accessing: 10
Accessing: 11
Accessing: 12
Accessing: 13
Accessing: 14
Accessing: 15
Accessing: 16
Accessing: 17
Accessing: 18
Accessing: 19
复制代码

 

PS:进一步学习链接

jdk1.5中的线程池使用简介:http://www.java3z.com/cwbwebhome/article/article2/2875.html
CAS原理:http://www.blogjava.net/syniii/archive/2010/11/18/338387.html?opt=admin
jdk1.5中java.util.concurrent包编写多线程:http://hi.baidu.com/luotoo/blog/item/b895c3c2d650591e0ef47731.html
ExecutorSerive vs CompletionService:http://www.coderanch.com/t/491704/threads/java/ExecutorSerive-vs-CompletionService

 

文章来源:http://heipark.iteye.com/blog/1156011

posted on   bijian1013  阅读(300)  评论(2编辑  收藏  举报

编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示