转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html
我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoized关键字,简单方便。但是syncrhoized功能上较单一,为此,concurrent包为我们提供了额外的几种同步控制工具,让我们可以根据不同的同步需求更加灵活地选择同步工具。
一:ReentrantLock(重入锁)
ReentrantLock具有可重入、可中断、可限时申请、可公平获取等特点。
它的使用很简单:1:创建一个reentrantlock对象;2:在需要同步控制的代码块的起始处用 lock.lock() 加锁;3:在退出同步控制块处 lock.unlock() 解锁。
1)可重入
ReentrantLock可以重复获得同一把锁,即:在一个线程中,一个reentrantlock对象可以重复调用 lock() 申请这个锁对象进行加锁。
我们知道。如果一个线程申请同步锁时,如果锁被占用了就会导致阻塞,等待请求的锁释放,若是请求的锁刚好是自己以及在使用的锁呢?难道也要导致阻塞吗?可重入性就是为了解决这个问题,若当前线程请求的锁是自己已获得的锁,则使锁的获取计数器+1而已,不会自己阻塞自己,线程继续照常执行。
但是可重入性也要求了,一个线程请求了多少次同一个锁,那么就要相应地在结束时释放多少次。
lock.lock(); lock.lock(); try { i++; } finally { lock.unlock(); lock.unlock(); }
2)可中断
如果有两个线程,一个在lock1.lock()后申请了lock2.lock(),一个在lock2.lock()后申请了lock1.lock(),启动这两个线程。这两个线程都在等待对方释放掉已申请的lock,好让自己获得第二个lock而进行下去,这样的话就会导致死锁。我们知道死锁是由于资源请求存在环路导致的,只要打破这个环路即可让大部分请求得到满足,打破就是我们说的——中断。我们可以通过中断其中一个线程,使其让出所占用的资源(已获得的lock),则另一个线程即可获得两个lock而得以继续进行下去。
reentrantlock的可中断锁是通过 lockInterruptibly() 获取的,而不是普通的 lock()方法。
public static ReentrantLock lock1 = new ReentrantLock(); lock1.lockInterruptibly();
我们怎么知道死锁并解决呢?这需要用到ThreadMXBean(Java虚拟机线程管理接口)中提供的一系列方法,我们可以通过 ManagementFactory .getThreadMXBean();来获得一个线程管理接口的实现类,里面实现了ThreadMXBean中的线程管理方法,罗列如下:
方法摘要 | |
---|---|
ThreadInfo[] | dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers) 返回所有活动线程的线程信息,并带有堆栈跟踪和同步信息。 |
long[] | findDeadlockedThreads() 查找因为等待获得对象监视器或可拥有同步器而处于死锁状态的线程循环。 |
long[] | findMonitorDeadlockedThreads() 找到处于死锁状态(等待获取对象监视器)的线程的周期。 |
long[] | getAllThreadIds() 返回活动线程 ID。 |
long | getCurrentThreadCpuTime() 返回当前线程的总 CPU 时间(以毫微秒为单位)。 |
long | getCurrentThreadUserTime() 返回当前线程在用户模式中执行的 CPU 时间(以毫微秒为单位)。 |
int | getDaemonThreadCount() 返回活动守护线程的当前数目。 |
int | getPeakThreadCount() 返回自从 Java 虚拟机启动或峰值重置以来峰值活动线程计数。 |
int | getThreadCount() 返回活动线程的当前数目,包括守护线程和非守护线程。 |
long | getThreadCpuTime(long id) 返回指定 ID 的线程的总 CPU 时间(以毫微秒为单位)。 |
ThreadInfo | getThreadInfo(long id) 返回指定 id 的不具有堆栈跟踪的线程的线程信息。 |
ThreadInfo[] | getThreadInfo(long[] ids) 返回其 ID 在输出数组 ids 中的每个线程的线程信息,这些线程不具有堆栈跟踪。 |
ThreadInfo[] | getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers) 返回每个线程的线程信息,线程 ID 位于输入数组 ids 中,带有堆栈跟踪和同步信息。 |
ThreadInfo[] | getThreadInfo(long[] ids, int maxDepth) 返回其 ID 在输入数组 ids 中的每个线程的线程信息,并带有指定堆栈追踪元素数的堆栈追踪。 |
ThreadInfo | getThreadInfo(long id, int maxDepth) 返回指定 id 的线程的线程信息,并带有指定堆栈追踪元素数的堆栈追踪。 |
long | getThreadUserTime(long id) 返回指定 ID 的线程在用户模式中执行的 CPU 时间(以毫微秒为单位)。 |
long | getTotalStartedThreadCount() 返回自从 Java 虚拟机启动以来创建和启动的线程总数目。 |
boolean | isCurrentThreadCpuTimeSupported() 测试 Java 虚拟机是否支持当前线程的 CPU 时间测量。 |
boolean | isObjectMonitorUsageSupported() 测试 Java 虚拟机是否支持使用对象监视器的监视。 |
boolean | isSynchronizerUsageSupported() 测试 Java 虚拟机是否支持使用 可拥有同步器的监视。 |
boolean | isThreadContentionMonitoringEnabled() 测试是否启用了线程争用监视。 |
boolean | isThreadContentionMonitoringSupported() 测试 Java 虚拟机是否支持线程争用监视。 |
boolean | isThreadCpuTimeEnabled() 测试是否启用了线程 CPU 时间测量。 |
boolean | isThreadCpuTimeSupported() 测试 Java 虚拟机实现是否支持任何线程的 CPU 时间测量。 |
void | resetPeakThreadCount() 将峰值线程计数重置为当前活动线程的数量。 |
void | setThreadContentionMonitoringEnabled(boolean enable) 启用或禁用线程争用监视。 |
void | setThreadCpuTimeEnabled(boolean enable) 启用或禁用线程 CPU 时间测量。 |
从上面我们可以看到,通过ThreadMXBean我们可以获取虚拟机当前运行的所以线程的信息,包括 线程ID、存活的线程、死锁的线程等等信息。
所以,我们可以通过ThreadMXBean获取到由reentrantlock的lockInterruptibly()导致的死锁的随便一个线程的ID,然后通过 线程的interrupt() 方法自动中断自己,就可以使得死锁环中其他线程得以顺利执行。
3)可限时申请锁
reentrantlock对象可以限时申请获得锁,如果在限定时间内没能获取则放弃申请,在没锁的情况下执行代码(此时是不同步进行,线程不安全)。这样就不会由于申请被占用的锁而阻塞线程。
限时申请锁是通过 lock.trylock(num,unit)实现的,unit是时间单元,可以选择秒、分钟、小时、天等,num是数量,指定有多少个时间单元时间用来尝试获得锁。
public static ReentrantLock lock = new ReentrantLock(); lock.tryLock(5, TimeUnit.SECONDS);
4)可公平获取
一般的锁不是公平的,不会说先申请的线程就先获得锁,这样容易使得某一个线程一直处于申请获得锁的状态,即——饥饿。
ReentrantLock可以在创建时通过构造参数指定是不是公平锁,当作为公平锁创建时,申请获得该锁的线程们将严格按照“先申请先获得”的顺序来使用该锁。
public ReentrantLock(boolean fair) public static ReentrantLock fairLock = new ReentrantLock(true);