您当前的位置:首页 > 互联网教程

java并发包源码怎么读

发布时间:2025-05-22 01:02:57    发布人:远客网络

java并发包源码怎么读

一、java并发包源码怎么读

ReentrantLock感觉上是synchronized的增强版,synchronized的特点是使用简单,一切交给JVM去处理,但是功能上是比较薄弱的。在JDK1.5之前,ReentrantLock的性能要好于synchronized,由于对JVM进行了优化,现在的JDK版本中,两者性能是不相上下的。如果是简单的实现,不要刻意去使用ReentrantLock。

相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。

首先我们通过一个例子来说明ReentrantLock最初步的用法:

import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock= new ReentrantLock(); public static int i= 0;

@Override public void run(){ for(int j= 0; j< 10000000; j++)

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

有两个线程都对i进行++操作,为了保证线程安全,使用了ReentrantLock,从用法上可以看出,与synchronized相比,ReentrantLock就稍微复杂一点。因为必须在finally中进行解锁操作,如果不在finally解锁,有可能代码出现异常锁没被释放,而synchronized是由JVM来释放锁。

那么ReentrantLock到底有哪些优秀的特点呢?

单线程可以重复进入,但要重复退出

由于ReentrantLock是重入锁,所以可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。这模仿了synchronized的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个synchronized块时,才释放锁。

public class Child extends Father implements Runnable{ final static Child child= new Child();//为了保证锁唯一

public static void main(String[] args){ for(int i= 0; i< 50; i++){ new Thread(child).start();

public synchronized void doSomething(){

System.out.println("1child.doSomething()");

doAnotherThing();//调用自己类中其他的synchronized方法

private synchronized void doAnotherThing(){ super.doSomething();//调用父类的synchronized方法

System.out.println("3child.doAnotherThing()");

}class Father{ public synchronized void doSomething(){

System.out.println("2father.doSomething()");

我们可以看到一个线程进入不同的synchronized方法,是不会释放之前得到的锁的。所以输出还是顺序输出。所以synchronized也是重入锁

与synchronized不同的是,ReentrantLock对中断是有响应的。中断相关知识查看[高并发Java二]多线程基础

普通的lock.lock()是不能响应中断的,lock.lockInterruptibly()能够响应中断。

我们模拟出一个死锁现场,然后用中断来处理死锁

package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock1= new ReentrantLock(); public static ReentrantLock lock2= new ReentrantLock(); int lock; public Test(int lock)

lock1.lockInterruptibly(); try

lock2.lockInterruptibly(); try

{ if(lock1.isHeldByCurrentThread())

} if(lock2.isHeldByCurrentThread())

System.out.println(Thread.currentThread().getId()+":线程退出");

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

Thread thread1= new Thread(t1);

Thread thread2= new Thread(t2);

Thread.sleep(1000);//DeadlockChecker.check();

} static class DeadlockChecker

{ private final static ThreadMXBean mbean= ManagementFactory

.getThreadMXBean(); final static Runnable deadlockChecker= new Runnable()

{// TODO Auto-generated method stub

{ long[] deadlockedThreadIds= mbean.findDeadlockedThreads(); if(deadlockedThreadIds!= null)

ThreadInfo[] threadInfos= mbean.getThreadInfo(deadlockedThreadIds); for(Thread t: Thread.getAllStackTraces().keySet())

{ for(int i= 0; i< threadInfos.length; i++)

{ if(t.getId()== threadInfos[i].getThreadId())

Thread t= new Thread(deadlockChecker);

上述代码有可能会发生死锁,线程1得到lock1,线程2得到lock2,然后彼此又想获得对方的锁。

我们用jstack查看运行上述代码后的情况

DeadlockChecker.check();方法用来检测死锁,然后把死锁的线程中断。中断后,线程正常退出。

超时不能获得锁,就返回false,不会永久等待构成死锁

使用lock.tryLock(long timeout, TimeUnit unit)来实现可限时锁,参数为时间和单位。

package test;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock= new ReentrantLock();@Override

{ if(lock.tryLock(5, TimeUnit.SECONDS))

System.out.println("get lock failed");

{ if(lock.isHeldByCurrentThread())

public static void main(String[] args)

使用两个线程来争夺一把锁,当某个线程获得锁后,sleep6秒,每个线程都只尝试5秒去获得锁。

所以必定有一个线程无法获得锁。无法获得后就直接退出了。

public ReentrantLock(boolean fair) public static ReentrantLock fairLock= new ReentrantLock(true);

一般意义上的锁是不公平的,不一定先来的线程能先得到锁,后来的线程就后得到锁。不公平的锁可能会产生饥饿现象。

公平锁的意思就是,这个锁能保证线程是先来的先得到锁。虽然公平锁不会产生饥饿现象,但是公平锁的性能会比非公平锁差很多。

Condition与ReentrantLock的关系就类似于synchronized与Object.wait()/signal()

await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。

awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。 singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obejct.notify()方法很类似。

这里就不再详细介绍了。举个例子来说明:

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock= new ReentrantLock(); public static Condition condition= lock.newCondition();

@Override public void run(){ try

System.out.println("Thread is going on");

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

condition.signal(); lock.unlock();

上述例子很简单,让一个线程await住,让主线程去唤醒它。condition.await()/signal只能在得到锁以后使用。

对于锁来说,它是互斥的排他的。意思就是,只要我获得了锁,没人能再获得了。

而对于Semaphore来说,它允许多个线程同时进入临界区。可以认为它是一个共享锁,但是共享的额度是有限制的,额度用完了,其他没有拿到额度的线程还是要阻塞在临界区外。当额度为1时,就相等于lock

package test;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class Test implements Runnable{ final Semaphore semaphore= new Semaphore(5);@Override

System.out.println(Thread.currentThread().getId()+" done");

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

ExecutorService executorService= Executors.newFixedThreadPool(20); final Test t= new Test(); for(int i= 0; i< 20; i++)

有一个20个线程的线程池,每个线程都去Semaphore的许可,Semaphore的许可只有5个,运行后可以看到,5个一批,一批一批地输出。

当然一个线程也可以一次申请多个许可

public void acquire(int permits) throws InterruptedException

ReadWriteLock是区分功能的锁。读和写是两种不同的功能,读-读不互斥,读-写互斥,写-写互斥。

这样的设计是并发量提高了,又保证了数据安全。

private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();

private static Lock readLock= readWriteLock.readLock();

private static Lock writeLock= readWriteLock.writeLock();

详细例子可以查看Java实现生产者消费者问题与读者写者问题,这里就不展开了。

一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程

,等待所有检查线程全部完工后,再执行

static final CountDownLatch end= new CountDownLatch(10);

package test;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Test implements Runnable{ static final CountDownLatch countDownLatch= new CountDownLatch(10); static final Test t= new Test();@Override

System.out.println("complete");

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

ExecutorService executorService= Executors.newFixedThreadPool(10); for(int i= 0; i< 10; i++)

主线程必须等待10个线程全部执行完才会输出"end"。

和CountDownLatch相似,也是等待某些线程都做完以后再执行。与CountDownLatch区别在于这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程

public CyclicBarrier(int parties, Runnable barrierAction) barrierAction就是当计数器一次计数完成后,系统会执行的动作await()

package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{ private String soldier; private final CyclicBarrier cyclic; public Test(String soldier, CyclicBarrier cyclic)

{ this.soldier= soldier; this.cyclic= cyclic;

dowork();//等待所有士兵完成工作

{// TODO Auto-generated catch block

{// TODO Auto-generated method stub

System.out.println(soldier+": done");

} public static class BarrierRun implements Runnable

{ boolean flag; int n; public BarrierRun(boolean flag, int n)

{ super(); this.flag= flag; this.n= n;

System.out.println(n+"个任务完成");

System.out.println(n+"个集合完成");

} public static void main(String[] args)

Thread[] threads= new Thread[n]; boolean flag= false;

CyclicBarrier barrier= new CyclicBarrier(n, new BarrierRun(flag, n));

System.out.println("集合"); for(int i= 0; i< n; i++)

threads[i]= new Thread(new Test("士兵"+ i, barrier));

士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10个任务完成

与suspend相比不容易引起线程冻结

LockSupport的思想呢,和Semaphore有点相似,内部有一个许可,park的时候拿掉这个许可,unpark的时候申请这个许可。所以如果unpark在park之前,是不会发生线程冻结的。

下面的代码是[高并发Java二]多线程基础中suspend示例代码,在使用suspend时会发生死锁。

而使用LockSupport则不会发生死锁。

park()能够响应中断,但不抛出异常。中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志。

在JDK当中有大量地方使用到了park,当然LockSupport的实现也是使用unsafe.park()来实现的。

public static void park(){ unsafe.park(false, 0L);

下面来介绍下ReentrantLock的实现,ReentrantLock的实现主要由3部分组成:

ReentrantLock的父类中会有一个state变量来表示同步的状态

通过CAS操作来设置state来获取锁,如果设置成了1,则将锁的持有者给当前线程

setExclusiveOwnerThread(Thread.currentThread()); else

如果拿锁不成功,则会做一个申请

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

首先,再去申请下试试看tryAcquire,因为此时可能另一个线程已经释放了锁。

如果还是没有申请到锁,就addWaiter,意思是把自己加到等待队列中去

其间还会有多次尝试去申请锁,如果还是申请不到,就会被挂起

LockSupport.park(this); return Thread.interrupted();

同理,如果在unlock操作中,就是释放了锁,然后unpark,这里就不具体讲了。

我们知道HashMap不是一个线程安全的容器,最简单的方式使HashMap变成线程安全就是使用Collections.synchronizedMap,它是对HashMap的一个包装

同理对于List,Set也提供了相似方法。

但是这种方式只适合于并发量比较小的情况。

我们来看下synchronizedMap的实现

它会将HashMap包装在里面,然后将HashMap的每个操作都加上synchronized。

它会将HashMap包装在里面,然后将HashMap的每个操作都加上synchronized。

由于每个方法都是获取同一把锁(mutex),这就意味着,put和remove等操作是互斥的,大大减少了并发量。

下面来看下ConcurrentHashMap是如何实现的

在ConcurrentHashMap内部有一个Segment段,它将大的HashMap切分成若干个段(小的HashMap),然后让数据在每一段上Hash,这样多个线程在不同段上的Hash操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样实现了锁的分离,大大增加了并发量。

在使用ConcurrentHashMap.size时会比较麻烦,因为它要统计每个段的数据和,在这个时候,要把每一个段都加上锁,然后再做数据统计。这个就是把锁分离后的小小弊端,但是size方法应该是不会被高频率调用的方法。

在实现上,不使用synchronized和lock.lock而是尽量使用trylock,同时在HashMap的实现上,也做了一点优化。这里就不提了。

BlockingQueue不是一个高性能的容器。但是它是一个非常好的共享数据的容器。是典型的生产者和消费者的实现。

二、自旋锁和互斥锁的区别 java中lock Syntronized区别

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:

1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时间内获得锁,这无疑会使CPU效率降低。

2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。

因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。

自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

互斥锁属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞(blocking),Core0会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而自旋锁则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

互斥锁的起始原始开销要高于自旋锁,但是基本是一劳永逸,临界区持锁时间的大小并不会对互斥锁的开销造成影响,而自旋锁是死循环检测,加锁全程消耗cpu,起始开销虽然低于互斥锁,但是随着持锁时间,加锁的开销是线性增长。

互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器。

java并发之Lock与synchronized的区别

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

三、java线程安全的问题

1、某一个函数或者变量没有在使用之前声明。

2、某个地方少了个括号。(并不一定是编译器指出错误的地方,这种情况,编译器一般会在最后一行代码报错,但错误很可能不在最后一行,要靠自己去找出来)

结尾处应当expected'}'或者报 unexpected'{'.

但无论怎样,是说花括号的配对有问题。

可以用添加/*和添加*/的方法,把某些程序块注解出去,检查余留部分,看哪里多了或少了花括号。

头文件部分也要查,有些头由于宏配对问题会造成花括配对错。

pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。

work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。

pthread_cleanup_push(pthread_mutex_unlock,(void*)&mut);

pthread_mutex_unlock(&mut);

必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。

因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。