精华内容
下载资源
问答
  • java有哪些锁种类

    2021-02-12 12:14:57
    java有哪些锁种类(转)在读很并发文章中,会提及各种各样如公平,乐观等等,这篇文章介绍各种的分类。介绍的内容如下:公平/非公平可重入独享/共享互斥/读写乐观/悲观分段偏向/轻量...

    java有哪些锁种类(转)

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:

    公平锁/非公平锁

    可重入锁

    独享锁/共享锁

    互斥锁/读写锁

    乐观锁/悲观锁

    分段锁

    偏向锁/轻量级锁/重量级锁

    自旋锁

    上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。

    公平锁/非公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

    对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    可重入锁

    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。

    对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。

    对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

    synchronized void setA() throws Exception{

    Thread.sleep(1000);

    setB();

    }

    synchronized void setB() throws Exception{

    Thread.sleep(1000);

    }

    上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

    独享锁/共享锁

    独享锁是指该锁一次只能被一个线程所持有。

    共享锁是指该锁可被多个线程所持有。

    对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。

    读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

    对于Synchronized而言,当然是独享锁。

    互斥锁/读写锁

    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

    互斥锁在Java中的具体实现就是ReentrantLock

    读写锁在Java中的具体实现就是ReadWriteLock

    乐观锁/悲观锁

    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

    悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。

    乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

    从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

    悲观锁在Java中的使用,就是利用各种锁。

    乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

    但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

    偏向锁/轻量级锁/重量级锁

    这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    自旋锁

    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

    典型的自旋锁实现的例子,可以参考自旋锁的实现

    展开全文
  • 问题集合多线程常见问题之多线程安全问题多线程常见问题之多线程乱跑传参问题 多线程常见问题之多线程安全问题 明确一点,不操作共享变量就不会线程安全问题。 线程安全问题,什么时候加锁 共享数据可能同时被...

    1、多线程常见问题之多线程安全问题

    提到多线程,大家就会想到锁。

    他们两个是两个概念。

    用到多线程的场景多了去了,不是每个多线程都要加锁。

    不操作共享变量就不会有线程安全问题。

    共享数据有可能同时被两个线程操作,再加锁。

    不能碰到多线程就想到线程安全,然后加锁。

    锁的前提是你操作了共享变量,如果没有共享变量,是不存在数据安全问题的,并且不能盲目的追求速度快,忽视了数据的安全,本末倒置。

    乱用锁的后果是性能降低或者死锁。

    效率和数据安全是个跷跷板,A高了,B就低,合理的运用锁,才是正解。

    另外ArrayList与HashMap本身亦是线程不安全的,他们本身没有针对线程级进行处理,所以在进行多线程操作时,就会出现线程安全问题。

    2、多线程常见问题之多线程乱跑传参问题

    多线程协同问题, 本身多线程是没有执行顺序的。

    有的时候会遇到这样的场景,想提高效率,使用了多线程,但是方法与方法之间的传参乱套了。

    这么多线程齐头乱跑,第二个线程要用到第一个线程的结果,怎么搞。

    join()方法:当调用join()方法的线程结束之后,其他线程才可以跑。

    将join()方法调用者的线程加入到当前线程,调用者这个线程执行完,在执行当前线程。

    比如说将子线程.join();这行代码加入到主线程中,那么子线程先执行完,再走主线程后面的代码。

    现在有两个线程A,线程B

    不使用join()方法,两个线程争夺CPU执行权。

    在线程B中使用线程A,结果就是一定先执行完线程A,才会执行线程B。

    在线程A中使用线程B,结果就是一定先执行完线程B,才会执行线程A。

    上图如下:

    1、新建一个线程。
    在这里插入图片描述

    2、不加join()方法。
    在这里插入图片描述

    3、加入join()方法。
    在这里插入图片描述

    再举个例子:

    在这里插入图片描述

    总结:

    如果你有两个线程A,线程B,线程B要使用线程A的结果,可以说是线程传参问题,怎么解决?

    你可以使用join()方法,但你的执行速度肯定会变慢,那怎么办?

    可以取巧一下,打个时间差,根据自己的业务来,不一定非要用户触发再跑多线程,提前跑了完事。

    几十ms的时间,用户是感受不到的,但是如果就在这100ms内,多个线程一起跑,你还要利用其他线程的结果,肯定会有问题,计算机是能感受到多少多少ms的。

    3、start()方法与run()方法区别

    start()方法执行后,线程状态由新建状态进入运行状态。

    run方法调用后,就是一个普通的方法,不涉及到线程层次。

    4、wait()方法与sleep()方法区别

    1、wait进入等待状态会释放锁,sleep不会释放锁。

    2、wait是Object类的方法,sleep是Thread的方法。

    3、wait必须在同步代码块使用,sleep任意。

    4、wait可以指定时间进入计时等待也可以不指定进入无限等待,sleep必须指定时长。

    5、继承Thread类与实现Runable接口有什么不同

    1、适合多个相同的程序代码的线程共享同一个资源,比如声明成员变量。

    2、面向接口编程,多实现,解决java中单继承的局限性问题。

    3、增加程序的健壮性,实现解耦操作。

    4、ThreadPoolExecutor线程池只能放入实现Runable或callable类线程,不能直接放入Thread类。

    6、为什么wait/notify是Object类方法,而不是Thread

    首先,java语言为什么叫java语言。

    其次,wait方法为什么是Object类,你个杠精。

    最后,等待唤醒方法是由锁来使用的,锁类型是任意类型,那么等待唤醒方法自然也就是Object类咯。

    如果是Thread类,那么实现Runable接口的可不是线程类,难道多线程只能采用继承Thread类的方法吗。

    7、Synchronized与ReentrantLock的区别

    1、Synchronized由编译器保证锁的加锁和释放,而ReentrantLock需要手工声明来加锁和释放锁,容易忘记释放锁,造成死锁。

    2、synchronized可以锁住方法和代码块,lock只可以锁住代码块。

    3、API的区别,比如lock中有很多丰富功能的API。synchronized不可以中断,lock可以中断也可以不中断;synchronized只能是非公平锁,而reentrantLock可以指定是公平锁还是非公平锁。

    4、Synchronized依赖于JVM实现,操作系统实现。ReentrantLock是jdk层次,一个接口,有源码可以阅读。

    如果你需要使用很多API,比如判断某线程是否获取锁,判断等待队列中有哪些线程,那么这种情况下,lock锁是可以实现的,synchronized不可以。

    8、volatile与synchronized的区别

    1、volatile只能修饰实例变量和类变量,synchronized可以修饰方法,以及代码块。

    2、volatile只能保证可见性和有序性,synchronized是一种排他互斥锁,可以保证三种特性。

    3、两个都是关键字,lock是实实在在的jdk代码。

    9、volatile与atomic的区别

    1、volatile常用于修饰类似开关类型的变量,Boolean 类型的变量。

    2、Atomic常用于计数器之类变量。

    展开全文
  • 多线程中,每个线程的执行顺序,是无法预测不可控制的,那么在对数据进行读写的时候便存在由于读写顺序多乱而造成数据混乱错误的可能性。那么如何控制,每个线程对于数据的读写顺序呢?这里就涉及到线程锁。什么是...

    在多线程中,每个线程的执行顺序,是无法预测不可控制的,那么在对数据进行读写的时候便存在由于读写顺序多乱而造成数据混乱错误的可能性。那么如何控制,每个线程对于数据的读写顺序呢?这里就涉及到线程锁。

    什么是线程锁?使用锁的目的是什么?先看一个例子。

    private voidtestSimple(){

    SimpleRunner runner= newSimpleRunner();

    pool.execute(runner);

    pool.execute(runner);

    }int account01 =10;int account02 = 0;class SimpleRunner implementsRunnable{

    @Overridepublic voidrun() {while(true){//保证两个账户的总额度不变

    account01 --;

    sleep(1000);

    account02++;

    Console.println("account01:"+account01+" account02:"+account02);

    }

    }

    }private void sleep(inttime){try{

    Thread.sleep(time);

    }catch(InterruptedException e) {

    e.printStackTrace();

    }

    }

    调用testSimple()方法开启两个线程执行账户金额转移,运行结果如下:

    account01:9  account02:2

    account01:9  account02:2

    account01:8  account02:4

    account01:8  account02:4

    account01:6  account02:6

    account01:6  account02:6

    account01:5  account02:7

    account01:4  account02:8

    很明显两个账户的金额总和无法保证为10,甚至变多了。之所以发生这种状况一方面是因为++ 和--操作不是原子操作,其次两个变量的修改也没有保证同步进行。由于线程的不确定性则将导致数据严重混乱。下面换一种方式看看如何:

    我们修改while循环体,不使用++或者--操作,同时对操作进行加锁:

    while(true){//保证两个账户的总额度不变

    synchronized ("lock"){//通过synchronized锁把两个变量的修改进行同步

    account01 = account01 -1;

    account02= account02 +1;

    Console.println("account01:"+account01+" account02:"+account02);

    sleep(1000);

    }

    }

    执行结果如下:

    account01:9  account02:1

    account01:8  account02:2

    account01:7  account02:3

    account01:6  account02:4

    account01:5  account02:5

    现在数据就能够完全正常了。这里涉及到synchronized锁,其目的就是保证在任意时刻,只允许一个线程进行对临界区资源(被锁着的代码块)的操作。

    习惯上喜欢称这种机制为加锁,为了容易理解,可以把这种机制理解为一把钥匙和被锁着的代码块,只有拿到钥匙的线程才能执行被锁住的代码块。而钥匙就是synchronized(“lock”)中的字符串对象"lock",而被锁着的代码块则是{}中的代码。

    1b0ac5009c3d434e022f79b3c3e3784f.png

    某个线程如果想要执行代码块中的内容,则必须要拥有钥匙"lock"对象。但“lock”有个特性,同一时刻只允许一个线程拥有(暂时不考虑共享锁)。这样就可以保证所有的线程依次执行被锁着的代码块,避免数据混乱。在这里有一个前提条件,也就是钥匙是对于所有线程可见的,应该设置为全局变量且只有一个实例,否则每一个线程都有一个自己的钥匙,那么就起不到锁的作用了。例如:

    while(true){

    String lock= new String("lock");//每个线程进入run方法的时候都new一个自己的钥匙

    synchronized(lock){

    account01= account01 -1;

    account02= account02 +1;

    Console.println("account01:"+account01+" account02:"+account02);

    sleep(1000);

    }

    }

    执行结果如下:

    account01:8 account02:2account01:8 account02:2account01:6 account02:3account01:6 account02:3account01:5 account02:5account01:4 account02:5

    这样便又发生了混乱,每个线程都有自己的钥匙,他们随时都可以操作临界区资源,和没有加锁无任何区别。所以在多线程操作中,锁的使用至关重要!!!

    在java中有哪些锁?该如何进行分类呢?

    1、共享锁/排它锁

    共享锁和排他锁是从同一时刻是否允许多个线程持有该锁的角度来划分。

    共享锁允许同一时刻多个线程进入持有锁,访问临界区资源。而排他锁就是通常意义上的锁,同一时刻只允许一个线程访问临界资源。对于共享锁,主要是指对数据库读操作中的读锁,在读写资源的时候如果没有线程持有写锁和请求写锁,则此时允许多个线程持有读锁。

    在这里理解共享锁的时候,不是任意时刻都允许多线程持有共享锁的,而是在某些特殊情况下才允许多线程持有共享锁,在某些情况下不允许多个线程持有共享锁,否则,如果没有前提条件任意时刻都允许线程任意持有共享锁,则共享锁的存在无意义的。例如读写锁中的读锁,只有当没有写锁和写锁请求的时候,就可以允许多个线程同时持有读锁。这里的前提条件就是“没有写锁和写锁请求”,而不是任意时刻都允许多线程持有共享读锁。

    2、悲观锁/乐观锁

    主要用于数据库数据的操作中,而对于线程锁中较为少见。

    悲观锁和乐观锁是一种加锁思想。对于乐观锁,在进行数据读取的时候不会加锁,而在进行写入操作的时候会判断一下数据是否被其它线程修改过,如果修改则更新数据,如果没有则继续进行数据写入操作。乐观锁不是系统中自带的锁,而是一种数据读取写入思想。应用场景例如:在向数据库中插入数据的时候,先从数据库中读取记录修改版本标识字段,如果该字段没有发生变化(没有其他线程对数据进行写操作)则执行写入操作,如果发生变化则重新计算数据。

    对于悲观锁,无论是进行读操作还是进行写操作都会进行加锁操作。对于悲观锁,如果并发量较大则比较耗费资源,当然保证了数据的安全性。

    3、可重入锁/不可重入

    这两个概念是从同一个线程在已经持有锁的前提下能否再次持有锁的角度来区分的。

    对于可重入锁,如果该线程已经获取到锁且未释放的情况下允许再次获取该锁访问临界区资源。此种情况主要是用在递归调用的情况下和不同的临界区使用相同的锁的情况下。

    对于不可重入锁,则不允许同一线程在持有锁的情况下再次获取该锁并访问临界区资源。对于不可重入锁,使用的时候需要小心以免造成死锁。

    4、公平锁/非公平锁

    这两个概念主要使用线程获取锁的顺序角度来区分的。

    对于公平锁,所有等待的线程按照按照请求锁的先后循序分别依次获取锁。

    对于非公平锁,等待线程的线程获取锁的顺序和请求的先后不是对应关系。有可能是随机的获取锁,也有可能按照其他策略获取锁,总之不是按照FIFO的顺序获取锁。

    在使用ReentrantLock的时候可以通过构造方法主动选择是实现公平锁还是非公平锁。

    5、自旋锁/非自旋锁

    这两种概念是从线程等待的处理机制来区分的。

    自旋锁在进行锁请求等待的时候不进行wait挂起,不释放CPU资源,执行while空循环。直至获取锁访问临界区资源。适用于等待锁时间较短的情景,如果等待时间较长,则会耗费大量的CPU资源。而如果等待时间较短则可以节约大量的线程切换资源。

    非自旋锁在进行锁等待的时候会释放CPU资源,可以通多sleep wait 或者CPU中断切换上下文,切换该线程。在线程等待时间较长的情况下可以选择此种实现机制。

    除此之外还有一种介于两者之间的锁机制——自适应自旋锁。当线程进行等待的时候先进性自旋等待,在自旋一定时间(次数)之后如果依旧没有持有锁则挂起等待。在jvm中synchronized锁已经使用该机制进行处理锁等待的情况。

    在工作中可以根据不同的情况选取合适的锁进行使用。无论使用哪种锁,其目的都是保证程序能够按照要求顺利执行,避免数据混乱情况的发生。

    常用锁的使用方法

    1、synchronized锁:

    对于synchronized锁首先需要明白加锁的底层原理。每一个对象实例在对象头中都会有monitor record列表记录持有该锁的线程,底层通多对该列表的查询来判断是否已经有线程在访问临界区资源。JVM内部细节之一:synchronized关键字及实现细节(轻量级锁Lightweight Locking)

    在使用synchronized的时候必须弄清楚谁是“钥匙”,属于全局变量还是线程内局部变量,每个加锁的临界区是使用的哪个“钥匙”对象。必须理清楚加锁线程和“钥匙”对象的关系!!!!

    synchronized只可以对方法和方法中的代码块进行加锁,而网上所说的“类锁”并不是对类进行加锁,而是synchronized(XXXX.class)。synchronized是不支持对类、构造方法和静态代码块进行加锁的。

    public synchronized void showInfo01(){//这里synchronized锁的是this对象,也即synchronized(this)

    }public voidshowInfo02(){synchronized (this){//这里的this可以替换为任意Object对象。注意是Object对象,基本变量不行。java中字符串是String实例,所以字符串是可以的。//doSomething

    }

    }

    2、reentranLock

    synchronized加锁机制使基于JVM层面的加锁,而ReentrantLock是基于jdk层面的加锁机制。ReentrantLock根据名称可以看出是可重入锁,其提供的构造方法可以指定公平锁或非公平锁。ReentrantLock使用起来比synchronized更加灵活、方便和高效。

    /*** Creates an instance of {@codeReentrantLock} with the

    * given fairness policy.

    *

    *@paramfair {@codetrue} if this lock should use a fair ordering policy*/

    public ReentrantLock(boolean fair) {//通过true或false来指定公平锁或非公平锁

    sync = fair ? new FairSync() : newNonfairSync();

    }

    下面看一下使用方法:这里使用的是默认非公平锁进行测试。

    private voidtestReentrantLock() {

    MyRunnerForReentrantLock run= newMyRunnerForReentrantLock();for (int i = 0; i < 10; i++) {//开启10个线程进行测试

    sleep(10);//睡眠10ms保证线程开启的顺序能够按照1-10依次开启

    pool.execute(run);

    }

    }

    LockTest lockTest= newLockTest();class MyRunnerForReentrantLock implementsRunnable {

    @Overridepublic voidrun() {

    lockTest.reEnterLock(new AtomicInteger(3));//在run方法中调用reEnterLock()方法测试重入测试

    }

    }classLockTest {

    ReentrantLock reentrantLock= new ReentrantLock();//使用默认的非公平锁ReentrantLock

    private voidreEnterLock(AtomicInteger time) {

    reentrantLock.lock();//加锁

    Console.println(Thread.currentThread().getName() + "--" +time);try{if (time.get() == 0) {return;

    }else{

    time.getAndDecrement();

    reEnterLock(time);//这里使用递归来测试重入

    }

    }finally{

    reentrantLock.unlock();//释放锁。注意这里在finally中释放锁避免加锁代码抛出异常导致锁无法释放造成阻塞

    }

    }

    }

    执行结果如下,注意线程输出的顺序.

    pool-1-thread-1--3pool-1-thread-1--2pool-1-thread-1--1pool-1-thread-1--0

    pool-1-thread-2--3pool-1-thread-2--2pool-1-thread-2--1pool-1-thread-2--0

    pool-1-thread-4--3pool-1-thread-4--2pool-1-thread-4--1pool-1-thread-4--0

    pool-1-thread-5--3pool-1-thread-5--2pool-1-thread-5--1pool-1-thread-5--0

    pool-1-thread-8--3

    ......

    ......

    可以看出同一个线程中time变量从3、2、1、0依次循环,说明线程进入了循环体,那么线程确实是允许重入,同一个线程可以多次获取该锁。

    但是注意以下线程输出的顺序却不是由1到10.而是 pool-1-thread-1、pool-1-thread-2、pool-1-thread-4、pool-1-thread-5、pool-1-thread-8.这就是因为ReentrantLock使用的非公平锁造成的,使用非公平锁的线程在获取“钥匙”的顺序上和线程开始等待的顺序是没有关系的。我们修改一下使用公平锁测试一下:修改以下代码:

    ReentrantLock reentrantLock = new ReentrantLock(true);//使用公平锁ReentrantLock

    执行结果如下:

    pool-1-thread-1--3pool-1-thread-1--2pool-1-thread-1--1pool-1-thread-1--0pool-1-thread-2--3pool-1-thread-2--2pool-1-thread-2--1pool-1-thread-2--0pool-1-thread-3--3pool-1-thread-3--2pool-1-thread-3--1pool-1-thread-3--0pool-1-thread-4--3pool-1-thread-4--2pool-1-thread-4--1pool-1-thread-4--0pool-1-thread-5--3pool-1-thread-5--2....

    ....

    可以看出线程的执行顺序按照1、2、3、4的顺序进行输出。

    除了上面的lock()方法外ReentrantLock还提供了两个重载的方法tryLock。ReentrantLock在进行等待持锁的时候不同于synchronized之处就在于ReentrantLock可以中断线程的等待,不再等待锁。其主要方法就是tryLock()的使用。

    tryLock被重载了两个方法,方法签名为:

    public boolean tryLock() {}

    public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {}

    后者指定了等待超时时间官方文档中注明了tryLock等待锁的机制:

    public booleantryLock()

    Acquires the lock onlyifit is not held by another thread at the time of invocation.

    Acquires the lockif it is not held by another thread and returns immediately with the value true, setting the lock hold count to one. Even when this lock has been set to use a fair ordering policy, a call to tryLock() will immediately acquire the lock if it is available, whether or not other threads are currently waiting for the lock. This "barging" behavior can be useful in certain circumstances, even though it breaks fairness. If you want to honor the fairness setting for this lock, then use tryLock(0, TimeUnit.SECONDS) which is almost equivalent (it also detects interruption).

    If the current thread already holdsthis lock then the hold count is incremented by one and the method returns true.

    If the lock is held by another thread thenthis method will return immediately with the value false.

    public boolean tryLock(long timeout, @NotNull TimeUnit unit) throwsInterruptedException

    Acquires the lockifit is not held by another thread within the given waiting time and the current thread has not been interrupted.

    Acquires the lockif it is not held by another thread and returns immediately with the value true, setting the lock hold count to one. If this lock has been set to use a fair ordering policy then an available lock will not be acquired if any other threads are waiting for the lock. This is in contrast to the tryLock() method. If you want a timed tryLock that does permit barging on a fair lock then combine the timed and un-timed forms together:if (lock.tryLock() ||lock.tryLock(timeout, unit)) {

    ...

    }

    If the current thread already holdsthis lock then the hold count is incremented by one and the method returns true.

    If the lock is held by another thread then the current thread becomes disabledforthread scheduling purposes and lies dormant until one of three things happens:

    The lock is acquired by the current thread; or

    Some other thread interrupts the current thread; or

    The specified waiting time elapses

    If the lock is acquired then the valuetrueis returned and the lock hold count is set to one.

    If the current thread:

    has its interrupted status set on entry tothismethod; or

    is interruptedwhileacquiring the lock,

    then InterruptedException is thrown and the current thread's interrupted status is cleared.

    If the specified waiting time elapses then the value falseis returned. If the time is less than or equal to zero, the method will not wait at all.

    Inthis implementation, as this method is an explicit interruption point, preference is given to responding to the interrupt over normal or reentrant acquisition of the lock, and over reporting the elapse of the waiting time.

    对于读写锁的请求“钥匙”策略如下:

    当写锁操作临界区资源时,其它新过来的线程一律等待,无论是读锁还是写锁。

    当读锁操作临界区资源时,如果有读锁请求资源可以立即获取,不用等待;如果有写锁过来请求资源则需要等待读锁释放之后才可获取;如果有写锁在等待,然后又过来的有读锁,则读锁将会等待,写锁将会优先获取临界区资源操作权限,这样可以避免写线程的长期等待。

    使用方法如下:

    private voidtestReentrantRWLock() {

    MyRunnerForReentrantRWLock run= newMyRunnerForReentrantRWLock();for (int i = 0; i < 10; i++) {//开启10个线程测试       sleep(10);//睡眠10ms保证线程开启的顺序能够按照1-10依次开启

    pool.execute(run);

    }

    }

    AtomicInteger num= new AtomicInteger(1);//用来切换读写锁测试方法

    ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(true);//公平读写锁

    private class MyRunnerForReentrantRWLock implementsRunnable {

    @Overridepublic voidrun() {if(num.getAndIncrement() ==3){

    lockTest.write();//调用写锁测试

    }else{

    lockTest.read();//调用读锁测试

    }

    }

    }public void read() {//使用读锁

    rwlock.readLock().lock();try{

    Console.println(Thread.currentThread().getName()+"------read");

    sleep(2000);

    }finally{

    rwlock.readLock().unlock();

    }

    }public void write() {//使用写锁

    rwlock.writeLock().lock();try{

    sleep(2000);//模拟写操作

    Console.println(Thread.currentThread().getName()+"------write");

    }finally{

    rwlock.writeLock().unlock();

    }

    }

    执行结果如下:

    pool-1-thread-1------read

    pool-1-thread-2------read//在这里有明显的停顿,大约2s之后下面的直接输出,没有停顿

    pool-1-thread-3------write

    pool-1-thread-4------read

    pool-1-thread-5------read

    pool-1-thread-7------read

    pool-1-thread-10------read

    pool-1-thread-6------read

    pool-1-thread-8------read

    pool-1-thread-9------read

    由运行结果执行顺序和时间可以看出,在进行write的时候其它读线程进行了等待操作,然后write释放之后,其它读操作同时操作临界区资源,未发生阻塞等待。

    4、自旋锁

    自旋锁是在线程等待的时候通过自选while(){}空循环避免了线程挂起切换,减少了线程切换执行的时间。因此在选择使用自旋锁的时候尽量保证加锁代码的执行时间小于等待时间,这样就可以避免自旋锁大量占用CPU空转,同时又免去了非自旋锁线程切换的花销。如果加锁代码块较多,此时自旋锁就哟啊占用太多的CPU进行空转,此时如果发生大量线程请求锁则会大量浪费资源。用户可以根据具体情况来自定义自旋锁的实现,可以实现公平自旋锁和非公平自旋锁。

    这里有介绍自定义自旋锁的实现方式:Java锁的种类以及辨析(二):自旋锁的其他种类

    文章中介绍的很清楚了,TicketLock和CLHLock 逻辑比较简单,这里不再详述,只对MCSLock的实现做一下解读。其中原文中MCSLock的实现unlock()方法中在释放资源解锁下一个等待线程的机制有些问题,已经做出了修改,请注意辨别。

    packagecom.zpj.thread.blogTest.lock;/*** Created by PerkinsZhu on 2017/8/16 18:01.*/

    importjava.util.concurrent.atomic.AtomicReferenceFieldUpdater;public class MCSLock {//这是通过链表实现对线程的控制的。每过来一个新的线程则把它添加到链表上阻塞进行while循环,当前一个线程结束之后,修改下一个线程的开关,开启下个线程持有锁。

    public static classMCSNode {volatileMCSNode next;volatile boolean isLocked = true;

    }private static final ThreadLocal NODE = new ThreadLocal();//这里保存的是当前线程的node,要理解ThreadLocal 的工作机制

    @SuppressWarnings("unused")private volatileMCSNode queue;private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class, MCSNode.class, "queue");public voidlock() {

    MCSNode currentNode= new MCSNode();//过来一个新线程创建一个node,同时防止在当前线程的NODE中进行保存。

    NODE.set(currentNode);//注意,这里的NODE存储的数据各个线程中是不共享的

    MCSNode preNode = UPDATER.getAndSet(this, currentNode);//获取前一个node节点,并更新当前节点

    if (preNode != null) {//前一个节点存在说明有线程正在操作临界区资源。则当前线程循环等待

    preNode.next = currentNode;//把当前节点加入到链表中,等待获取资源

    while (currentNode.isLocked) {}//循环等待,直至前一个线程释放资源,修改当前node的isLocked标志位

    }

    }public voidunlock() {

    MCSNode currentNode= NODE.get();//取出当前线程的node节点

    if (currentNode.next == null) {//如果没有新的线程等待持锁

    if (UPDATER.compareAndSet(this, currentNode, null)) {//把当前node释放,如果成功则结束,如果失败进入else

    } else { //设置失败说明突然有线程在请求临界区资源进行等待。此时有新的线程更新了UPDATER数据。//***********************注意下面的逻辑,已经进行修改 【start】*********************************

    while (currentNode.next == null) {}//等待新加入的线程把节点加入链表//此时currentNode.next != null 这里理应使用锁资源,而不应该直接结束,不然等待的线程无法获取“钥匙”访问临界区资源。所以添加以下两行代码释放锁资源

    currentNode.next.isLocked = false;//释放新添加线程的等待

    currentNode.next = null;

    //********************************** end ******************************

    }

    }else{

    currentNode.next.isLocked= false;//释放下一个等待锁的线程

    currentNode.next = null;

    }

    }

    }

    5、信号量实现锁效果

    在jdk中,除了以上提供的Lock之外,还有信号量Semaphore也可以实现加锁特性。Semaphore是控制访问临界区资源的线程数量,Semaphore设置一个允许同时操作临界区资源的阈值,如果请求的线程在阈值之内则允许所有线程同时访问临界区资源,如果超出设置的该阈值则挂起等待,直至有线程退出释放之后,才允许新的资源获得操作临界区资源的权利。如果需要把它当做锁使用,则只需要设置该阈值为1,即任意时刻只允许一个线程对临界区资源进行操作即可。虽然不是锁,但却实现了锁的功能——线程互斥串行。

    使用示例:

    Semaphore semaphore = new Semaphore(1);//同时只允许一个线程可以访问临界区资源

    private voidtestSemaphore(){for(int i = 0; i<5;i++){//开启5个线程竞争资源

    pool.execute(newSemapRunner());

    }

    }class SemapRunner implementsRunnable{

    @Overridepublic voidrun() {try{

    Console.println(Thread.currentThread().getName()+" 请求资源");

    semaphore.acquire();//请求资源

    Console.println(Thread.currentThread().getName()+" 获取到资源");

    sleep(2000);

    Console.println(Thread.currentThread().getName()+" 释放资源");

    semaphore.release();//释放资源

    } catch(InterruptedException e) {

    e.printStackTrace();

    }

    }

    }

    运行结果如下:

    pool-1-thread-2请求资源

    pool-1-thread-4请求资源

    pool-1-thread-2获取到资源

    pool-1-thread-5请求资源

    pool-1-thread-1请求资源

    pool-1-thread-3请求资源

    pool-1-thread-2 释放资源

    pool-1-thread-4获取到资源

    pool-1-thread-4 释放资源

    pool-1-thread-5获取到资源

    pool-1-thread-5 释放资源

    pool-1-thread-1获取到资源

    pool-1-thread-1 释放资源

    pool-1-thread-3获取到资源

    pool-1-thread-3 释放资源

    由结果可以看出,只有当一个线程释放资源之后,才允许一个等待的资源获取到资源,这样便实现了类似加锁的操作。

    在进行线程操作的过程中需要根据实际情况选取不同的锁机制来对线程进行控制,以保证数据、执行逻辑的正确!!!无论是使用synchronized锁还是使用jdk提供的锁亦或自定义锁,都要清晰明确使用锁的最终目的是什么,各种锁的特性是什么,使用场景分别是什么?这样才能够在线程中熟练运用各种锁。

    =========================================

    =========================================

    ----end

    展开全文
  • java中多线程的使用是非常频繁的,而且它的作用也是很明显的。很多人可能对于java中多线程的一些知识不是很了解,今天就来详细简述一下java多线程的优缺点,一起来看看吧。首先我们需要知道的是,java 中使用...

    java中多线程的使用是非常频繁的,而且它的作用也是很明显的。很多人可能对于java中多线程的一些知识不是很了解,今天就来详细简述一下java多线程的优缺点,一起来看看吧。

    首先我们需要知道的是,java 中使用synchronized是用来表示该资源或者该方法是不能进行多个线程的共享的,所以当多个线程都在请求该资源的时候,就跟串行是一样的也就是单线程效果一样,但是当不为共享的时候就可以利用并发来大大的提高系统的效率。

    优缺点如下:

    1.多线程并不解决软件执行效率和你硬件系统的瓶颈,它只是达到更高效使用你的硬件系统资源。

    2.从本质上来说,单核单CPU不存在并行,即使多线程也是串行,多线程不会提高CPU利用率。

    3.对于同步块与单线程执行比较,并不存在多少性能差异,如果相比较,同步块的执行效率还要大大低于单线程程序,如果设计差,可能还是灾难级的。因为同步本身就需要占用系统资源和CPU片段,而且每个线程自己也需要占用资源,如果多线程达不到多线程的优势,那么它本身就会降低效率。

    其实设计synchronized的目的是在使用多线程的时候从安全性方面去考虑的,例如在一个方法中修改一个全局变量,如果使用单线程当然是没什么问题的,一个线程进来我就自己一个人修改,也不会影响什么。

    但是如果在多线程环境中,很多线程同时访问这个方法修改全局变量,那么就可能会出问题了,可能你争我抢(分配到CPU资源)把这个全局变量改的不成样子了,所以考虑安全性,我们使用synchronized。

    如果有多个线程,表示你们得一个一个排队来,如果我进去方法里面了,那么我就把门给锁上,只有我一个在里面修改全局变量,你们都不能进来,等我处理完成了,我再释放锁,然后让下一个进来修改。

    这样也就确保了全局变量不会被改的乱七八糟不成样子了,因为很多项目中会有这样的需求,所以我们得根据需求来确定相应的解决方案。

    举例说明,你有100份英语卷子,有听力,选择题和作文三部分;每部分做完之后,必须把100份卷子都交上来以后,才能拿下一部分。一起拿下一部分的动作就叫做同步,你是拿叫一个同学做,还是叫100个同学做呢?(单线程,还是多线程)

    一个进程中如果有多个线程,那么执行多线程中的非同步代码时比一个进程只有一个线程快。如果进程中有多个线程但是每个线程的所有代码都需要同步的话就和进程只有一个线程的效率是相同的。

    总的来说java中线程的优缺点还是很明显的,上文也对于java的线程进行了详细的解析。java中关于线程的知识点还是非常详细的。想要了解更多java基础知识,敬请关注奇Q工具网。

    推荐阅读:

    展开全文
  • 在分布式集群系统的开发中,线程锁往往并不能支持全部场景的使用,必须引入新的技术方案分布式锁。线程锁:大家都不陌生,主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有有一个...
  • Java中有哪些锁

    2021-03-05 18:57:33
    1.公平 / 非公平2.可重入 / 不可重入3....非公平非公平是指线程获取的顺序并不是按照申请的顺序,可能后申请的线程比先申请的线程优先获取可能,会造成优先级反转或者饥饿现象。对于...
  • 非公平是指线程获取的顺序,并不是按照申请的顺序,可能后申请的线程比先申请的线程优先获取可能,会造成优先级反转或者饥饿现象。独享/共享独享是指该一次只能被一个线程所持有。共享...
  • 多线程

    2021-03-19 12:35:27
    多线程 1.什么是线程和进程? 线程与进程的关系,区别及优缺点? 进程是系统资源分配和调度的基本单位,线程是cpu资源分配和调度的基本单位, 一个程序至少一个进程,一个进程至少一个线程。 进程独立的内存单元...
  • 多线程中 synchronized 升级的原理是什么? 什么是升级(膨胀)?  JVM优化synchronized的运行机制,当JVM检测到不同的竞争状态时,就会根据需要自动切换到合适的,这种切换就是的升级。升级是不可逆的,...
  • 多线程基本原理 线程的合理使用能够提升程序的处理性能,主要两个方面,第一个是能够利用多核 cpu 以及超线程技术来实现线程的并行执行;第二个是线程的异步化执行相比于同步执行来说,异步执行能够很好的优化...
  • java多线程的应用

    2021-06-11 19:59:58
    应用于java多线程中的同步机制,我们知道线程安全的问题大部分是由于多个线程并发的访问共享变量或共享数据。于是我们想到将并发访问变为串行访问,既一次只能一个线程访问共享数据。这就是的思想 如果你学过...
  • 你知道的越,不知道的就越,业余的像一棵小草!你来,我们一起精进!你不来,我和你的竞争对手一起精进!编辑:业余草cnblogs.com/intsmaze/p/6384105.html推荐...
  • 一、多线程情况下的线程安全问题先理解一个概念:线程安全:多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象...
  • 一、线程的基本概念1.1 单线程简单的说,单线程就是进程中只有一个线程。单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。Java示例:public class SingleThread {public ...
  • 线程之间的锁有:互斥、条件、自旋、读写、递归。一般而言,的功能越强大,性能就会越低。 1、互斥 互斥用于控制线程对他们之间共享资源互斥访问的一个信号量。也就是说是为了避免线程在...
  • 本文主要讲解python多线程:如何实现多线程,递归,互斥,信号量,事件等。
  • 一、在 Python 使用多线程 二、互斥&防止死锁 一、在 Python 使用多线程 import threading import time import random class MyThread(threading.Thread): def __init__(self, name, mutex, arg): ...
  • 如果在一个程序中有多线程读取某个变量,使用互斥量时也必须排队。而实际上若只是读取一个变量,是可以让线程同时访问的,这样互斥量就会降低程序的性能。 lockForRead():以只读方式锁定资源,如果其他...
  • 示例描述: 通过两个线程将 公共值 i 从 0 加到 10 先看一下没加锁的代码及输出: #include<iostream> #include<thread> using namespace std; int i = 0; // 公共数据 // 线程1函数 void aaa() { ...
  • 介绍的内容如下:公平/非公平可重入独享/共享互斥/读写乐观/悲观分段偏向/轻量级/重量级自旋上面是很多锁的名词,这些分类并不是全是指的状态,的指的特性,的指的设计,下面...
  • 一般情况下,只要涉及到多线程编程,程序的复杂性就会显著上升,性能显著下降,BUG出现的概率大大提升。 多线程编程本意是将一段程序并行运行,提升数据处理能力,但是由于大部分情况下都涉及到共有资源的竞争,...
  • 使用python的多线程锁lock。 例子: 未使用多线程锁lock: lock = threading.Lock() def a(): for i in range(3): print('a%d' % (i + 1)) time.sleep(1) def b(): for i in range(3): print('b%d' % (i
  • 随着互联网的蓬勃发展,越来越的互联网企业面临着...也就是说,若某一线程获取后,便进入偏向模式,当线程再次请求这个时,就无需再进行相关的同步操作了,从而节约了操作时间,如果在此之间其他的线程...
  • C++线程中的几类

    2021-11-18 22:17:05
      多线程中的主要五类:互斥、条件、自旋、读写、递归。一般而言,所得功能与性能成反比。而且我们一般不使用递归(C++提供std::recursive_mutex),这里不做介绍。 互斥   ==互斥用于控制...
  • 多线程的优势之一就是线程之间可以共享数据,但我们需要一套规则规定哪个线程可以访问哪部分数据,什么时候可以访问,以及怎么通知其他关心该数据的线程已经更新了数据,如果不能处理好数据共享的问题,多线程的这个...
  • day12【多线程机制、lock

    多人点赞 2021-08-04 12:20:05
    一、多线程概念 我们之前在进行程序设计时,程序都是由上往下依次执行,无法让多个应用程序同时执行。不管是我们电脑上还是手机中的应用程序,我们都可以让多个应用程序同时执行。例如一边运行QQ,一边运行酷狗音乐...
  • 为了解决多线程的线程安全问题,从而引入了的概念。 的分类 Java中往往是按照是否含有某一特性来定义,这是java中根据的特性进行分类的分类图: 1,乐观、悲观 乐观和悲观是广义上的概
  • 2个线程同时改变一个全局变量counter的时候,需要加一个。#include #include using namespace std;#define NLOOP 5000int counter=0;pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;void *doit(void ...
  • 由于等待一个锁定线程只有在获得这把之后,才能恢复运行,所以让持有线程在不需要的时候及时释放是很重要的。在以下情况下,持有线程会释放:1. 执行完同步代码块。2. 在执行同步代码块的过程中,...
  • 我们这样一个问题:在一个多线程程序中创建子进程并且让子线程和子进程去获取一把全局变量的,输出子线程得到,然后解锁,子进程拿到,然后解锁; (一)初次尝试 代码: #include <stdio.h> #...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 631,087
精华内容 252,434
关键字:

多线程的锁有哪些