学习多线程CAS及相关知识

本文详细介绍了Java多线程中的CAS实现自旋锁、ABA问题的处理、Callable接口的作用以及ReentrantLock、信号量和CountDownLatch组件的使用,为理解并发编程提供了一个全面的概述。
摘要由CSDN通过智能技术生成

书接上回, 上篇博客中总结了synchronized的原理和CAS的实现原子类, 我们将要继续学习CAS实现自旋锁, CAS中的ABA问题, Callable创建线程等等..

CAS实现自旋锁

首先我们来看一段伪代码:

public class SpinLock {
	// owner表示是哪个线程持有这把锁, 设为null表示当前线程没有加锁
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        // Thread.currentThread(): 获取当前线程的引用
        // 如果该先线程处于加锁状态, 就会返回false, 就会进入循环等待...
        while(!CAS(this.owner, null, Thread.currentThread())){	
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

CAS的ABA问题

通过比较寄存器和内存的值,通过这里的是否相等,来判定内存的值是否发生了改变.
如果内存的值变了, 就存在其它线程修改.
如果内存的值没变, 就没有别的线程修改, 后面进行修改就是安全的.

那么问题来了: 如果内存的值没变, 就一定没有别的线程修改吗?
这就是ABA问题… A->B->A
在这里插入图片描述
即使以上情况发生概率很小, 但它还是会发生的, 就需要我们去处理!

这个时候我们就引入了版本号, 通过版本号的值是否被修改, 来判断数据有没有修改.

Callable接口

Callable是一个interface, 相当于把线程封装成"返回值".
我们可以通过Callable创建线程:

public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 1000; i++) {
                    sum += i;
                }
                return sum;
            }
        };

        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);

        thread.start();
        // 如果call方法没执行完, 会进入阻塞等待.
        Integer ret = futureTask.get();
        System.out.println(ret);
    }

Runnable 能表示一个任务, 通过run方法, 返回void.
Callable 也能表示一个任务, 通过call方法, 返回一个具体的泛型参数.
如果在设计多线程的地方, 我们更看重过程, 推荐使用Runable,如果更看重结果, 更推荐使用Callable.
要注意的是, Callable不能直接作为Thread的构造方法参数,要引入FutureTask.
用FutureTask对象作为Thread的构造方法参数.

ReentrantLock

ReentrantLock也是与synchronized类似的, 都是一个加锁的组件.
但它要手动设置lock():加锁和unlock(): 解锁.
ReentrantLock特点:

  1. 提供tryLock方法进行加锁:
    对于lock方法, 加锁失败就进入阻塞等待.
    对于tryLock方法, 加锁失败就返回false或者在规定的等待时间中返回, 给我们提供了更多的操作空间.
  2. ReentrantLock有两种加锁模式, 可以工作在公平锁状态下, 也可以工作在非公平锁状态下.
  3. ReentrantLock也具有等待通知功能, 搭配Condition类来使用.要比synchronized的wait,notify方法功能更强.
    实际开发中, 多线程开发, 还是首选synchronized.

信号量Semaphone

信号量, 用来表示 "可用资源的个数". 本质上就是一个计数器.
我们申请一个资源, 计数器-1, 称为p操作.
我们释放一个资源, 计数器+1, 称为v操作.

我们利用代码来熟悉pv操作过程:

public static void main(String[] args) throws InterruptedException {
        // 申请4个可用资源
        Semaphore semaphore = new Semaphore(4);
        // 申请一个资源
        semaphore.acquire();
        System.out.println("p操作");
        // 申请一个资源
        semaphore.acquire();
        System.out.println("p操作");
        // 申请一个资源
        semaphore.acquire();
        System.out.println("p操作");
        // 申请一个资源
        semaphore.acquire();
        System.out.println("p操作");
        // 释放一个资源
        semaphore.release();
        System.out.println("v操作");
        // 申请一个资源
        semaphore.acquire();
        System.out.println("p操作");
    }

CountDownLatch组件

作用: 把主线程拆成多个线程进行工作, 用来提高执行效率,比如说IDM下载器, 就可以分配多个线程来下载.

public static void main(String[] args) throws InterruptedException {
        // 分为10个线程
        CountDownLatch count = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                System.out.println("线程" + i + "开始工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程" + i + "结束工作");
                // 象征任务结束
                count.countDown();
            });
            thread.start();
        }
        // 所有线程都已经执行完 await->allwait
        count.await();
        System.out.println("所有线程都执行完了");
    }

for循环中的i其实是访问不到的, 此处设计变量捕获, 它会给我们报final修饰或者像final的对象.

在这里插入图片描述

public static void main(String[] args) throws InterruptedException {
        // 分为10个线程
        CountDownLatch count = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int n = i;
            Thread thread = new Thread(() -> {
                System.out.println("线程" + n + "开始工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("线程" + n + "结束工作");
                // 象征任务结束
                count.countDown();
            });
            thread.start();
        }
        // 所有线程都已经执行完 await->allwait
        count.await();
        System.out.println("所有线程都执行完了");
    }

此时只要新创建一个变量, 就可以了.我们新创建的变量的n, 实际上就是没有改的, 也就是像final的变量.

小结

多线程方面的知识已经总结完了, 我将会复习文件操作的知识, 有收获的小伙伴多多支持.

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值