zoukankan      html  css  js  c++  java
  • Java 基础 锁

    滴滴面试,分布式の锁,我:?。故记录

    https://www.cnblogs.com/wuhan729/p/8601108.html

    单进程的锁

    在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量(synchronized)。而同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。Sync可以对变量、代码块等同步,但是有其缺陷,因此还有Lock锁

    LOCK锁:当Sync的线程被sleep了,那别的都拿不到这个变量了,影响程序执行效率。为了不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock可以办到。

    lock是一个接口,讲几个常用的方法,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。

    • lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
    • tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
    • tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。反之拿到了就是true。
    • lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
     1 public class Test {
     2     private ArrayList<Integer> arrayList = new ArrayList<Integer>();
     3     private Lock lock = new ReentrantLock();    //注意这个地方
     4     public static void main(String[] args)  {
     5         final Test test = new Test();
     6          
     7         new Thread(){
     8             public void run() {
     9                 test.insert(Thread.currentThread());
    10             };
    11         }.start();
    12          
    13         new Thread(){
    14             public void run() {
    15                 test.insert(Thread.currentThread());
    16             };
    17         }.start();
    18     }  
    19      
    20     public void insert(Thread thread) {
    21         lock.lock();
    22         try {
    23             System.out.println(thread.getName()+"得到了锁");
    24             for(int i=0;i<5;i++) {
    25                 arrayList.add(i);
    26             }
    27         } catch (Exception e) {
    28             // TODO: handle exception
    29         }finally {
    30             System.out.println(thread.getName()+"释放了锁");
    31             lock.unlock();
    32         }
    33     }
    34 }

    ReentrantLock属于“可重入锁”,实现了Lock

    可重入锁:

      如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。例子:两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

    可中断锁:顾名思义,就是可以相应中断的锁。

      在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

      如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

      在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。

    公平锁:

    公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

      非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

      在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

      而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

    读写锁

      读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。

      正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

      ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。

      可以通过readLock()获取读锁,通过writeLock()获取写锁。

      上面已经演示过了读写锁的使用方法,在此不再赘述。

    分布式的锁

    目前很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。基于 CAP理论,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。

    在许多的场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务分布式锁等。很多时候我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,通过 Java 提供的并发 API 我们可以解决,但是在分布式环境下,就没有那么简单啦。

    • 分布式与单机情况下最大的不同在于其不是多线程而是多进程
    • 多线程由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而进程之间甚至可能都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方

    我们需要什么样的分布式锁?

    • 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
    • 与单机模式下的锁不仅需要保证进程可见,还需要考虑进程与锁之间的网络问题。(主要就是需要考虑到网络的延时和不可靠)
    • 分布式锁还是可以将标记存在内存,只是该内存不是某个进程分配的内存而是公共内存。至于利用数据库、文件等做锁与单机的实现是一样的,只要保证标记能互斥就行。

    基于Mysql的分布式锁

    直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。

    缺点:

    ①这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
    ②这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
    ③这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
    ④这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

    解决:

    ①数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
    ②没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
    ③非阻塞的?搞一个while循环,直到insert成功再返回成功。
    ④非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

    基于zookeeper的分布式锁

    待。。。

  • 相关阅读:
    在Xcode5中修改整个项目名
    EFCore的事务和分布式事务的使用
    NET CORE API权限控制之JWT的创建和引用
    NET CORE引用log4net日志文件的应用
    NET CORE在Linux下部署并且用Nginx 做负载均衡(主要说明CentOS)
    Centos 7下安装nginx,使用yum install nginx,提示没有可用的软件包
    Laravel Packages
    Laravel artisan commands
    ExtJs xtype
    ExtJS Complex data binding
  • 原文地址:https://www.cnblogs.com/tillnight1996/p/12510531.html
Copyright © 2011-2022 走看看