多线程?不用怕,大不了多学几遍 - 工具类

多线程?不用怕,大不了多学几遍 - 工具类

好久没更新了,有2个多月了吧。

准备继续巩固下知识,先更新一些关于java多线程的学习笔记。之前也写过 java多线程进阶学习1 java多线程进阶学习2

但是总觉得掌握的不扎实,所以重新来过,好事多磨嘛。😄


本篇准备学习的内容:

  • Fork/Join
  • 并发工具类
    • CountDownLatch
    • CyclicBarrier
    • Semaphore
    • Exchange
  • Callable、Future和FutureTask

tip:本片偏向的应用,底层源码后在后面的博客更新

Fork/Join

Fork/Join 是分而治之。

规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解。内部可以理解为递归调用。

客官,先给您上菜

/**
 * @author mark
 *产生整形数组
 */
public class MakeArray {
    //数组长度
    public static final int LENGTH  = 1000000;

    public static int[] makeArray() {

        //new一个随机数发生器
        Random r = new Random();
        int[] result = new int[LENGTH];
        for(int i=0; i<LENGTH; i++){
            //用随机数填充数组
            result[i] =  r.nextInt(LENGTH*3);
        }
        return result;
    }
}


/**
 * @Author: jimmy
 * @Date: 2021/4/17 20:19
 * @Description:
 *  使用fork join 实现数字相加
 *  这里仅展示如何使用fork jion
 *  关于越界等其他问题不进行优化
 */
public class SumDemo {

    private static class SumTask extends RecursiveTask<Integer> {
        // 阈值
        private final static int THRESHOLD = MakeArray.LENGTH/10;
        //表示我们要实际统计的数组
        private int[] data;
        //开始统计的下标
        private int fromIndex;
        //统计到哪里结束的下标
        private int toIndex;

        public SumTask(int[] data, int fromIndex, int toIndex) {
            this.data = data;
            this.fromIndex = fromIndex;
            this.toIndex = toIndex;
        }

        /**
         * 计算方法
         * @return
         *   1、规模没有超过阈值则直接计算
         *   2、规模超过阈值则拆分为子任务
         *          invokeAll
         *          join
         */
        @Override
        protected Integer compute() {
            if(toIndex-fromIndex < THRESHOLD) {
                int count = 0;
                for(int i=fromIndex; i<=toIndex; i++) {
                    count = count + data[i];
                }
                return count;
            }else {
                // 拆分逻辑 fromIndex....mid....toIndex
                int mid = (fromIndex+toIndex)/2;
                SumTask left = new SumTask(data, fromIndex, mid);
                SumTask right = new SumTask(data,mid+1, toIndex);
                invokeAll(left, right);
                return left.join() + right.join();
            }
        }
    }

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

        // 1、创建 forkJoinPool实例
        ForkJoinPool pool = new ForkJoinPool();
        int[] data = MakeArray.makeArray();
        // 2、创建任务
        SumTask task = new SumTask(data,0,data.length-1);
        // 3、执行任务
        pool.invoke(task);
        // 4、获取结果
        Integer result = task.join();
        System.out.println(result);
    }
}

总结下fork/join

首先,创建一个自己的任务执行类(继承RecursiveTask<T>),重写处理方法,这里面要注意阈值的处理及子任务的划分,当然这个要看具体的需求场景。这里俩个重要的方法invokeAll RecursiveTask.join()。用来将任务执行和获取任务执行结果

然后我们可以进行fork/join的范式调用

1、创建 forkJoinPool实例

2、创建任务

3、执行任务

4、获取结果

并发工具类

CountDownLatch

CyclicBarrier

Semaphore

Exchange

…… …… …… …… …… …… …… …… …… ……∞’ …… …… …… …… …… …… …… …… …… ……∞’ …… …… …… …… …… ……

这里在业务开发中相对比较常用的可能是CountDownLatchCyclicBarrier

现在我们想想有一场短跑比赛。一共10个选手参赛。(这里有11个线程,10个选手线程和一个开始比赛线程)

只有当所有选手都到场的时候才能开始比赛。这个时候就可以使用CountDownLatch 。开始比赛线程必须等10个选手线程都完成才能开始。

现在换一个场景,玩moba类游戏,dota2、LOL啥的,都需要一个匹配,匹配成功后开始游戏。假设有10位玩家。匹配过程中所有玩家都必须加载完成才能继续进行后面的比赛。那么这里就可以使用CyclicBarrier。注意这里只有10个匹配线程,每个线程在加载完成的时候阻塞,当10个匹配线程都加载完成后,一起进入后面的比赛。

废话一堆,show me code

  • CountDownLatch
package concurrentClass.countdown;

import java.util.concurrent.CountDownLatch;

public class CutDownLatchTest {

    public static void main(String[] args) {

        final CountDownLatch latch = new CountDownLatch(2); // 构造方法,放入2个线程

        new Thread(){
            public void run() {
                try {
                    System.out.println("选手子线程"+Thread.currentThread().getName()+"选手准备入场");
                    Thread.sleep(3000);
                    System.out.println("选手子线程"+Thread.currentThread().getName()+"选手到场");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();

        new Thread(){
            public void run() {
                try {
                    System.out.println("选手子线程"+Thread.currentThread().getName()+"选手准备入场");
                    Thread.sleep(3000);
                    System.out.println("选手子线程"+Thread.currentThread().getName()+"选手到场");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        }.start();

        // 使用await()方法让主线程等待子线程执行完后在执行
        try {
            latch.await();
            System.out.println("选手到齐,开始比赛");
        } catch (InterruptedException e) {

        }
    }
}

// 输出
选手子线程Thread-0选手准备入场
选手子线程Thread-1选手准备入场
选手子线程Thread-0选手到场
选手子线程Thread-1选手到场
选手到齐,开始比赛    
  • cyclicBarrier
package concurrentClass.cyclicBarrier;

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {

    static CyclicBarrier c = new CyclicBarrier(2);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("玩家" + Thread.currentThread().getName() + "加载完成");
                    Thread.sleep(1000);
                    c.await();
                    System.out.println("玩家" + Thread.currentThread().getName() + "时间:"
                            + System.currentTimeMillis() +"准备进入比赛");
                } catch (Exception e) {

                }
                System.out.println("阿杜跟");
            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("玩家" + Thread.currentThread().getName() + "加载完成");
                    Thread.sleep(3000);
                    c.await();
                    System.out.println("玩家" + Thread.currentThread().getName() + "时间:"
                            + System.currentTimeMillis() + "准备进入比赛");
                } catch (Exception e) {

                }
                System.out.println("哈撒剋");
            }
        }).start();
    }
}

// 输出
    
玩家Thread-1加载完成
玩家Thread-0加载完成
玩家Thread-1时间:1618666778562准备进入比赛
玩家Thread-0时间:1618666778562准备进入比赛
哈撒剋
阿杜跟
  • Semaphore

    控制同时访问某个特定资源的线程数量,用在流量控制

package concurrentClass.semaphore;

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

    public static void main(String[] args) {
        int N = 8;            //工人数
        Semaphore semaphore = new Semaphore(5); //机器数目
        for(int i=0;i<N;i++){
            new Worker(i,semaphore).start();
        }
    }

    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"占用一个机器在生产...");
                Thread.sleep(1000);
                System.out.println("工人"+this.num+"释放出机器");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 输出
工人0占用一个机器在生产...
工人1占用一个机器在生产...
工人2占用一个机器在生产...
工人3占用一个机器在生产...
工人4占用一个机器在生产...
工人1释放出机器
工人4释放出机器
工人3释放出机器
工人5占用一个机器在生产...
工人2释放出机器
工人7占用一个机器在生产...
工人0释放出机器
工人6占用一个机器在生产...
工人6释放出机器
工人5释放出机器
工人7释放出机器
  • Exchange

两个线程间的数据交换(这个我接触很少)

package concurrentClass.exchange;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Exchanger;

public class ExchangeDemo {
    private static final Exchanger<Set<String>> exchange 
    	= new Exchanger<Set<String>>();

    public static void main(String[] args) {

    	//第一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
            	Set<String> setA = new HashSet<String>();//存放数据的容器
                try {
                	/*添加数据
                	 * set.add(.....)
                	 * */
                	setA = exchange.exchange(setA);//交换set
                	/*处理交换后的数据*/
                } catch (InterruptedException e) {
                }
            }
        }).start();

      //第二个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
            	Set<String> setB = new HashSet<String>();//存放数据的容器
                try {
                	/*添加数据
                	 * set.add(.....)
                	 * set.add(.....)
                	 * */
                	setB = exchange.exchange(setB);//交换set
                	/*处理交换后的数据*/
                } catch (InterruptedException e) {
                }
            }
        }).start();
    }
}

Callable、Future和FutureTask

这套工具可以获取子线程的结果信息。

  • FutureTask可以理解为继承了 RunnableFuture接口的Callable包装类
  • Future是用来提供获取线程处理结果的接口
package future;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureDemo {
	
	/*实现Callable接口,允许有返回值*/
	private static class UseCallable implements Callable<Integer>{

		private int sum;
		@Override
		public Integer call() throws Exception {
			System.out.println("Callable子线程开始计算");
			Thread.sleep(2000);
			for(int i=0; i<300; i++) {
				sum = sum+i;
			}
			System.out.println("Callable子线程计算完成,结果="+sum);
			return sum;
		}

	}
	
	public static void main(String[] args) 
			throws InterruptedException, ExecutionException {
		
		UseCallable useCallable = new UseCallable();
		FutureTask<Integer> futureTask = new FutureTask<Integer>(useCallable);
		new Thread(futureTask).start();
		Random r = new Random();
		Thread.sleep(1000);
		if(r.nextBoolean()) {//随机决定是获得结果还是终止任务
			System.out.println("Get UseCallable result = "+futureTask.get());
		}else {
			System.out.println("中断计算");
			futureTask.cancel(true);
		}
		
	}
}


// 输出
Callable子线程开始计算
Callable子线程计算完成,结果=44850
Get UseCallable result = 44850
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值