NO3锁相关内容

参考内容:
https://www.cnblogs.com/dolphin0520/p/3920373.html

https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E5%B9%B6%E5%8F%91.md#%E4%BA%94%E4%BA%92%E6%96%A5%E5%90%8C%E6%AD%A5

<码出高效>

概述

为什么出现了锁操作的概念,首先需要说明一下 电脑的内存设计

电脑内存设计简要说明

电脑中分为两块 一个是主存, 一个是缓存,

主存也称为物理内存,即使在断电情况下数据也不会丢失(除非用锤子把硬件砸了哈哈),但是缓存则不会进行物理存入很容易丢失,但是缓存有自己的优点就是,读取速率极快.

所以目前cpu处理数据的方式时,先查看缓存,看是否有此条数据,有的话直接使用,没有的话就再访问主存.

java内存模型

然后需要说明一下 java的内存模型. 每个线程之间,都有自己独立的缓存,相同的主存.并且线程只能操作自己缓存内的数据,不能操作主存数据和其他线程缓存中的数据.

这样很容易引发以下场景:

想要通过 两个线程 每个线程给 主存中的i=0 进行+1 操作

主存中存储i= 0

线程1读取主存i=0 放入自己的缓存中 执行i+1

在线程1没有 将自己缓存中的 i= 1刷新到主存中时,

线程2 开始读取主存 ,此时 i = 0; 线程2进行+1操作, i=1.

并没有达到 i =2 的效果
package JavaThread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class NO22ThreadDemo1 {

    private  static int num = 0;
    
    //此种情况,如果使用线程安全的类,则不会出现问题
    //private  static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newCachedThreadPool();

        for(int i = 0 ; i < 100 ; i++){
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    num = num + 1;
                }
            });
        }
        
        //让主线程 最后再输出,因为有可能出现线程池中线程未执行直接,主线程先输出结束的情况
        Thread.sleep(10000);
        System.out.println("num : " + num);

    }
}


并没有达到加到100的效果

出现线程不安全的情况

满足线程安全的三个特性

原子性

每一个操作都为原子操作,不可进行拆分 比如 int num = 1, 但是 num = num + 1,就不是原子操作了.

一致性

每次对数据进行更改,其他相关操作都是可见的,能及时进行更新
可以使用volatile原语进行保证(当某一个线程对变量进行修改之后,可以使其他缓存中次变量失效,重新读取主存中数据)

有序性

按照代码顺序执行操作

如何保证线程安全

  1. 使用线程安全的类
  2. 保证单线程对数据进行修改
  3. 只进行查询,不修改
  4. 通过加锁操作来保证线程安全

锁分为 乐观锁 悲观锁两种

乐观锁

一般是CAS , 首先会有三个值, 存储地址,预期值,新值.

首先去存储地址查询 值,然后与预期值进行比较,看是否相同

相同则更新为新值,不同则更改预期值,再次查询比较.或者其他操作

弊端:

  1. ABA问题,从A 变为B 再变为A,则判断没有发生变化,会引发异常, 这个可以通过版本号机制解决.每次修改更新版本号
  2. 流程较长涉及多修改操作则无法使用此方法.

实例:

java中的 AtomicInteger等类使用此方法保证线程安全

悲观锁

对线程不安全的操作进行加锁

分为两种:

synchronized

原理

基于JVM实现的, 每个类对应都有一个monitor监控器,当线程调用加锁操作,会持有此监控器,其他线程只有等待此线程释放后才能调用操作.

经过版本更新,提出了 偏向锁,轻量级锁,重量级锁的概念.

  • 当线程第一次调用加锁操作时,将monitor中的id 重置为threadId,变为偏向锁
  • 在锁持有期间,其他线程再次调用,会判断id 与新线程的threadId是否相同,不同则变为轻量级锁
  • 当多个不同线程访问的时候,升级为重量级锁

适用场景:

  • 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。
  • 轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。
  • 重量级锁:有实际竞争,且锁竞争时间长。

使用

对类进行加锁操作, 此类产生的所有对象都会影响

1. synchronized互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

1.1 代码块同步

基本语法:

public void func(){
    synchronized(this){
        //.....逻辑部分
    }
}

只作用于一个对象,同时执行一个对象中的同步代码块的时候才会进行同步,执行两个对象中各自的代码块时候不会进行同步.

例子:

package JavaThread.Concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedBlock1 {

    public void function1(){
        synchronized(this){
            for (int i=0; i<10 ;i++){
                System.out.print(i);
            }
        }
    }

    public static void main(String [] args){
        SynchronizedBlock1 synchronizedBlock1 = new SynchronizedBlock1();
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(()->synchronizedBlock1.function1());
        service.execute(()->synchronizedBlock1.function1());
    }
}

结果

01234567890123456789

由于加入了同步操作,需要第一次调用之后才能到第二次调用

例子2:

package JavaThread.Concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedBlock2 {

    public void function1(){
        synchronized(this){
            for (int i=0; i<10 ;i++){
                System.out.print(i);
            }
        }
    }

    public static void main(String [] args){
        SynchronizedBlock2 synchronizedBlock1 = new SynchronizedBlock2();
        SynchronizedBlock2 synchronizedBlock2 = new SynchronizedBlock2();
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(()->synchronizedBlock1.function1());
        service.execute(()->synchronizedBlock2.function1());
        service.shutdownNow();
    }
}

结果

00112345263789456789

调用两个对象中的同步代码块时不会保证同步

1.2 同步一个方法

基本语法:

public synchronized void func () {
    // ...
}

类似于同步代码块, 是针对于对象同步的.

例子:

package JavaThread.Concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedMethod1 {

    public synchronized void function1() {
        for (int i = 0; i < 10; i++) {
            System.out.print(i);
        }
    }

    public static void main(String[] args) {
        SynchronizedMethod1 synchronizedBlock1 = new SynchronizedMethod1();
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(() -> synchronizedBlock1.function1());
        service.execute(() -> synchronizedBlock1.function1());
    }
}
1.3 同步一个类

基本语法:

public void func() {
    synchronized (SynchronizedExample.class) {
        // ...
    }
}

即使调用一个类的不同对象 也可以保证同步

例子:

package JavaThread.Concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedClass1 {

    public  void function1() {
        synchronized(SynchronizedBlock1.class) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i);
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedClass1 synchronizedClass1 = new SynchronizedClass1();
        SynchronizedClass1 synchronizedClass2 = new SynchronizedClass1();
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(() -> synchronizedClass1.function1());
        service.execute(() -> synchronizedClass2.function1());
    }
}

结果:

01234567890123456789
1.4 同步静态方法

基本语法:

public synchronized static void fun() {
    // ...
}

由于静态方法的特殊性(很早进行加载,而且唯一)

所以也可以实现 对类的方法进行加锁.

lock

原理

基于jdk实现,是自旋锁, 其中维护一个 int volatile state变量, 当线程1持有锁后,state变为1, 此时 线程2无法操作,但是线程1还可以再次持有锁,state变为2, 然后只有等线程1释放两次后,state变为2, 线程2才可以进行争抢.

juc包中 工具部分基于此实现

使用

是JUC包中的锁

例子:

package JavaThread.Concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class ReentranLock1 {
    public void function1(){
        ReentrantLock lock = new ReentrantLock();
        try {
            lock.lock();
            for (int i=0; i<10 ;i++){
                System.out.print(i);
            }
        }finally {
            lock.unlock();//及时释放锁 避免死锁
        }
    }

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

        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(() -> reentranLock1.function1());
        service.execute(() -> reentranLock1.function1());
    }
}

两者比较

目前 一般都使用synchronized. 后续真正使用lock再进行分析比较

猜你喜欢

转载自www.cnblogs.com/yaoxublog/p/11001865.html
今日推荐