精华内容
下载资源
问答
  • Java多线程锁对象的改变

    千次阅读 2016-11-22 15:33:01
    在将任何数据类型作为同步时,需要注意的是,是否有线程同时持有对象,如果同时持有相同的对象,则这些线程之间就是同步的;如果分别获得对象,这些线程之间就是异步的。 这个时候线程A和B持有的都是...

    在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,这些线程之间就是异步的。

    这里写图片描述
    这里写图片描述

    这个时候线程A和B持有的锁都是”123”,虽然将锁改成了”456”,但结果还是同步的,因为A和B共同争抢的锁是”123”。

    把Thread.sleep(50)放开
    这里写图片描述

    这个时候A取得的锁是”123”,过了50毫秒之后B线程取得的锁上”456”。所以是异步的。

    友情提示:只要对象不变,即使对象的属性被改变,运行的结果还是同步的。

    展开全文
  • java多线程的15种

    万次阅读 多人点赞 2019-05-05 19:19:24
    1 java锁分类 下面我们依照序号依次的介绍每一种 2 悲观和乐观 悲观和乐观是一种广义的概念,体现的是看待线程同步的不同的角度 悲观认为自己在使用数据的时候,一定有别的线程来修改数据,在获取...

    1 java锁分类

    下面我们依照序号依次的介绍每一种锁

    2 悲观锁和乐观锁

    悲观锁和乐观锁是一种广义的概念,体现的是看待线程同步的不同的角度

    悲观锁认为自己在使用数据的时候,一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不会被别的线程修改。

    锁实现:关键字synchronized、接口Lock的实现类

    使用的场景:写操作较多,先加锁可以保证写操作是数据正确

    乐观锁认为自己在使用数据的时候不会有其他的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
    锁实现:CAS算法
    使用场景:读操作较多,不加锁的特点能够使其读操作的性能大幅提升

    3 阻塞和自旋锁

    是指当一个线程在获取锁的饿时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断判断是否能够被成功获取,自旋知道获取到锁才会退出循环。自旋是通过CAS算法进行的。何为CAS算法呢?

    CAS(compare and swap):比较和交换,顾名思义就是先进行比较然后在进行交换。这里比较和交换是线程中的数据和内存中的数据之间的操作。

    如下图所示:1 线程1和线程2都读取内存中的数据V赋值给A 2 线程1把V的值由0改为了1,并想把修改后的值写回到内存 3 线程1将A的值和V的值进行比较 4 两者相等,说明没有线程对V的值进行修改,直接把修改后的值(B=1)写入内存,此时,V=1。3 线程2进行将A的值和V的值进行比较 5 两者不相等,说明有线程对V的值进行修改,此时线程2不能够把修改后的值写入内存,因为它获得的A的值不是最新的,由A得到的B的值也可能是错误的。线程2会读取A的值,重新计算出B的值,再尝试重新写入,如果还是不相等在继续尝试,不断的自旋。

    我们发现CAS算法存在一个非常明显的缺陷,那就是ABA问题。何为ABA问题呢?

    如下图所示:线程1线程2 线程3 都获取A=V=0  1 线程1修改V的值为1 写入内存 2 线程2 把v的值改为2,但是没来的及写入,线程3 就开始运行 3 线程3 将V的值改为0 写入内存 4 线程2 比较A和V的值发现A=V,他自认为没有其他的线程对V进行修改,因而忽略了A->B->A的过程,形成了ABA问题。ABA的问题解决方法很简单:AtomicStampedReference在变量前面添加版本号,每次变量更新的时候都把版本号加一。

     

    我们知道自旋是会消耗CPU资源的(不断循环),为什么不用阻塞的方式使等待的线程停止工作呢?原因有两个

    (1)同步代码块逻辑简单情况下,自旋消耗的资源时很少的(在同步代码块逻辑简单的情况下,用自旋是比较合适的)

    (2)最为关键的原因是阻塞与唤醒线程需要操作系统切换CPU状态,需要消耗一定的时间(CPU上下文切换)

    那么为什么阻塞和唤醒线程会消耗大量时间呢?

    因为线程的阻塞唤醒涉及大量的步骤,我们以线程阻塞为例进行说明

    步骤1:线程1获取CPU时间片执行

    步骤2:线程1被阻塞,上下文切换

    步骤3:线程2抢占CPU时间片执行

    步骤2涉及两个子步骤:

    步骤2.1 从用户态切换到内核态

    为什么要进行这种切换呢?

    用户是没有权限对内存进行操作,我们要切换到内核态才有权对内存进行操作。

    步骤2.2 把线程1的状态保存到PCB(内存)

    我们到底要保存线程的哪些信息呢?

    一个线程在运行时的内存模型主要有5个部分组成:(1)程序计数器:记录下一条指令的地址(2)虚拟机栈:保存函数的信息,例如,局部的变量,函数返回地址,操作数等(3)本地方法栈:和虚拟机栈类似,不过其保存的函数的信息是native函数(4)方法区:保存类的信息,静态变量等(5)堆:实例化的对象(堆是用户申请的(C语言中malloc函数),而栈是系统自动分配的)。很明显:程序计数器、虚拟机栈、方法区、堆等信息都会被保存到内存中。

    可见上下文切换是非常耗时的。

    4 无锁、偏向锁、轻量级锁、重量级锁

    随着竞争不断的加剧,锁要不断的升级。

    (1)无锁:适用于单线程(2)偏向锁:适用于只有一个线程访问同步块的情况,因为多个线程同时访问同步块,给某一个线程特权是不合理的(3)轻量级锁:竞争不是太多,循环等待消耗CPU资源的线程的数量在可接受的范围(4)重量级锁:多个线程同时竞争资源,只让一个线程运行,其余的线程都阻塞

    5 可重入锁和不可重入锁

    可重入锁:一个线程可以多次获得同一把锁并通过state记录加了多少锁

    6 公平锁和非公平锁

    公平锁和非公平锁主要是关于等待的线程的排队的问题,这个排队要利用AQS(AbstractQueuedSynchronizer)

    我们以重入锁(ReentrantLock)为例解释AQS。AQS由三个部分组成,State:当前线程锁的个数、exclusiveOwerThread:当前占有锁的线程 、CLH队列等待运行的线程。

    1 线程1 CAS算法A=V(state)=0,修改state的值为1 2 线程1又想获取锁,此时A=V(state)=1,state再加1,无论A想获得多少次,只是state+1 3 线程2 进行CAS比较,发现A不等于V,并且发现state不等于0,直接到CLH列队中等待。线程3和线程4也一样到CLH队列中等待。如果先来的线程先排队,获取锁的优先权,则为公平锁。如果,无视等待队列,直接尝试获取锁,则为非公平锁。

    队列如果已经满了,该怎么办呢?

    无法进入队列的线程,进入ArrayBlockingQueue,等队列有空位再进入队列

    7 互斥锁和共享锁

    互斥锁:在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

    共享锁:共享锁从字面来看也即是允许多个线程共同访问资源。

    互斥锁很容易理解,上锁之后,其他线程都阻塞了。

    共享锁一个典型的例子就是读者写者模式,一个人写多个人去读,写者写出的东西,多个读者是可以一块去读的,这就是多个读者共享一个同步资源。

    共享锁--用到共享锁的有Semapore信号量和ReadLock读锁

    展开全文
  • Java多线程的理解与使用

    万次阅读 多人点赞 2017-10-14 18:45:51
    作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等 )

    1.简介

    锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等 ) 。

    2.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里面的同步原语synchronized关键字的实现就是悲观锁。

    乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的。

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。当需要put元素的时候,并不是对整个HashMap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。但是,在统计size的时候,可就是获取HashMap全局信息的时候,就需要获取所有的分段锁才能统计。

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

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

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

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

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

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

    自旋锁

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

    3.锁的使用

    1.预备知识

    1.AQS

    AbstractQueuedSynchronized 抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch…
    image

    AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。state的访问方式有三种:

    getState()
    setState()
    compareAndSetState()

    AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

    不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

    isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
    tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
    tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
    tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
    tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false

    以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

    再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

    一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

    2.CAS

    CAS(Compare and Swap 比较并交换)是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。   

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

    JAVA对CAS的支持:

    在JDK1.5中新增java.util.concurrent包就是建立在CAS之上的。相对于对于synchronized 这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以java.util.concurrent在性能上有了很大的提升。

    以java.util.concurrent包中的AtomicInteger为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解 getAndIncrement方法,该方法的作用相当于 ++i 操作。

    public class AtomicInteger extends Number implements java.io.Serializable {  
        private volatile int value; 
    
        public final int get() {  
            return value;  
        }  
    
        public final int getAndIncrement() {  
            for (;;) {  
                int current = get();  
                int next = current + 1;  
                if (compareAndSet(current, next))  
                    return current;  
            }  
        }  
    
        public final boolean compareAndSet(int expect, int update) {  
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
        }  
    }

    2.实战

    1.synchronized

    synchronized可重入锁验证

    //import java.util.concurrent.locks.ReentrantLock;
    
    public class Test implements Runnable{
    
        //private ReentrantLock reentrantLock = new ReentrantLock();
    
        public synchronized void get(){
            System.out.println("2 enter thread name-->" + Thread.currentThread().getName());
            //reentrantLock.lock();
            System.out.println("3 get thread name-->" + Thread.currentThread().getName());
            set();
            //reentrantLock.unlock();
            System.out.println("5 leave run thread name-->" + Thread.currentThread().getName());
        }
    
        public synchronized void set(){
            //reentrantLock.lock();
            System.out.println("4 set thread name-->" + Thread.currentThread().getName());
            //reentrantLock.unlock();
        }
    
        @Override
        public void run() {
            System.out.println("1 run thread name-->" + Thread.currentThread().getName());
            get();
        }
    
        public static void main(String[] args){
            Test test = new Test();
            for(int i = 0; i < 10; i++){
                new Thread(test, "thread-" + i).start();
            }
        }
    }
    

    运行结果

    1 run thread name-->thread-0
    1 run thread name-->thread-3
    1 run thread name-->thread-2
    1 run thread name-->thread-1
    2 enter thread name-->thread-0
    3 get thread name-->thread-0
    1 run thread name-->thread-4
    4 set thread name-->thread-0
    5 leave run thread name-->thread-0
    2 enter thread name-->thread-4
    3 get thread name-->thread-4
    1 run thread name-->thread-7
    4 set thread name-->thread-4
    1 run thread name-->thread-6
    1 run thread name-->thread-5
    5 leave run thread name-->thread-4
    1 run thread name-->thread-8
    1 run thread name-->thread-9
    2 enter thread name-->thread-1
    3 get thread name-->thread-1
    4 set thread name-->thread-1
    5 leave run thread name-->thread-1
    2 enter thread name-->thread-2
    3 get thread name-->thread-2
    4 set thread name-->thread-2
    5 leave run thread name-->thread-2
    2 enter thread name-->thread-3
    3 get thread name-->thread-3
    4 set thread name-->thread-3
    5 leave run thread name-->thread-3
    2 enter thread name-->thread-9
    3 get thread name-->thread-9
    4 set thread name-->thread-9
    5 leave run thread name-->thread-9
    2 enter thread name-->thread-8
    3 get thread name-->thread-8
    4 set thread name-->thread-8
    5 leave run thread name-->thread-8
    2 enter thread name-->thread-5
    3 get thread name-->thread-5
    4 set thread name-->thread-5
    5 leave run thread name-->thread-5
    2 enter thread name-->thread-6
    3 get thread name-->thread-6
    4 set thread name-->thread-6
    5 leave run thread name-->thread-6
    2 enter thread name-->thread-7
    3 get thread name-->thread-7
    4 set thread name-->thread-7
    5 leave run thread name-->thread-7
    

    get()方法中顺利进入了set()方法,说明synchronized的确是可重入锁。分析打印Log,thread-0先进入get方法体,这个时候thread-3、thread-2、thread-1等待进入,但当thread-0离开时,thread-4却先进入了方法体,没有按照thread-3、thread-2、thread-1的顺序进入get方法体,说明sychronized的确是非公平锁。而且在一个线程进入get方法体后,其他线程只能等待,无法同时进入,验证了synchronized是独占锁。

    2.ReentrantLock

    ReentrantLock既可以构造公平锁又可以构造非公平锁,默认为非公平锁,将上面的代码改为用ReentrantLock实现,再次运行。

    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test implements Runnable{
    
        private ReentrantLock reentrantLock = new ReentrantLock();
    
        public void get(){
            System.out.println("2 enter thread name-->" + Thread.currentThread().getName());
            reentrantLock.lock();
            System.out.println("3 get thread name-->" + Thread.currentThread().getName());
            set();
            reentrantLock.unlock();
            System.out.println("5 leave run thread name-->" + Thread.currentThread().getName());
        }
    
        public void set(){
            reentrantLock.lock();
            System.out.println("4 set thread name-->" + Thread.currentThread().getName());
            reentrantLock.unlock();
        }
    
        @Override
        public void run() {
            System.out.println("1 run thread name-->" + Thread.currentThread().getName());
            get();
        }
    
        public static void main(String[] args){
            Test test = new Test();
            for(int i = 0; i < 10; i++){
                new Thread(test, "thread-" + i).start();
            }
        }
    }
    

    运行结果

    1 run thread name-->thread-0
    2 enter thread name-->thread-0
    3 get thread name-->thread-0
    4 set thread name-->thread-0
    1 run thread name-->thread-1
    2 enter thread name-->thread-1
    1 run thread name-->thread-2
    2 enter thread name-->thread-2
    1 run thread name-->thread-3
    2 enter thread name-->thread-3
    5 leave run thread name-->thread-0
    3 get thread name-->thread-2
    4 set thread name-->thread-2
    5 leave run thread name-->thread-2
    3 get thread name-->thread-1
    4 set thread name-->thread-1
    5 leave run thread name-->thread-1
    3 get thread name-->thread-3
    4 set thread name-->thread-3
    5 leave run thread name-->thread-3
    1 run thread name-->thread-4
    1 run thread name-->thread-5
    2 enter thread name-->thread-5
    3 get thread name-->thread-5
    4 set thread name-->thread-5
    5 leave run thread name-->thread-5
    1 run thread name-->thread-6
    2 enter thread name-->thread-6
    3 get thread name-->thread-6
    4 set thread name-->thread-6
    5 leave run thread name-->thread-6
    2 enter thread name-->thread-4
    3 get thread name-->thread-4
    4 set thread name-->thread-4
    5 leave run thread name-->thread-4
    1 run thread name-->thread-7
    2 enter thread name-->thread-7
    3 get thread name-->thread-7
    4 set thread name-->thread-7
    5 leave run thread name-->thread-7
    1 run thread name-->thread-8
    2 enter thread name-->thread-8
    3 get thread name-->thread-8
    4 set thread name-->thread-8
    1 run thread name-->thread-9
    2 enter thread name-->thread-9
    3 get thread name-->thread-9
    4 set thread name-->thread-9
    5 leave run thread name-->thread-9
    5 leave run thread name-->thread-8

    的确如其名,可重入锁,当然默认的确是非公平锁。thread-0持有锁期间,thread-1、thread-2、thread-3等待拥有锁,当thread-0释放锁时thread-2先获取到锁,并非按照先后顺序获取锁的。

    将其构造为公平锁,看看运行结果是否符合预期。查看源码构造公平锁很简单,只要在构造器传入boolean值true即可。

        /**
         * Creates an instance of {@code ReentrantLock} with the
         * given fairness policy.
         *
         * @param fair {@code true} if this lock should use a fair ordering policy
         */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }

    修改上面例程的代码构造方法为:

    ReentrantLock reentrantLock = new ReentrantLock(true);

    如果使用了IntelliJ IDEA IDE可以看到在true前面还有个fair提示。

    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test implements Runnable{
    
        private ReentrantLock reentrantLock = new ReentrantLock(true);
    
        public void get(){
            System.out.println("2 enter thread name-->" + Thread.currentThread().getName());
            reentrantLock.lock();
            System.out.println("3 get thread name-->" + Thread.currentThread().getName());
            set();
            reentrantLock.unlock();
            System.out.println("5 leave run thread name-->" + Thread.currentThread().getName());
        }
    
        public void set(){
            reentrantLock.lock();
            System.out.println("4 set thread name-->" + Thread.currentThread().getName());
            reentrantLock.unlock();
        }
    
        @Override
        public void run() {
            System.out.println("1 run thread name-->" + Thread.currentThread().getName());
            get();
        }
    
        public static void main(String[] args){
            Test test = new Test();
            for(int i = 0; i < 10; i++){
                new Thread(test, "thread-" + i).start();
            }
        }
    }
    

    运行结果

    1 run thread name-->thread-1
    1 run thread name-->thread-0
    2 enter thread name-->thread-0
    3 get thread name-->thread-0
    4 set thread name-->thread-0
    1 run thread name-->thread-2
    5 leave run thread name-->thread-0
    2 enter thread name-->thread-1
    3 get thread name-->thread-1
    4 set thread name-->thread-1
    2 enter thread name-->thread-2
    1 run thread name-->thread-3
    5 leave run thread name-->thread-1
    2 enter thread name-->thread-3
    3 get thread name-->thread-2
    4 set thread name-->thread-2
    1 run thread name-->thread-7
    2 enter thread name-->thread-7
    1 run thread name-->thread-5
    5 leave run thread name-->thread-2
    2 enter thread name-->thread-5
    3 get thread name-->thread-3
    4 set thread name-->thread-3
    5 leave run thread name-->thread-3
    3 get thread name-->thread-7
    4 set thread name-->thread-7
    5 leave run thread name-->thread-7
    3 get thread name-->thread-5
    4 set thread name-->thread-5
    5 leave run thread name-->thread-5
    1 run thread name-->thread-8
    2 enter thread name-->thread-8
    3 get thread name-->thread-8
    1 run thread name-->thread-9
    2 enter thread name-->thread-9
    4 set thread name-->thread-8
    3 get thread name-->thread-9
    4 set thread name-->thread-9
    5 leave run thread name-->thread-9
    5 leave run thread name-->thread-8
    1 run thread name-->thread-4
    2 enter thread name-->thread-4
    3 get thread name-->thread-4
    4 set thread name-->thread-4
    5 leave run thread name-->thread-4
    1 run thread name-->thread-6
    2 enter thread name-->thread-6
    3 get thread name-->thread-6
    4 set thread name-->thread-6
    5 leave run thread name-->thread-6
    

    公平锁在多个线程想要同时获取锁的时候,会发现再排队,按照先来后到的顺序进行。

    3.ReentrantReadWriteLock

    读写锁的性能都会比排它锁要好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。Java并发包提供读写锁的实现是ReentrantReadWriteLock。

    特性说明
    公平性选择支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平
    重进入该锁支持重进入,以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁
    锁降级遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class Test {
    
        public static void main(String[] args){
            for(int i = 0; i < 10; i++){
                new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        Cache.put("key", new String(Thread.currentThread().getName() + " joke"));
                    }
                }, "threadW-"+ i).start();
                new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        System.out.println(Cache.get("key"));
                    }
                }, "threadR-"+ i).start();
                new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        Cache.clear();
                    }
                }, "threadC-"+ i).start();
            }
    
        }
    }
    
    class Cache {
        static Map<String, Object> map = new HashMap<String, Object>();
        static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        static Lock r = rwl.readLock();
        static Lock w = rwl.writeLock();
        // 获取一个key对应的value
        public static final Object get(String key) {
            r.lock();
            try {
                System.out.println("get " + Thread.currentThread().getName());
                return map.get(key);
            } finally {
                r.unlock();
            }
        }
        // 设置key对应的value,并返回旧有的value
        public static final Object put(String key, Object value) {
            w.lock();
            try {
                System.out.println("put " + Thread.currentThread().getName());
                return map.put(key, value);
            } finally {
                w.unlock();
            }
        }
        // 清空所有的内容
        public static final void clear() {
            w.lock();
            try {
                System.out.println("clear " + Thread.currentThread().getName());
                map.clear();
            } finally {
                w.unlock();
            }
        }
    }
    

    运行结果

    clear threadC-0
    get threadR-2
    null
    put threadW-2
    get threadR-0
    threadW-2 joke
    get threadR-3
    threadW-2 joke
    clear threadC-1
    put threadW-3
    clear threadC-3
    clear threadC-2
    get threadR-1
    null
    put threadW-1
    put threadW-0
    put threadW-4
    get threadR-4
    threadW-4 joke
    clear threadC-4
    get threadR-5
    null
    put threadW-5
    put threadW-6
    get threadR-6
    threadW-6 joke
    get threadR-7
    threadW-6 joke
    put threadW-7
    clear threadC-6
    put threadW-8
    get threadR-8
    threadW-8 joke
    clear threadC-8
    get threadR-9
    null
    clear threadC-5
    clear threadC-9
    clear threadC-7
    put threadW-9
    

    可看到普通HashMap在多线程中数据可见性正常。

    参考资料

    1.http://ifeve.com/java-art-reentrantlock/

    展开全文
  • java多线程锁

    千次阅读 2012-05-11 14:56:56
    我们已经了解了Java多线程编程中常用的关键字synchronized,以及与之相关的对象机制。这一节中,让 我们一起来认识JDK 5中新引入的并发框架中的机制。 我想很多购买了《Java程序员面试宝典》之类图书的朋友...

    本文转自:http://www.blogjava.net/zhangwei217245/archive/2010/04/08/315526.html

    在上一节中,

    我们已经了解了Java多线程编程中常用的关键字synchronized,以及与之相关的对象锁机制。这一节中,让 我们一起来认识JDK 5中新引入的并发框架中的锁机制。

    我想很多购买了《Java程序员面试宝典》之类图书的朋友一定对下面 这个面试题感到非常熟悉:

    问:请对比synchronized与java.util.concurrent.locks.Lock 的异同。
    答案:主要相同点:Lock能完成synchronized所实现的所有功能
         主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放 锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

    恩,让我们先鄙视一下应试教育。

    言归正传,我们先来看一个多线程程序。它使用多个线程对一个Student对象进行访问,改变其中的变量值。 我们首先用传统的synchronized 机制来实现它:

    public   class  ThreadDemo  implements  Runnable {

        
    class  Student {

            
    private   int  age  =   0 ;

            
    public   int  getAge() {
                
    return  age;
            }

            
    public   void  setAge( int  age) {
                
    this .age  =  age;
            }
        }
        Student student 
    =   new  Student();
        
    int  count  =   0 ;

        
    public   static   void  main(String[] args) {
            ThreadDemo td 
    =   new  ThreadDemo();
            Thread t1 
    =   new  Thread(td,  " a " );
            Thread t2 
    =   new  Thread(td,  " b " );
            Thread t3 
    =   new  Thread(td,  " c " );
            t1.start();
            t2.start();
            t3.start();
        }

        
    public   void  run() {
            accessStudent();
        }

        
    public   void  accessStudent() {
            String currentThreadName 
    =  Thread.currentThread().getName();
            System.out.println(currentThreadName 
    +   "  is running! " );
            
    synchronized  ( this ) { // (1)使用同一个ThreadDemo对象作为同步锁
                System.out.println(currentThreadName  +   "  got lock1@Step1! " );
                
    try  {
                    count
    ++ ;
                    Thread.sleep(
    5000 );
                } 
    catch  (Exception e) {
                    e.printStackTrace();
                } 
    finally  {
                    System.out.println(currentThreadName 
    +   "  first Reading count: "   +  count);
                }

            }
           
            System.out.println(currentThreadName 
    +   "  release lock1@Step1! " );

            
    synchronized  ( this ) { // (2)使用同一个ThreadDemo对象作为同步锁
                System.out.println(currentThreadName  +   "  got lock2@Step2! " );
                
    try  {
                    Random random 
    =   new  Random();
                    
    int  age  =  random.nextInt( 100 );
                    System.out.println(
    " thread  "   +  currentThreadName  +   "  set age to: "   +  age);

                    
    this .student.setAge(age);

                    System.out.println(
    " thread  "   +  currentThreadName  +   "  first  read age is: "   +   this .student.getAge());

                    Thread.sleep(
    5000 );
                } 
    catch  (Exception ex) {
                    ex.printStackTrace();
                } 
    finally {
                    System.out.println(
    " thread  "   +  currentThreadName  +   "  second read age is: "   +   this .student.getAge());
                }

            }
            System.out.println(currentThreadName 
    +   "  release lock2@Step2! " );
        }
    }
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/ 
    运行结果:

    a is running!
    a got lock1@Step1!
    b is running!
    c is running!
    a first Reading count:
    1
    a release lock1@Step1!
    a got lock2@Step2!
    thread a set age to:
    76
    thread a first  read age is:
    76
    thread a second read age is:
    76
    a release lock2@Step2!
    c got lock1@Step1!
    c first Reading count:
    2
    c release lock1@Step1!
    c got lock2@Step2!
    thread c set age to:
    35
    thread c first  read age is:
    35
    thread c second read age is:
    35
    c release lock2@Step2!
    b got lock1@Step1!
    b first Reading count:
    3
    b release lock1@Step1!
    b got lock2@Step2!
    thread b set age to:
    91
    thread b first  read age is:
    91
    thread b second read age is:
    91
    b release lock2@Step2!
    成功生成(总时间:
    30  秒)
    显然,在这个程序中,由于两段synchronized块使用了同样的对象做为对象锁,所以JVM优先使刚刚释放该锁的线程重新获得该 锁。这样,每个线程执行的时间是10秒钟,并且要彻底把两个同步块的动作执行完毕,才能释放对象锁。这样,加起来一共是 30秒 转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/ 
    我想一定有人会说:如果两段synchronized块采用两个不同的对象锁,就可以提高程序的并发性,并且,这 两个对象锁应该选择那些被所有线程所共享的对象。

    那么好。我们把第二个同步块中的对象锁改为student(此处略去代码,读 者自己修改),程序运行结果为:

    a is running!
    a got lock1@Step1!
    b is running!
    c is running!
    a first Reading count:
    1
    a release lock1@Step1!
    a got lock2@Step2!
    thread a set age to:
    73
    thread a first  read age is:
    73
    c got lock1@Step1!
    thread a second read age is:
    73
    a release lock2@Step2!
    c first Reading count:
    2
    c release lock1@Step1!
    c got lock2@Step2!
    thread c set age to:
    15
    thread c first  read age is:
    15
    b got lock1@Step1!
    thread c second read age is:
    15
    c release lock2@Step2!
    b first Reading count:
    3
    b release lock1@Step1!
    b got lock2@Step2!
    thread b set age to:
    19
    thread b first  read age is:
    19
    thread b second read age is:
    19
    b release lock2@Step2!
    成功生成(总时间:
    21  秒)
    从 修改后的运行结果来看,显然,由于同步块的对象锁不同了,三个线程的执行顺序也发生了变化。在一个线程释放第一个同步块的同步锁之 后,第二个线程就可以进入第一个同步块,而此时,第一个线程可以继续执行第二个同步块。这样,整个执行过程中,有10秒钟 的时间是两个线程同时工作的。另外十秒钟分别是第一个线程执行第一个同步块的动作和最后一个线程执行第二个同步块的动作。相比较第一 个例程,整个程序的运行时间节省了1/3。细心的读者不难总结出优化前后的执行时间比例公式:(n+1)/2n,其中n为 线程数。如果线程数趋近于正无穷,则程序执行效率的提高会接近50%。而如果一个线程的执行阶段被分割成m个 synchronized块,并且每个同步块使用不同的对象锁,而同步块的执行时间恒定,则执行时间比例公式可以写作:((m- 1)n+1)/mn那么当m趋于无穷大时,线程数n趋近于无穷大,则程序执行效率的提升几乎可以达到100%。(显然,我 们不能按照理想情况下的数学推导来给BOSS发报告,不过通过这样的数学推导,至少我们看到了提高多线程程序并发性的一种方案,而 这种方案至少具备数学上的可行性理论支持。)
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/ 
    可见,使用不同的对象锁,在不同的同步块中完成任务,可以使性能大大提升。

    很多人看到这不禁要问:这和新的Lock框 架有什么关系?

    别着急。我们这就来看一看。

    synchronized块的确不错,但是他有一些功能性的限制:
    1. 它无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。
    2.synchronized 块对于锁的获得和释放是在相同的堆栈帧中进行的。多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些更适合使用 非块结构锁定的情况。 转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/ 
    java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 

    JDK 官方文档中提到:
    ReentrantLock是“一个可重入的互斥锁 Lock,它具有与使用 synchronized  方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
    ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。 ”

    简单来说,ReentrantLock有一个与锁相关的获取计 数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。 
    转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/ 
    ReentrantLock  类(重入锁)实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性 能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。) 
    展开全文
  • 文章目录多线程的升级原理是什么? 多线程的升级原理是什么?
  • Java多线程编程-(1)-线程安全和Synchronized概念

    万次阅读 多人点赞 2017-09-15 14:51:59
    一、进程与线程的概念 (1)在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单位都是进程。 在未配置 OS 的系统中,程序的执行方式是顺序执行,即必须在一个程序执行完后,才允许另一个...
  • java 多线程 降级

    千次阅读 2018-04-19 21:44:22
    如果当前线程拥有写,然后将其释放,最后再获取读,这种并不能称之为降级,降级指的是把持住(当前拥有的)写,再获取到读,随后释放(先前有用的)写的过程。下面给出一个降级的示例,当数据变动时,...
  • Java多线程原理等

    千次阅读 2018-07-08 02:09:58
    Java多线程应用一、 线程创建方式线程创建有二种方式,继承Thread、或者实现Runnable接口。继承Thread。 new Thread();public class MyThread extends Thread{ public MyThread(String name) { super(name); ...
  • JAVA多线程之——对象的理解

    千次阅读 2018-07-28 14:24:24
    个对象 例子: 输出 代码解释: athread.start()后,调用addI(),传入字符”a”,”a set over”后,线程a睡眠了,并未释放a的,bthread.start()后,可以调用addI,因为他们不是一个...
  • Java多线程锁释放

    千次阅读 2016-06-20 23:19:15
    Java多线程锁释放
  • Java 多线程 并发 Java线程面试题

    千次阅读 2018-05-12 00:02:12
    1) 什么是线程?线程是操作系统能够进行运算调度的最小单位,它被...Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点。2) 线程和进程有什么区别?线程是进程的子集,一个进程可以有很多线程,每条线...
  • Java多线程-Lock的使用

    千次阅读 2019-03-15 13:42:08
    文章目录Lock的使用一 Lock接口1.1 Lock接口简介1.2 Lock的简单使用1.3 Lock接口的特性和常见方法二 Lock接口的实现类:ReentrantLock2.1 第一个ReentrantLock程序2.2 Condition接口简介2.3 使用Condition实现等待...
  • [Java多线程 五]---JAVA有哪些种类

    万次阅读 多人点赞 2017-09-02 12:09:25
    转载自: ...上一篇既然提到了,这一篇来详细介绍JAVA中的,也为之后JUC下的做一个铺垫 ... 自旋 ,自旋的其他种类,阻塞,可重入 ,读写 ,互斥 ,悲观 ,乐观 ,公平 ,偏向, 对象线程
  • Java多线程探究-Lock对象条件变量

    千次阅读 2017-04-12 18:08:50
    Lock的条件变量 设想这样的一种情况,现在有一个盘子,一个线程负责往盘子里放一个苹果,一个线程从盘子取一个苹果,如何保证线程A放一个苹果,线程B就把这个苹果取了,不会出现已经放了好几个了,线程B才一个一...
  • 详解Java多线程锁之synchronized

    千次阅读 2019-06-13 08:36:27
    synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。 synchronized的四种使用方式 修饰代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用于调用对象 ...
  • Java多线程的升级

    千次阅读 2018-03-06 19:39:35
    Java多线程的升级 先说说为什么会有锁升级 因为Sycronized是重量级(也是悲观),每次在要进行的请求的时候,如果当前资源被其他线程占有要将当前的线程阻塞加入到阻塞队列,然后清空当前线程的缓存,...
  • Java多线程-6】synchronized同步

    千次阅读 2020-03-31 15:35:49
    前文描述了Java多线程编程,多线程的方式提高了系统资源利用和程序效率,但多个线程同时处理共享的数据时,就将面临线程安全的问题。 例如,下面模拟这样一个场景:一个售票处有3个售票员,出售20张票。 public ...
  • ●保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。 ●禁止进行指令重排序 volatile的可见性 Java内存...
  • 你好我是辰兮,很高兴你能来阅读,本篇文章为大家讲解Java多线程之synchronized关键词,下面有案例的截图和相关代码可以自行实践,相关的更多面试知识已经提前整理好文章可以阅读学习,分享获取新知,希望对Java初学...
  • java多线程锁种类

    千次阅读 2016-03-01 14:50:24
    作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的为我们开发提供了便利,但是的具体性质以及类型却很少被提及。本系列文章将分析JAVA...
  • 详解Java多线程锁之Lock和ReadWriteLock

    千次阅读 2019-06-14 08:00:00
    ReentrantLock是实现了Lock接口的类,属于独享,独享在同一时刻仅有一个线程可以进行访问。Lock接口很简单,实现了如下: public interface Lock { void lock(); void lockInterruptibly() throws ...
  • ReentrantLock属于排他,这些在同一时刻只允许一个线程进行访问,而读写在同一时刻可以允许线程访问,但是在写线程访问时,所有的读和其他写线程都被阻塞。读写维护了一对,一个读和一个写,通过...
  • Java多线程--多个对象多个

    千次阅读 热门讨论 2018-06-04 17:22:05
    上一篇博客中介绍了线程同时访问一个对象,产生一个对象,属于同步访问,现在介绍下如果是访问个对象,会怎么执行那? Demo: HasSelfPrivateNum类: public class HasSelfPrivateNum { private int num=...
  • 1.三个线程同时抢票导致线程不安全 不安全的代码 //测试Lock public class TestLock { public static void main(String[] args) { Testlock2 testlock2 = new Testlock2(); new Thread(testlock2).start(); ...
  • Java 多线程全局与对象

    千次阅读 2018-05-12 17:49:04
      我们看一个例子: class Demo { public synchronized void test() { ...test方法开始执行,当前线程为:&quot;+Thread.currentThread().getName()); try { Thread.sleep(1000); ...
  • Java多线程安全问题和

    千次阅读 2020-02-28 17:32:26
    多线程安全问题和 文章目录多线程安全问题和锁线程在jvm中的特点的出现synchronized 关键字-监视器monitor lock死锁的产生和避免 什么是线程安全问题? 当多个线程同时操作同一个数据是,可能会出现数据不一样...
  • 多线程锁的升级原理是什么?

    万次阅读 多人点赞 2019-05-20 11:04:06
    多线程锁的升级原理是什么? 锁的级别从低到高: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁 锁分级别原因: 没有优化以前,sychronized是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换...
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...
  • Java 多线程加锁的方式总结及对比

    万次阅读 多人点赞 2017-03-28 14:27:53
    一.Java多线程可以通过: 1. synchronized关键字 2. Java.util.concurrent包中的lock接口和ReentrantLock实现类 这两种方式实现加锁。 二.synchronized关键字加锁的缺陷: 如果一个代码块被s
  • 在项目多线程编程中用了ReentrantLock配合Condition来控制线程的加锁和解锁:private void signalAllConnect() { final ReentrantLock lock = this.connectLock; try { lock.lockInterruptibly(); } catch

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 375,813
精华内容 150,325
关键字:

java多线程的锁

java 订阅