• JAVA多线程(MultiThread)的各种用法


    多线程简单应用

    单线程的问题在于,一个线程每次只能处理一个任务,如果这个任务比较耗时,那在这个任务未完成之前,其它操作就会无法响应。

    如下示例中,点击了“进度1”后,程序界面就没反应了,强行拖动容器后变成了“无响应”。

    使用线程之前

     

     其原因是这段循环代码处于独占状态,这里并没有给其它代码执行的机会,包括接收界面更新的后台消息,导致应用程序处于一个假死的状态。只有等这个循环退出后,才可以进行其它的操作。

    1. for (double i = 0; i < 100.0; i++) {
    2. try {
    3. Thread.sleep(20);
    4. } catch (InterruptedException e) {
    5. throw new RuntimeException(e);
    6. }
    7. progressBar0.setProgress(i);
    8. }

     接下来,我们把这个循环代码放到一个单独的线程中,这样的话就可以不用占用主线程,等这个线程执行完了,再通知主线程更新界面。

    代码改动后,变成这样:

    1. Executors.newSingleThreadExecutor().submit(() -> {
    2. for (double i = 0; i < 100.0; i++) {
    3. try {
    4. Thread.sleep(50);
    5. } catch (InterruptedException e) {
    6. throw new RuntimeException(e);
    7. }
    8. final double p = i;
    9. Platform.runLater(() -> {
    10. progressBar0.setProgress(p / 100);
    11. });
    12. }
    13. });

     这里使用了Excutors工具类(JDK1.5以后引入),使用方法和new Thread()大同小异,只不过Executors里默认使用线程池,可以降低不必要的线程开销。

    运行效果:

     

    使用线程之后

     多线程的数据同步

    我们对两个进度条进行计数,最上边的标签显示总和,下边一个标签对应一个进度条。结果中可以发现,两个进度条的计数是正确的,但总和却错了。

     

     我们看下进度1的代码(进度2的代码相同,仅仅标签和进度条的id不一样):

    1. Executors.newSingleThreadExecutor().submit(() -> {
    2. for (double i = 0; i <= 100.0; i++) {
    3. var c = count;
    4. try {
    5. Thread.sleep(20);
    6. } catch (InterruptedException e) {
    7. throw new RuntimeException(e);
    8. }
    9. c++;
    10. count = c;
    11. final double p = i;
    12. Platform.runLater(() -> {
    13. progressBar0.setProgress(p / 100);
    14. label0.setText(count + "");
    15. label1.setText(p + "");
    16. });
    17. }
    18. });

    count是个全局变量,两个线程同时向这个变量赋值。因为中间有一个sleep()操作,所以在取得count的值之后,并不能确保count没有发生改变,如果得到count是的时候是1,等sleep()完后,可能是3,而此时还在1的基础上累加,最后将2再赋给count,于是误差就产生了。

    为了解决这个问题,我们可以在累加完之前,不允许别的线程去修改count的值,大家共同拥有同一把钥匙,我拿钥匙了,别的就在外边等着。

     我们给计数部分的代码,套一层synchronized 块,此时HelloController.this就是同步锁的钥匙,哪个线程先执行到这个语句,就代表着拿到了钥匙,然后继续执行后边的语句,别的线程则需要停留在synchronized 的行,直到前边的线程已经退出语句块。

    1. synchronized (HelloController.this) {
    2. var c = count;
    3. try {
    4. Thread.sleep(50);
    5. } catch (InterruptedException e) {
    6. throw new RuntimeException(e);
    7. }
    8. c++;
    9. count = c;
    10. }

     加上同步操作后,我们再下运行效果:

    线程同步

     原子变量

    对于计数器这东西,我们可以使用原子变量。这样,我们就不需要在自增自减上加锁,可以提升代码的性能。

    定义一个整型的原子变量:

    private AtomicInteger count = new AtomicInteger(0);

     修改同步代码:

    1. Executors.newSingleThreadExecutor().submit(() -> {
    2. for (double i = 0; i < 100.0; i++) {
    3. try {
    4. Thread.sleep(50);
    5. } catch (InterruptedException e) {
    6. throw new RuntimeException(e);
    7. }
    8. var c = count.incrementAndGet();
    9. final double p = i;
    10. Platform.runLater(() -> {
    11. progressBar0.setProgress((p + 1) / 100);
    12. label0.setText(count + "");
    13. label1.setText((p + 1) + "");
    14. });
    15. }
    16. });

     关于死锁

    在使用多级锁的时候,容易发生死锁。

    死锁示例:

    1. private Object locker1 = new Object();
    2. private Object locker2 = new Object();
    3. @FXML
    4. protected void onDeadLockButtonClick() {
    5. new Thread(() -> {
    6. synchronized (locker1) {
    7. System.out.println("Thread 1 in locker1");
    8. try {
    9. Thread.sleep(1000);
    10. } catch (InterruptedException e) {
    11. throw new RuntimeException(e);
    12. }
    13. synchronized (locker2) {
    14. System.out.println("Thread 1 in locker2");
    15. }
    16. }
    17. }).start();
    18. new Thread(() -> {
    19. synchronized (locker2) {
    20. System.out.println("Thread 2 in locker2");
    21. try {
    22. Thread.sleep(1000);
    23. } catch (InterruptedException e) {
    24. throw new RuntimeException(e);
    25. }
    26. synchronized (locker1) {
    27. System.out.println("Thread 2 in locker1");
    28. }
    29. }
    30. }).start();
    31. }

     运行结果为,点击死锁后,两个线程都不再往下执行,处在锁死状态。

     线程重用

    按照计算机操作系统原理,创建线程是有一定的开销的。不停地创建销毁线程,会造成系统性能下降,所以尽量创建少的进程,并反复加以利用。原理是创建一个工作进程,并让其进入等待状态,在需要的时候发出通知,让工作进程再次进入工作。

    实现代码:

    1. private Object locker3 = new Object();
    2. @FXML
    3. protected void onReUseThreadButtonClick() {
    4. synchronized (locker3) {
    5. locker3.notify();
    6. }
    7. }
    8. @FXML
    9. protected void onCreateThreadButtonClick() {
    10. new Thread(() -> {
    11. logs.appendText("Thread created...\n");
    12. synchronized (locker3) {
    13. while (true) {
    14. try {
    15. logs.appendText("Thread waiting...\n");
    16. locker3.wait();
    17. logs.appendText("All right. Next...\n");
    18. Thread.sleep(300);
    19. } catch (InterruptedException e) {
    20. throw new RuntimeException(e);
    21. }
    22. }
    23. }
    24. }).start();
    25. }

     运行效果:

     线程的汇集操作

    可以将一些线程并行计算的结果,汇总到同一个线程。假如电脑的CPU核心数和线程数较多,可以拆分对应个数的线程进行并行计算,再将结果汇集到主线程。原理使用Thread.join()方法。

    代码:

    1. new Thread(() -> {
    2. Thread t1 = new Thread(() -> {
    3. try {
    4. Thread.sleep(500);
    5. } catch (InterruptedException e) {
    6. throw new RuntimeException(e);
    7. }
    8. Platform.runLater(() -> {
    9. logs.appendText("t1 exited....\n");
    10. });
    11. });
    12. t1.start();
    13. Thread t2 = new Thread(() -> {
    14. try {
    15. Thread.sleep(1000);
    16. } catch (InterruptedException e) {
    17. throw new RuntimeException(e);
    18. }
    19. Platform.runLater(() -> {
    20. logs.appendText("t2 exited....\n");
    21. });
    22. });
    23. t2.start();
    24. try {
    25. t1.join();
    26. t2.join();
    27. } catch (InterruptedException e) {
    28. throw new RuntimeException(e);
    29. }
    30. Platform.runLater(() -> {
    31. logs.appendText("current exited....\n");
    32. });
    33. }).start();

     运行效果:

     

  • 相关阅读:
    java中静态属性和静态方法的使用
    Spring MVC 中文文档
    15、wpf之button样式小记
    你的Web3域名,价值究竟何在?
    mpu6050移植DMP库
    2024年抖店的市场已经饱和,小白不适合入局了?真实现状如下
    卷积神经网络图片放大,神经网络输入图片大小
    【STM32/FreeRTOS】SysTick定时器及FreeRTOS系统节拍
    改善游戏体验:数据分析与可视化的威力
    linux下的c/c++动静态库
  • 原文地址:https://blog.csdn.net/icoolno1/article/details/122720847