java线程知识

java线程间的通讯方式

多个线程按顺序执行

使用Thread类里面的join()方法。
例子:B 在 A 全部打印 完后再开始打印。

private static void demo2() {
    Thread A = new Thread(new Runnable() {
        @Override
        public void run() {
            printNumber("A");
        }
    });
    Thread B = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("B 开始等待 A");
            try {
                A.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            printNumber("B");
        }
    });
    B.start();
    A.start();
}

让两个线程按照指定方式有序交叉运行

利用 object.wait() 和 object.notify(),这两个方法要在synchronized代码块里执行。

/**
 * A 1, B 1, B 2, B 3, A 2, A 3
 */
private static void demo3() {
    Object lock = new Object();
    Thread A = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("A 1");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A 2");
                System.out.println("A 3");
            }
        }
    });
    Thread B = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("B 1");
                System.out.println("B 2");
                System.out.println("B 3");
                lock.notify();
            }
        }
    });
    A.start();
    B.start();
}

D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的

使用CountdownLatch里面的await() 和 countDown()方法。
CountdownLatch基本用法:
1、创建一个计数器,设置初始值,CountdownLatch countDownLatch = new CountDownLatch(2);
2、在 等待线程 里调用 countDownLatch.await() 方法,进入等待状态,直到计数值变成 0;
3、在 其他线程 里,调用 countDownLatch.countDown() 方法,该方法会将计数值减小 1;
4、当 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程 里的 countDownLatch.await() 立即退出,继续执行下面的代码。

private static void runDAfterABC() {
    int worker = 3;
    CountDownLatch countDownLatch = new CountDownLatch(worker);
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("D is waiting for other three threads");
            try {
                countDownLatch.await();
                System.out.println("All done, D starts working");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
    for (char threadName='A'; threadName <= 'C'; threadName++) {
        final String tN = String.valueOf(threadName);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(tN + " is working");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(tN + " finished");
                countDownLatch.countDown();
            }
        }).start();
    }
}

等到三线程都准备好后,再一起执行

使用CyclicBarrier
基本用法:
1、先创建一个公共 CyclicBarrier 对象,设置 同时等待 的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
2、这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await(); 即可开始等待别人;
3、当指定的 同时等待 的线程数都调用了 cyclicBarrier.await();时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行。

private static void runABCWhenAllReady() {
    int runner = 3;
    CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
    final Random random = new Random();
    for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
        final String rN = String.valueOf(runnerName);
        new Thread(new Runnable() {
            @Override
            public void run() {
                long prepareTime = random.nextInt(10000) + 100;
                System.out.println(rN + " is preparing for time: " + prepareTime);
                try {
                    Thread.sleep(prepareTime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println(rN + " is prepared, waiting for others");
                    cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(rN + " starts running"); // 所有运动员都准备好了,一起开始跑
            }
        }).start();
    }
}

把任务执行结果回传给主线程

使用Callable+FutureTask,但FutureTask的get()方法会阻塞主线程。
例子:让子线程去计算从 1 加到 100,并把算出的结果返回到主线程。

private static void doTaskWithResultInWorker() {
    Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            System.out.println("Task starts");
            Thread.sleep(1000);
            int result = 0;
            for (int i=0; i<=100; i++) {
                result += i;
            }
            System.out.println("Task finished and return result");
            return result;
        }
    };
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    new Thread(futureTask).start();
    try {
        System.out.println("Before futureTask.get()");
        System.out.println("Result: " + futureTask.get());
        System.out.println("After futureTask.get()");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

备注:由于FutureTask的get()方法会阻塞主线程,因此应该把FutureTask放到线程池里面执行。

线程池 + FutureTask执行多任务计算

用线程池+ FutureTask将1个查询拆分成多个小查询。

public class Test {
 //线程池最好作为全局变量, 若作为局部变量记得用完后shutdown()
 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder(.setNameFormat("thread-start-runner-%d").build();
 ExecutorService taskExe= new ThreadPoolExecutor(10,20,800L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(100),namedThreadFactory);
 
 int count=0;
 @Test
 public void test(String[] args) {
  
  //任务列表
  List<FutureTask<Integer>> taskList=new ArrayList<FutureTask<Integer>>();
  for(int i=0;i<100;i++){
   //创建100个任务放入【任务列表】
   FutureTask<Integer> futureTask=new FutureTask<Integer>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
     return 1;
    }
   });
   //执行的结果装回原来的FutureTask中,后续直接遍历集合taskList来获取结果即可
   taskList.add(futureTask);
   taskExe.submit(futureTask);
  }
  //获取结果
  try{
   for(FutureTask<Integer> futureTask:taskList){
                count+=futureTask.get();
            }
  } catch (InterruptedException e) {
   logger.error("线程执行被中断",e);
  } catch (ExecutionException e) {
   logger.error("线程执行出现异常",e);
  }
  //关闭线程池
  taskExe.shutdown();
  //打印: 100
  System.out.println(count);
 }
}

子线程出的异常抛不出的情况

submit(Runnable task)提交任务的方式 ,是存在“隐患”的:FutureTask内部的run()代码块会把异常给吞进去。
正确做法:FutureTask内部的run()代码块用try-catch来截获抛出的异常,然后通过get()方法获取到异常。

线程池submit()提交任务的方法和execute()执行任务的方法有什么区别

submit()方法,当子线程执行异常时不会中断退出,而会一直往下执行,相当于把异常信息吞掉了。
execute()方法,当子线程执行异常时会中断退出,打印异常信息。

解决异常被吞的方法

把 submit 返回的 Future 放到了 List 中,随后遍历 List 来捕获所有任务的异常。


List<Future> tasks = IntStream.rangeClosed(1, 10).mapToObj(i -> threadPool.submit(() -> {
    if (i == 5) throw new RuntimeException("error");
    log.info("I'm done : {}", i);
})).collect(Collectors.toList());

tasks.forEach(task-> {
    try {
        task.get();
    } catch (Exception e) {
        log.error("Got exception", e);
    }
});
解决execute()打印异常信息没有统一的错误日志格式

在声明线程池时自定义线程池的未捕获异常处理程序:

new ThreadFactoryBuilder()
  .setNameFormat(prefix+"%d")
  .setUncaughtExceptionHandler((thread, throwable)-> log.error("ThreadPool {} got exception", thread, throwable))
  .get()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值