Java--JUC并发工具

ReentrantLock

ReentrantReadWriteLock

CountDownLatch (信号量)

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信;

主要方法:

构造一个用给定计数初始化的 CountDownLatch。
CountDownLatch(int count)


// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()

使用方式:


    private static CountDownLatch doneSignal;
    public static void main(String[] args) {

        try {
            doneSignal = new CountDownLatch(LATCH_SIZE);

            // 新建5个任务
            for(int i=0; i<LATCH_SIZE; i++)
                new InnerThread().start();

            System.out.println("main await begin.");
            // "主线程"等待线程池中5个任务的完成
            doneSignal.await();

            System.out.println("main await finished.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class InnerThread extends Thread{
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
                // 将CountDownLatch的数值减1
                doneSignal.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

CyclicBarrier(栅栏)

所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。

可以用于多线程计算数据,最后合并计算结果的场景。

主要方法:

//初始化  parties 是参与线程的个数 
//第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)

//表示自己已经到达栅栏
public int await() throws InterruptedException, BrokenBarrierException
//表示自己已经到达栅栏 并且最多等待timeout时间
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException

使用方法:

static class TaskThread extends Thread {
        
        CyclicBarrier barrier;
        
        public TaskThread(CyclicBarrier barrier) {
            this.barrier = barrier;
        }
        
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(getName() + " 到达栅栏 A");
                barrier.await();
                System.out.println(getName() + " 冲破栅栏 A");
                
                Thread.sleep(2000);
                System.out.println(getName() + " 到达栅栏 B");
                barrier.await();
                System.out.println(getName() + " 冲破栅栏 B");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        int threadNum = 5;
        CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
            
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 完成最后任务");
            }
        });
        
        for(int i = 0; i < threadNum; i++) {
            new TaskThread(barrier).start();
        }
    }

CountDownLatch和CyclicBarrier的区别:

(1) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。

(2) CountDownLatch参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。

(3) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

Semaphore 信号量

比喻:相当于一个阀门,来控制某时刻的流量;比如高速路收费站;

举例:用来控制同时执行的最大线程数;常用于限流;

用法:初始化时指定最大信号量,线程调用acquire方法,表接下来一段时间这一通道会有自己参与占用;如果acquire时已经满了,就阻塞住;调用release方法表示释放占用;

注意:支持公平和非公平模式;

Exchanger 交换器

用于两个线程间的唤醒,并值传递;

过程:

1. Exchanger内部有两个位置,当第一个线程A执行了exchange方法后,将传入的值放入第一个位置,计为A1,然后A进入阻塞状态,直到有其他线程也来执行了exchange方法为止;

2. 然后,线程B也来执行exchange方法,首先将传入的值写到第二个位置A2,然后交换A1和A2的值,接下来两个线程分别向下执行;

LockSupport

主要对标wait()/notify();

主要用法:park()、unpark(),内部不是锁,调用了之后立马停止或者恢复;

注意,unpark()可以先于park()执行,效果是:程序不会暂停,即park()不生效;

区别:LockSupport可以启动指定线程,而wait/notify要做到这一点确是非常麻烦;

Fork/Join框架 -- ForkJoinPool

ForkJoinPool 是 JDK1.7 开始提供的线程池。为了解决 CPU 负载不均衡的问题。如某个较大的任务,被一个线程去执行,而其他线程处于空闲状态;

如上所示,forkjoin框架是为了加大任务做拆分然后交由多个子线程去执行,然后返回结果。

       第一步分割任务。首先我们需要有一个fork类来把大任务分割成子任务,有可能子任务还是很大,所以还需要不停的分割,直到分割出的子任务足够小。

       第二步执行任务并合并结果。分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据。

ForkJoinPool将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合成总的计算结果。ForkJoinPool是ExecutorService的一个实现类,所以它是一个线程池;

使用方法:

//创建一个包含parallelism个并行线程的ForkJoinPool
public ForkJoinPool(int parallelism);
//以Runtime.getRuntime().availableProcessors()的返回值作为parallelism来创建ForkJoinPool
public ForkJoinPool();

ForkJoinPool提供了一系列invoke、submit和excute方法,来执行指定任务,一般地,调用ForkJoinPool的submit(ForkJoinTask task)或者invoke(ForkJoinTask task)来执行指定任务。其中ForkJoinTask代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,它有两个抽象子类:RecursiveAction和RecursiveTask。

  • RecursiveTask代表有返回值的任务
  • RecursiveAction代表没有返回值的任务。

RecursiveAction的示例:

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

//RecursiveAction为ForkJoinTask的抽象子类,没有返回值的任务
class PrintTask extends RecursiveAction {
	// 每个"小任务"最多只打印50个数
	private static final int MAX = 50;

	private int start;
	private int end;

	PrintTask(int start, int end) {
		this.start = start;
		this.end = end;
	}

	@Override
	protected void compute() {
		// 当end-start的值小于MAX时候,开始打印
		if ((end - start) < MAX) {
			for (int i = start; i < end; i++) {
				System.out.println(Thread.currentThread().getName() + "的i值:"
						+ i);
			}
		} else {
			// 将大任务分解成两个小任务
			int middle = (start + end) / 2;
			PrintTask left = new PrintTask(start, middle);
			PrintTask right = new PrintTask(middle, end);
			// 并行执行两个小任务
			left.fork();
			right.fork();
		}
	}
}

public class ForkJoinPoolTest {
	/**
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		// 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		// 提交可分解的PrintTask任务
		forkJoinPool.submit(new PrintTask(0, 200));
		forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);//阻塞当前线程直到 ForkJoinPool 中所有的任务都执行结束
		// 关闭线程池
		forkJoinPool.shutdown();
	}

}
RecursiveTask示例:
class SumTask extends RecursiveTask<integer> {
	// 每个"小任务"最多只打印50个数
	private static final int MAX = 20;
	private int arr[];
	private int start;
	private int end;

	SumTask(int arr[], int start, int end) {
		this.arr = arr;
		this.start = start;
		this.end = end;
	}

	@Override
	protected Integer compute() {
		int sum = 0;
		// 当end-start的值小于MAX时候,开始打印
		if ((end - start) < MAX) {
			for (int i = start; i < end; i++) {
				sum += arr[i];
			}
			return sum;
		} else {
			System.err.println("=====任务分解======");
			// 将大任务分解成两个小任务
			int middle = (start + end) / 2;
			SumTask left = new SumTask(arr, start, middle);
			SumTask right = new SumTask(arr, middle, end);
			// 并行执行两个小任务
			left.fork();
			right.fork();
			// 把两个小任务累加的结果合并起来
			return left.join() + right.join();
		}
	}

}

public class ForkJoinPoolTest2 {

	public static void main(String[] args) throws Exception {
		int arr[] = new int[100];
		Random random = new Random();
		int total = 0;
		// 初始化100个数字元素
		for (int i = 0; i < arr.length; i++) {
			int temp = random.nextInt(100);
			// 对数组元素赋值,并将数组元素的值添加到total总和中
			total += (arr[i] = temp);
		}
		System.out.println("初始化时的总和=" + total);
		// 创建包含Runtime.getRuntime().availableProcessors()返回值作为个数的并行线程的ForkJoinPool
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		// 提交可分解的PrintTask任务
		Future<integer> future = forkJoinPool.submit(new SumTask(arr, 0,
				arr.length));
		System.out.println("计算出来的总和=" + future.get());
		// 关闭线程池
		forkJoinPool.shutdown();
	}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值