JAVA多线程学习心得(二)

本文介绍了Fork/Join框架的原理与应用,通过实例演示如何利用该框架将大任务分解为小任务并行处理,最后汇总结果。还讨论了线程安全关键字volatile与synchronized的区别,以及Java锁的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

github上新上传了一个fork/join的多线程框架, 项目地址:https://github.com/jndf/multithreading-pratice

需要的朋友可以看看,代码如有错误,请多提出指正意见。

走过路过,麻烦给点个星,好人一生平安。

 

接着上篇文章:

Fork/Join框架

这个框架主要是应用与一些可以多分支同时运行的操作,最后将每个线程的运行结果合在一起,作为总结果返回。

 

 依旧是具体看注释:

/**
 * 写一个Fork/join 多线程框架
 *
 */
public class Demo5 {
    /**
     * ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。
     * 它提供在任务中执行 fork() 和 join() 操作的机制,通常情况下我们不需要直接继承 ForkJoinTask 类,而只需要继承它的子类,Fork/Join 框架提供了以下两个子类:
     *
     * RecursiveAction :用于没有返回结果的任务。
     * RecursiveTask :用于有返回结果的任务。
     * ForkJoinPool :ForkJoinTask 需要通过 ForkJoinPool 来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。
     * 当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
     */

    //线程
    static  class ForkJoinTest1 implements Callable{
        /**
         *  执行线程的代码块
         */
        @Override
        public Object call() {
            SumTest sumTest1 = new SumTest(1,2);
            SumTest sumTest2 = new SumTest(3,4);
            /**
             * fork将多个线程分流运算
             */
            sumTest1.fork();
            sumTest2.fork();
            /**
             * join将多个线程结果获取,这样就达到了forkjoin的把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的思想
             */
            return (int)sumTest1.join()+(int)sumTest2.join();
        }
    }

    //测试类
     static  class SumTest extends RecursiveTask{
        int startNum;
        int endNum;

        public SumTest(int startNum,int endNum){
            this.startNum = startNum;
            this.endNum = endNum;
        }

        //计算加法
        @Override
        protected Object compute() {
            return (int)startNum+endNum;
        }
    }

    public static void main(String[] args) {
        ForkJoinPool ForkJoinPool = new ForkJoinPool();

        ForkJoinTest1 forkJoinTest1 = new ForkJoinTest1();

        Future sum = ForkJoinPool.submit(forkJoinTest1);
        try {
            System.out.println(sum.get());
        }catch(InterruptedException e) {

        }catch(ExecutionException e) {

        }
    }
}

 

接下来是一些理论知识:

线程安全关键字:

当多线程操作时,有可能出现如同数据库事务隔离问题(脏读,幻读等)相似的问题,其实应该说是,大量执行数据库实务操作,其实就是执行多线程,而出现上述问题的原因,则是没有确保事务的原子和隔离性,这也是线程不安全的原因;

因此线程安全,就是指让线程们有序的去访问对象资源,进行操作,像 削弱之王宫本武藏说的一样:不要急,一个一个来~;

在这里提到volatile与synchronized两个线程安全关键字;

主要说说他们的区别:volatile使用场景较为苛刻,因为它主要保证的是,被线程操作的变量实时可见,说白了就是很多时候线程不安全是因为一个线程改变了数据,在改变数据的时候,另一个线程也加入进来改变数据的行列,这样数据就会出现问题。

而volatile可以让数据的改变实时可见,只要一个数据被改变了,就会马上把这个数据修改后的值实时通知给所有线程,这样就减少了数据结果的不确定性;

所以他的使用场景也是多线程改变同一个数据这种情况,个人理解比较适合fork/join,欢迎大神指教。

而synchronized则是将线程的安全考虑的更全面,添加了synchronized的资源只能被线程有序访问,其他的就等着吧,因此与等待通知机制合起来使用效果更佳。而且线程访问完synchronized修饰的对象,资源会自动释放,省时省力,这个关键字的应用也比较普遍。

然后随着使用,synchronized就出现了这么一个问题,并因此引申出了锁。

我们往下看:

JAVA锁的应用场景:

synchronized只是保证了线程安全,但是牺牲了性能,在某一个时刻cpu只会运行一个线程,而其他线程都进入阻塞状态,
那么当正在运行的线程由于要等待io,或者其他原因导致占用线程时间过长,将会影响到线程的使用效率。
因此使用锁这一概念,来将线程效率根据不同应用场景,来进行相应的实现。
有一个使用场景:比如可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock也可以办到。

 

具体关于锁的详解看下一篇文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值