zoukankan      html  css  js  c++  java
  • NO3锁相关内容

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

    https://github.com/CyC2018/CS-Notes/blob/master/notes/Java 并发.md#五互斥同步

    <码出高效>

    概述

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

    电脑内存设计简要说明

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

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

    所以目前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再进行分析比较

  • 相关阅读:
    UIActinSheet和UIActionSheetDelegate
    UIWebView和UIWebViewDelegate的基本用法
    iOS开发拓展篇—音效的播放
    【零基础学习iOS开发】【02-C语言】10-函数
    李洪强漫谈iOS开发[C语言-012]-C语言基本数据类型
    iOS开发拓展篇—UIDynamic(捕捉行为)
    【零基础学习iOS开发】【02-C语言】09-流程控制
    李洪强iOS开发拓展篇—UIDynamic(重力行为+碰撞检测)
    李洪强iOS开发之【零基础学习iOS开发】【02-C语言】08-基本运算
    LeetCode 153 Find Minimum in Rotated Sorted Array
  • 原文地址:https://www.cnblogs.com/yaoxublog/p/11001865.html
Copyright © 2011-2022 走看看