多线程基础(二)

本篇文章为知识点复习总结,初学者相关示例可以自己写代码测试一下
1. 悲观锁与乐观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样当第二个线程想拿这个数据的时候,第二个线程会一直堵塞,直到第一个释放锁,他拿到锁后才可以访问。传统的数据库里面就用到了这种锁机制,例如:行锁,表锁,读锁,写锁,都是在操作前先上锁。java中的synchronized的实现也是一种悲观锁

乐观锁:乐观锁概念为,每次拿数据的时候都认为别的线程不会修改这个数据,所以不会上锁,但是在更新的时候会判断一下在此期间别的线程有没有修改过数据,乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。在Java中java.util.concurrent.atomic包下面的原子变量就是使用了乐观锁的一种实现方式CAS实现。

2. cas (compareAndSwap:比较并交换)
乐观锁主要就是两个步骤:冲突检测和数据更新。当多个线程尝试使用CAS同时更新同一个变量时,只有一个线程可以更新变量的值,其他的线程都会失败,失败的线程并不会挂起,而是告知这次竞争中失败了,并可以再次尝试。

CAS操作包括三个操作数:需要读写的内存位置(V)、预期原值(A)、新值(B)。如果内存位置与预期原值的A相匹配,那么将内存位置的值更新为新值B。如果内存位置与预期原值的值不匹配,那么处理器不会做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS其实就是一个:我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。这其实和乐观锁的冲突检测+数据更新的原理是一样的。

乐观锁是一种思想,CAS只是这种思想的一种实现方式。

3. 名词解释:JUC与JNI
这个主要针对面试,juc指1.5之后java并发包的缩写,java.util.concurrent包
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 [1] 从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
简单来说jni就是通过java调用底层的其它语言的功能的一种方法,类似java中下面这种native方法

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

我们熟知的volatile,Lock等并发包下的类好多都是使用native方法和cas来实现相关功能的

4. Atomic类
我们前面说到volatile可以保证多线程环境下对象的内存可见性,但是无法解决数据同步问题,比如将一个int变量声明为volatile然后多线程进行++操作,结果可能是不准确的,这种情况就可以用我们的Atomic类来解决
以java.util.concurrent.atomic.AtomicInteger类为例,其所提供的方法:getAndSet、compareAndSet、getAndIncrement、getAndDecrement等底层都是调用的Unsafe类的native方法(compareAndSwapInt),通过c/c++调用操作系统的cpu指令来保证变量赋值的原子性

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

可以看到该方法会执行while循环设置变量的值,直到成功
synchorinized的自旋锁也是使用类似的方式保证性能,但需要注意我们在自己实现类似这种情况方案时如果变量的竞争很大,更新失败多成功很少,这时不建议使用这种方法,因为会造成大量线程一直执行循环竞争锁逻辑,耗费大量性能

5. ABA问题
我们知道cas是比较原值,相等才会设置为新值,如果原值为1,在线程a设置为2之前,线程b将其设置为了2然后紧接着又设置为了1,这种情况下线程a还是会执行成功,线程a并不能判断出在他执行期间变量值是否被修改过又被复原这种情况。这就是aba问题。
解决此问题可以使用AtomicStampedReference类,它除了变量外还额外提供了一个版本号属性,每次变量更改都设置新的版本号,这样就可以区分出aba问题了。

6. 等待与通知:wait/notify
wait和notify需要在同步方法或同步代码块中调用,他们属于Object类的方法,因为任何对象都可以作为锁对象,
简单来说wait使线程停止,notify随机使一个wait状态的线程运行,当然notify的线程要先执行完自己的任务才会释放锁。
同样的调用wait方法也会释放锁,然后该线程从运行状态退出进入等待队列,等待被重新唤醒
notifyAll()方法会唤醒所有因该锁处于wait状态的线程。
notify要注意唤醒时机,如果先于wait执行则会失去作用
wait(long timeout):通俗点来说就是,我等你一段时间这段时间内你可以把我叫醒(notify),不然过了这段时间我也会自己醒。

7. Join
join方法,调用join方法的线程先跑,另一线程等待其跑完后再执行

public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            System.out.println("我先跑,等我跑完你再跑");
            Thread.sleep(1000L);
            System.out.println("我跑完了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.join();
        System.out.println("main===");
    }
}

执行结果:

我先跑,等我跑完你再跑
我跑完了
main===

同理join(long millis)方法:用大白话理解就是,我先跑xx毫秒,这段时间你等我如果我跑完了会告诉你让你跑,如果过了这个时间没收到通知说明我没跑完,你就先跑吧不用等我了。
join和sleep的区别:join会释放锁,sleep不会释放锁

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值