Loading

线程常用问题

1、线程创建方式

  • 继承Thread

    class MyThread extends Thread{
        @overwrite
        public void run(){
            
        }
    }
    
  • 实现Runable

    class MyRunable implements Runable{
        @overwrite
        public void run(){
            
        }
    }
    class Test1{
        public static void main(String[] args) {
            MyRunable runable = new MyRunable();
            new Thread(runable,"aaa").start();
        }
    }
    
  • 实现Callable

    public class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            return null;
        }
    }
    
    class Test{
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            MyCallable myCallable = new MyCallable();
            FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
            new Thread(futureTask,"bb").start();
            System.out.println(futureTask.get());
    
    
        }
    }
    

    总结:

    1、在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成, 当主线程将来需要时,就可以通过 Future对象获得后台作业的计算结果或者执行状态
    2、一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果
    3、仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。 get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
    4、结果只计算一次

2、JUC常用辅助类

  • CountDownLatch: 减少计数
  • CyclicBarrier: 循环栅栏
  • Semaphore: 信号灯

CountDownLatch 减少计数

例:等班里六人走后关门

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "离开教室");
                countDownLatch.countDown();

            }, "同学" + i).start();
        }
        countDownLatch.await();
        System.out.println("全部结束,关上教室");
    }
}

CyclicBarrier: 循环栅栏

例:集七龙珠召唤神龙

public class CyclicBarrierDemo {
    private static final int NUM = 7;

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM, () -> {
            System.out.println("集齐" + NUM + "颗龙珠,召唤神龙");
        });
        //开始集龙珠
        for (int i = 1; i <= 7; i++) {
            int num = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集到" + num + "号龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, "龙珠" + String.valueOf(i) + "号").start();
        }
    }
}

Semaphore 信号灯

例:六辆车抢三个车位

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "找到车位");
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName() + "离开车位-----");
                    semaphore.release();
                }
            }, "车辆" + i).start();
        }
    }
}

3、读写锁

读锁:共享锁

写锁:独占锁

public class MyCache {
    private volatile Map<String, Object> map = new HashMap();
    ReadWriteLock rWLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        rWLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始写:" + key);
            TimeUnit.SECONDS.sleep(1);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写完:" + key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rWLock.writeLock().unlock();
        }
    }

    public Object get(String key) {
        Object result = null;
        rWLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "开始获取数据:" + key);
            TimeUnit.SECONDS.sleep(1);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "获取数据完毕:" + key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rWLock.readLock().unlock();
        }
        return result;
    }
}

class Test {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 1; i <= 10; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(String.valueOf(num), String.valueOf(num));
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i <= 10; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(String.valueOf(num));
            }, String.valueOf(i)).start();
        }

    }
}

4、BlockingQueue阻塞队列

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
        //第一组
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        System.out.println(blockingQueue.element());
        //System.out.println(blockingQueue.add("x"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //System.out.println(blockingQueue.remove());
        //第二组
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.offer("x"));
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //第三组
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //blockingQueue.put("x");
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //第四组
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.offer("a", 3L, TimeUnit.SECONDS));
    }
}

5、ThreadPool 线程池

创建线程池方式

  • newCachedThreadPool 根据性能调整线程池数
  • newFixedThreadPool 固定线程池数
  • newSingleThreadExecutor 单线程
  • ThreadPoolExecutor 自定义线程池
    • 七大参数

      o corePoolSize 线程池的核心线程数
      o maximumPoolSize 能容纳的最大线程数
      o keepAliveTime 空闲线程存活时间
      o unit 存活的时间单位
      o workQueue 存放提交但未执行任务的队列
      o threadFactory 创建线程的工厂类
      o handler 等待队列满后的拒绝策略
      
    • 四大策略

      CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用
      线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由
      于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效
      率上必然的损失较大
      AbortPolicy: 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常
      信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执
      行流程,影响后续的任务执行。
      DiscardPolicy: 直接丢弃,其他啥都没有
      DiscardOldestPolicy: 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞
      队列 workQueue 中最老的一个任务,并将新任务加入
      
public class ThreadPoolDemo {
    public static void main(String[] args) {
        //1、单线程
        ExecutorService threadPool1 = Executors.newSingleThreadExecutor();
        //2、固定线程
        ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
        //3、根据性能创建线程
        ExecutorService threadPool3 = Executors.newCachedThreadPool();
        //4、自定义线程
        ThreadPoolExecutor threadPool4 = new ThreadPoolExecutor(
                5,
                10,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        for (int i = 0; i < 15; i++) {
            threadPool4.execute(() -> {
                System.out.println(Thread.currentThread().getName() + "执行");
            });
        }
//        threadPool1.shutdown();
//        threadPool2.shutdown();
//        threadPool3.shutdown();
        threadPool4.shutdown();
    }
}

原理

image

6、Fork/Join

1、任务分割:首先 Fork/Join 框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
2、执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。

在 Java 的 Fork/Join 框架中,使用两个类完成上述操作
3、• ForkJoinTask:我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集成 ForkJoinTask 类,只需要继承它的子类, Fork/Join 框架提供了两个子类:
	a.RecursiveAction:用于没有返回结果的任务
	b.RecursiveTask:用于有返回结果的任务
• ForkJoinPool:ForkJoinTask 需要通过 ForkJoinPool 来执行
• RecursiveTask: 继承后可以实现递归(自己调自己)调用的任务Fork/Join 框架的实现原理
ForkJoinPool 由 ForkJoinTask 数组和 ForkJoinWorkerThread 数组组成,
ForkJoinTask 数组负责将存放以及将程序提交给 ForkJoinPool,而
ForkJoinWorkerThread 负责执行这些任务。
  • Fork 方法的实现原理: 当我们调用 ForkJoinTask 的 fork 方法时,程序会把任务放在 ForkJoinWorkerThread 的 pushTask 的 workQueue 中,异步地执行这个任务,然后立即返回结果

    public final ForkJoinTask<V> fork() {
    		Thread t;
    		if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
    			((ForkJoinWorkerThread)t).workQueue.push(this);
    		else
    			ForkJoinPool.common.externalPush(this);
    		return this;
    	}
    

    pushTask 方法把当前任务存放在 ForkJoinTask 数组队列里。然后再调用
    ForkJoinPool 的 signalWork()方法唤醒或创建一个工作线程来执行任务。代
    码如下:

    final void push(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a; ForkJoinPool p;
            int b = base, s = top, n;
            if ((a = array) != null) {    // ignore if queue removed
                int m = a.length - 1;     // fenced write for task visibility
                U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
                U.putOrderedInt(this, QTOP, s + 1);
                if ((n = s - b) <= 1) {
                    if ((p = pool) != null)
                        p.signalWork(p.workQueues, this);
                }
                else if (n >= m)
                    growArray();
            }
        }
    
  • join 方法原理:Join 方法的主要作用是阻塞当前线程并等待获取结果。让我们一起看看
    ForkJoinTask 的 join 方法的实现,代码如下:

    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        return getRawResult();
    }
    
    private int doJoin() {
        int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
        return (s = status) < 0 ? s :
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
            (w = (wt = (ForkJoinWorkerThread)t).workQueue).
            tryUnpush(this) && (s = doExec()) < 0 ? s :
            wt.pool.awaitJoin(w, this, 0L) :
            externalAwaitDone();
    }
    
    final int doExec() {
        int s; boolean completed;
        if ((s = status) >= 0) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                return setExceptionalCompletion(rex);
            }
            if (completed)
                s = setCompletion(NORMAL);
        }
        return s;
    }
    

案例:1+2+3+4......10000,每1000个任务切分一次任务

public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyTask task = new MyTask(0, 10_000);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(task);
        Integer result = forkJoinTask.get();
        System.out.println("结果为:" + result);
        forkJoinPool.shutdown();
    }

}

class MyTask extends RecursiveTask<Integer> {
    private static final int NUM = 1000;
    private int begin;
    private int end;
    private int result;

    public MyTask(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        System.out.println("开始:" + begin + "------------" + end + "开始累加");
        if ((end - begin) <= NUM) {
            for (int i = begin; i <= end; i++) {
                result += i;
            }
        } else {
            int middle = (end + begin) / 2;
            MyTask myTask1 = new MyTask(begin, middle);
            MyTask myTask2 = new MyTask(middle + 1, end);
            myTask1.fork();
            myTask2.fork();

            result = myTask1.join() + myTask2.join();

        }
        return result;
    }
}

输出结果
开始:0------------10000开始累加
开始:0------------5000开始累加
开始:5001------------10000开始累加
开始:0------------2500开始累加
开始:5001------------7500开始累加
开始:5001------------6250开始累加
开始:5001------------5625开始累加
开始:5626------------6250开始累加
开始:6251------------7500开始累加
开始:0------------1250开始累加
开始:6251------------6875开始累加
开始:6876------------7500开始累加
开始:7501------------10000开始累加
开始:0------------625开始累加
开始:7501------------8750开始累加
开始:7501------------8125开始累加
开始:1251------------2500开始累加
开始:2501------------5000开始累加
开始:1251------------1875开始累加
开始:8126------------8750开始累加
开始:626------------1250开始累加
开始:3751------------5000开始累加
开始:8751------------10000开始累加
开始:8751------------9375开始累加
开始:9376------------10000开始累加
开始:4376------------5000开始累加
开始:1876------------2500开始累加
开始:2501------------3750开始累加
开始:2501------------3125开始累加
开始:3751------------4375开始累加
开始:3126------------3750开始累加
结果为:50005000
posted @ 2022-04-08 15:58  mainwoodの慢屋  阅读(63)  评论(0编辑  收藏  举报