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

多线程简单应用

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

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

使用线程之前

 

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

        for (double i = 0; i < 100.0; i++) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            progressBar0.setProgress(i);

        }

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

代码改动后,变成这样:

        Executors.newSingleThreadExecutor().submit(() -> {
            for (double i = 0; i < 100.0; i++) {
                
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                   
                final double p = i;
                Platform.runLater(() -> {
                    progressBar0.setProgress(p / 100);
                    
                });
            }
        });

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

运行效果:

 

使用线程之后

 多线程的数据同步

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

 

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

        Executors.newSingleThreadExecutor().submit(() -> {
            for (double i = 0; i <= 100.0; i++) {
                    var c = count;
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    c++;
                    count = c;
                final double p = i;
                Platform.runLater(() -> {
                    progressBar0.setProgress(p / 100);
                    label0.setText(count + "");
                    label1.setText(p + "");
                });
            }
        });

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

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

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

                synchronized (HelloController.this) {
                    var c = count;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    c++;
                    count = c;
                }

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

线程同步

 原子变量

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

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

private AtomicInteger count = new AtomicInteger(0);

 修改同步代码:

Executors.newSingleThreadExecutor().submit(() -> {
            for (double i = 0; i < 100.0; i++) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                var c = count.incrementAndGet();

                final double p = i;
                Platform.runLater(() -> {
                    progressBar0.setProgress((p + 1) / 100);
                    label0.setText(count + "");
                    label1.setText((p + 1) + "");
                });
            }
        });

 关于死锁

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

死锁示例:

    private Object locker1 = new Object();
    private Object locker2 = new Object();

    @FXML
    protected void onDeadLockButtonClick() {
        new Thread(() -> {
            synchronized (locker1) {
                System.out.println("Thread 1 in locker1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker2) {
                    System.out.println("Thread 1 in locker2");

                }
            }
        }).start();

        new Thread(() -> {
            synchronized (locker2) {
                System.out.println("Thread 2 in locker2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (locker1) {
                    System.out.println("Thread 2 in locker1");

                }
            }
        }).start();
    }

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

 线程重用

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

实现代码:

    private Object locker3 = new Object();

    @FXML
    protected void onReUseThreadButtonClick() {
        synchronized (locker3) {
            locker3.notify();
        }
    }

    @FXML
    protected void onCreateThreadButtonClick() {
        new Thread(() -> {
            logs.appendText("Thread created...\n");
            synchronized (locker3) {
                while (true) {
                    try {
                        logs.appendText("Thread waiting...\n");
                        locker3.wait();
                        logs.appendText("All right. Next...\n");
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }

 运行效果:

 线程的汇集操作

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

代码:

        new Thread(() -> {
            Thread t1 = new Thread(() -> {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                Platform.runLater(() -> {
                    logs.appendText("t1 exited....\n");
                });
            });
            t1.start();
            Thread t2 = new Thread(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                Platform.runLater(() -> {
                    logs.appendText("t2 exited....\n");
                });
            });
            t2.start();
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            Platform.runLater(() -> {
                logs.appendText("current exited....\n");
            });
        }).start();

 运行效果:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值