自旋锁的伪代码实现,CAS的ABA问题,JUC常见类:Callable,ReentrantLock,线程创建方法的总结,信号量,原子类的应用场景,特定场所的组件CountDomLatch,针对集合类的

简介: 自旋锁的伪代码实现,CAS的ABA问题,JUC常见类:Callable,ReentrantLock,线程创建方法的总结,信号量,原子类的应用场景,特定场所的组件CountDomLatch,针对集合类的

一、 💛

自旋锁伪代码实现

就像是着急上厕所一样,谁在厕所,我就一直问上完没,上完没,我要去上Thread.currentThread(),这个是用来获取当前哪个线程调用lock,得到结果就是哪个线程的引用,如果当前锁已经处于加锁状态,这里就会返回false,cas不会进行实际操作,继续进行下一个循环(这里面会高速运行,此时一旦处于没有锁的状态,就会疯狂循环会第一时间拿到这个锁)

//伪代码哈,不是真实的
 class  SpinLock{
//此处使用owner表示当前是哪个线程持有的这把锁,null解锁状态
        private Thread owner=null;
        public void lock(){
            while(!CAS(this.owner,null,Thread.currentThread())){
//Thread.currentThread(),这个是用来获取当前哪个线程调用lock,得到结果就是哪个线程的引用,如果当前锁已经处于加锁状态,这里就会返回false,cas不会进行实际操作,继续进行下一个循环
            }
        }

二、 💙

CAS的ABA问题

CAS要点,是比较寄存器1和内存的值,通过这里的是否相等来判定内存的值,是否发生了改变,如果内存变了,存在其他线程进行了修改,如果内存的值没有变化,没有别的线程修改,接下来修改就是完全正确的的。

A->B->A 另一个线程,把变量值从A->B,又从B->A,此时本线程区分不了,这个值是始终没变还是出现变化后,又回来了的情况,大部分情况ABA没有任何影响,但是也不能排除一些极端情况,就算万分之一也是特别大的bug,假如说:账户100,希望取50元子🌚🌚,按第一下取款的时候,卡一下,我们又按了一手,产生了两个取款,请求ATM使用两个线程处理这俩请求。

假设按CAS方式取款,每个线程都去这么操作,

1.读取账户余额,放到变量m中

2.使用CAS判定当前代码安排余额是否还是m,如果是则进行修改M-50,如果不是,则取消操作。

此时下图的操作没有问题,但是看下面的第二个图

 

这种情况来个这个,小黑充50块钱,那么就会发现t1(傻呵呵🌝🌝)还是那个M值啊,我就扣你50,我直接吞你100,这种情况就是会有问题,你明明想取50块钱,机器扣你100,你把机器砸了,怒火攻心。💌💌“高并发,大数据”时代,这种情况并不少见。

ABA问题,💌💌CAS基本的思路是OK的,但主要是修改操作能够进行返回横跳就容易让咱们的CAS判定失效,CAS判定的“值相同”实际上期望的是“值没有变化过”,如果约定值只能单向变化(只多不少)但是账户余额好像不能只多不少(要不那么乐开花了吗),余额虽然是不行,但是版本号可以,一套一套的更新版本号,换句话说:此时衡量余额变没变,可以看版本号,如果版本号相同,那么就一定没有修改过,假如修改过,版本号一定增加。

CAS也是多线程开发的一种典型思路,一般都是使用CAS封装好的组件,原子类,自旋锁如何实现:可以回到使用CAS,我们在看自增的源码的时候看到了"native"关键字,就要明白,这个方法是在JVM内部通过C++代码去实现的。

三、💜

JUC(java.util.concurrent(并发的意思))的常见类,Runnable能够表示一个任务(用run方法,返回void)Callable也能表示一个任务call方法,返回一个具体的值,类型可以通过泛型参数来指定,如果使用多线程操作,如果你只关心多线程执行的过程,使用Runnable即可(像是线程池,计时器,当时都是使用Runnable只关心过程),如果关心多线程的计算结果,使用Callable更为合适。(通过多线程的方式,计算一个公式,比如创建 一个线程,让这个线程去计算1+2+3+····+100这种

  Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i=1;i< 100;i++) {
                    sum += i;
                }
                return sum;
            }
        };
//Callable不能作为Thread的构造方法参数,所以一般用下面这种操作
        FutureTask<Integer>futureTask=new FutureTask<>(callable);
//通过这样来作为Thread的参数
        Thread t=new Thread(futureTask);
        t.start();
        Integer result= futureTask.get();
//get方法类似于join一样,
        System.out.println(result);
    }
}

线程创建方法:💖

1.直接继承Thread

2.实现Runnable

3.使用lambda

4.使用线程池

5.使用Callable

这五个来搭配匿名内部类——线程池的构造方法有个ThreadFactory也能够构造线程->使用工厂模式的目的是简化设置参数的过程。

四、 ❤️

Reentrant(瑞安吹特可重入的,⚠️面试的第一印象,英语太差是很丢人的

回顾一下   synchronized务必会读(辛可肉耐子会写

                 volatile(会读会写(wao(平🐍)里太哦

ReentrantLock可重入锁,lock()加锁,unlock解锁,这样这个代码可以和synchronized替换,但是这个缺点就是很致命——要写解锁,加锁就要写解锁,这是很容易遗忘的

import java.util.concurrent.locks.ReentrantLock;
class Count {
    public int count = 0;
     public void increase() {
        count++;
    }
}
    public class Demo8 {
            public static void main(String[] args) throws InterruptedException {
                Count counter = new Count();
                ReentrantLock locker=new ReentrantLock();
                Thread t1 = new Thread(() -> {
                    locker.lock();
                    for (int i = 0; i < 5000; i++) {
                        counter.increase();
                    }
                    locker.unlock();
                });
                Thread t2 = new Thread(() -> {
                    ReentrantLock lock=new ReentrantLock();
                    locker.lock();          //加锁
                    for (int i = 0; i < 5000; i++) {
                        counter.increase();
                    }
                    locker.unlock();        //关锁
                });
                t1.start();
                t2.start();
                t1.join();       //等待t1执行完毕,之前上一个没写join,但是就算写他也是错的
                t2.join();       //等待t2执行完毕,最后main线程
                System.out.println(counter.count);
            }
        }

但是ReentrantLock具有一些特性是synchronized不具有的功能

💖1.提供了一个tryLock方法进行加锁,对于lock操作,假如加锁不成功,就会阻塞等待(死等),对于tryLock,如果加锁失败,直接返回false,当然也可以设置等待时间,tryLock给予操作提供了更多可操作空间。

💖2.ReentrantLock有两种需求,可以工作在公平锁状态下,也可以工作在非公平锁状态下,构造方法中通过参数设定的公平/非公平模式

💖3.ReentrantLock也存在等待通知机制,搭配Condition这样的类完成,这样的等待通知要比wait,notify功能更强——这几个ReentrantLock的优势

但是ReentrantLock劣势也非常明显且致命,解锁操作非常容易遗漏,推荐使用finally来执行unlock,这样就会不怕忘记这件事了

五、💚

synchronized和ReentrantLock的区别🙉🙉(要注意哦)

synchronized锁对象是任意对象

ReentrantLock锁的对象就是自己本身

如果多个线程针对不同的ReentrantLock,调用lock方法,此时是不同的ReentrantLock,调用lock方法,此时是不会产生锁竞争的

实际开发中进行多线程开发,即锁首选还是synchronized

六、💓

原子类的应用场景

💦💦1.技术需求

播放量,点赞,收藏,投币,同一个视频,有很多人同时播放/点赞/收藏。

💦💦2.统计效果

统计出现线程的请求错误->使用原子类记录出错的请求数目,另一个写一个监控服务器,来获取线上服务器的这些错误计数,并且以曲线的方式绘制到页面

其次发布程序后,发现这里的错误数大幅度上升,版本代码大概率就会存在bug!!!,还可以统计收到的请求总数(衡量服务器压力)统计每个请求的响应时间->评价的响应时间(衡量服务器的运行效率)线上服务器可通过这些内容,进行简单计数->实现监控服务器,获取/统计/展示/报警

七、 💗

信息量 Semaphore(辛么four),一个计数量(变量)描述了可用资源的个数,描述当前线程是否有临界资源可用

临界资源,多个线程进程等并发执行的实体可以公共使用到的资源(多个线程修改同一个变量,这个变量就可以认为是临界资源)

这么说挺抽象的:假如有一个停车场

💦💦💦停车场入口上面有一个显示屏,上面有显示屏,显示一行字,还有XX(信号量)车位,

如果开车进入停车场,相当于申请一个车位(申请了一个可用资源),此时计数器就要-1成为P操作(accquire(申请))

💦💦💦假如这时候你又开车出了停车场,相当于释放一个车位(释放了一个可用资源),此时计数器就要+1,成为V操作(release释放)

锁的本质是一个特殊的信号量(里面的数值,非0即1,二元信号值)

import java.util.concurrent.Semaphore;
public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(4);
        semaphore.acquire();
        System.out.println("计数器-1");
        semaphore.acquire();
        System.out.println("计数器-1");
        semaphore.acquire();
        System.out.println("计数器-1");
        semaphore.acquire();
        System.out.println("计数器-1");
//这里线程就不够用了,会进行阻塞等待
        semaphore.release();
        semaphore.acquire();
        System.out.println("我喜欢你");
    }
}

当计数器为0的时候,继续进行P操作,就会进入阻塞等待,一直等待到其他线程执行V操作,释放一个空间资源为止,信号量比锁更加广义,不仅仅可以描述一个资源,还可以描述N个资源

八、💕

CountDownLatch特定场所组件,下载某个东西~大文件下载比较慢,我们平时往往不是家里的网不足够好,而是服务器这边的限制,IDM(下载神器)有一些多线程下载器,可以把一个大的文件,拆分成多个小部分,多个线程分别下载,每个下载一部分,每个线程分别是一个网络连接,大幅度提高下载速度。10个都下载OK,整体才能叫做完成。

import java.util.concurrent.CountDownLatch;
public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int id = i;
            Thread t = new Thread(() -> {
//这里涉及变量捕获,java获取这个变量,但是final或者事实上final的变量(因为进行了++,改进)
                System.out.println("线程" + id+ "开始工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + id+ "完成工作");
                //这里输入的是id,因为输入i会报错,i是局部变量
            countDownLatch.countDown();});
              t.start();}
//countDownLatch.await()负责等待任务结束,a->all等待全部任务结束,
        countDownLatch.await();
        System.out.println("全部完成了");
        }
    }
import java.util.concurrent.CountDownLatch;
//当调用countDown次数<初始化设置的次数await就会阻塞等待 
public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);          //10个加载器
        for (int i = 0; i < 5; i++) {
            int id = i;
            Thread t = new Thread(() -> {
//这里涉及变量捕获,java获取这个变量,但是final或者事实上final的变量(因为进行了++,改进)
                System.out.println("线程" + id+ "开始工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + id+ "完成工作");
                //这里输入的是id,因为输入i会报错,i是局部变量
            countDownLatch.countDown();});
              t.start();}
//countDownLatch.await()负责等待任务结束,a->all等待全部任务结束,当调用countDown次数<初始化设置的次数await就会阻塞等待
//这时候我只是调用了5次,总共初始化是10次,所以线程会进行阻塞等待
        countDownLatch.await();
        System.out.println("全部完成了");
        }
    }

九、💞

集合类:哪些是安全的,多线程同时操作这个集合类,是否会有问题

Vector,Stack,HashTable,关键方法中,使用了synchronized安全但是并不推荐大家使用

因为这些老版本的集合类并不靠谱(加锁,也不一定是线程安全,就是无脑加,这种东西是否安全要具体情况进行具体的分析🤬🤬)

多个线程并发的执行set,由于synchronized限制是线程安全,但是更复杂的情况假如判定get值是xxx,在进行set就会有错误,我们需要做的是考虑哪些代码的实现要求是原子的

ArrayList本身没有使用synchronized,自己又不想加锁,就可以使用这个,Collections.synchronizedList(new ArrayList),无脑加锁,没必要用但是要了解

CopyWriteArrayList写的时候复制,去解决一些线程问题

多线程去同时修改同一个变量,如果多线程修改不同变量,是不是就线程安全了捏。

使用IDM下载神器,多线程下载巨牛马好用


相关文章
|
2月前
|
Java
【JavaEE】——多线程常用类
Callable的call方法,FutureTask类,ReentrantLock可重入锁和对比,Semaphore信号量(PV操作)CountDownLatch锁存器,
|
2月前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
2月前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
2月前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
3月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
4月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
43 2
|
4月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
92 2
|
4月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
68 2
|
4月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
44 1
|
3天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
32 20