自旋锁的伪代码实现,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下载神器,多线程下载巨牛马好用


相关文章
|
3天前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
8 1
|
16天前
|
Java 调度
Java并发基础-线程简介(状态、常用方法)
Java并发基础-线程简介(状态、常用方法)
17 0
|
4天前
|
Java
Java中,有两种主要的方式来创建和管理线程:`Thread`类和`Runnable`接口。
【6月更文挑战第24天】Java创建线程有两种方式:`Thread`类和`Runnable`接口。`Thread`直接继承受限于单继承,适合简单情况;`Runnable`实现接口可多继承,利于资源共享和任务复用。推荐使用`Runnable`以提高灵活性。启动线程需调用`start()`,`Thread`直接启动,`Runnable`需通过`Thread`实例启动。根据项目需求选择适当方式。
16 2
|
4天前
|
Java
Java多线程中notifyAll()方法用法总结
Java多线程中notifyAll()方法用法总结
|
9天前
|
Java C++ 开发者
线程创建的终极对决:Thread 类 VS Runnable 接口,你站哪边?
【6月更文挑战第19天】在Java多线程编程中,通过`Thread`类直接继承或实现`Runnable`接口创建线程各有优劣。`Thread`方式简洁但不灵活,受限于Java单继承;`Runnable`更灵活,适合资源共享和多接口实现,提高代码可维护性。选择取决于项目需求和设计原则,需权衡利弊。
|
1天前
|
Java
Java多线程notifyAll()方法
Java多线程notifyAll()方法
|
2天前
|
调度 Python
Python多线程学习优质方法分享
Python多线程学习优质方法分享
|
4天前
|
API C++
c++进阶篇——初窥多线程(三)cpp中的线程类
C++11引入了`std::thread`,提供对并发编程的支持,简化多线程创建并增强可移植性。`std::thread`的构造函数包括默认构造、移动构造及模板构造(支持函数、lambda和对象)。`thread::get_id()`获取线程ID,`join()`确保线程执行完成,`detach()`使线程独立,`joinable()`检查线程状态,`operator=`仅支持移动赋值。`thread::hardware_concurrency()`返回CPU核心数,可用于高效线程分配。
|
9天前
|
Java 开发者
线程的诞生之路:Java多线程创建方法的抉择与智慧
【6月更文挑战第19天】Java多线程编程中,开发者可选择继承Thread类或实现Runnable接口。继承Thread直接但受限于单继承,适合简单场景;实现Runnable更灵活,支持代码复用,适用于如银行转账这类需多线程处理的复杂任务。在资源管理和任务执行控制上,Runnable接口通常更优。
|
22天前
|
Java Apache Spring
面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
【6月更文挑战第3天】面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
13 0