目录
1) Lock显示锁之ReentrantLock
1.1)创建锁常用方法
在JDK5中增加了lock锁接口,有ReentrantLock实现类,ReentrantLock锁称为可重入锁,功能比synchronized多,不但能实现锁的功能,还能使现等待/通知 机制,是通过方法进行同步代码块的操作
lock() | 获取锁,锁元素为调用lock()对象本身 |
unlock() | 释放锁 |
lockInterruptibly() | 获取锁,如果线程有中断标识(interrupt),抛出异常,跟据这个特点可以解决死问题,在两个线程死锁,可以中断其中一个线程,解决死锁 |
tryLock() | 获取锁,如果获取到锁返回true,否则false(不等待其它线程释放锁) |
tryLock(long var1,TimeUnit var3) | 获取锁,在指定时间获取到锁返回true,否则返回false,也不再获取锁 |
newCondition() | 实现 等待/通知 机制 |
1.1.1)lock()与unlock()
相当于synchronized的同步代码块,功能是一样的,锁对象变成了lock变量。即使在两个方法,只同使用一个lock变量,锁对象就是一样的
public class LockTest01 {
//定义显示锁
static Lock lock = new ReentrantLock();
//定义方法
public static void sm(){
//先获取锁
lock.lock();
for (int i = 0; i < 100; i++) {
System.err.println(Thread.currentThread().getName()+":"+i);
}
//释放锁
lock.unlock();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
sm();
}
};
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
1.1.2) lockInterruptibly()使用
lockInterruptibly()与lock()类似都是获取锁,区别是当前线程处于interrupt时,lock()获取锁不报错而lockInterruptibly()会抛出异常
故意在执行lockInterruptibly()前执行interrupt()方法,抛出异常。不能使用sleep()阻碍线程执行,因为如果线程处理sleep()状态,执行interrupt()同样会抛出异常
public class LockTest05 {
static class LockInte{
private static Lock lock = new ReentrantLock();
public void sm() {
try {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
new StringBuffer();
}
System.err.println("::"+Thread.currentThread().isInterrupted());
System.out.println("开始获取锁");
lock.lockInterruptibly();
System.out.println("开始执行");
for (int i = 0; i < Integer.MAX_VALUE; i++) {
new StringBuffer();
}
System.out.println("执行结束");
}catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
LockInte lockInte = new LockInte();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
lockInte.sm();
}
});
thread.start();
thread.interrupt();
}
}
1.1.3) tryLock()方法使用
public class tryLockTest {
static class Tlt implements Runnable{
private static ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
try{
//tryLock获取锁,如果3秒内成功获取锁,返回true
if(reentrantLock.tryLock(3, TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName()+"获取了锁");
Thread.sleep(4000);
}else {
System.out.println(Thread.currentThread().getName()+"没有获取锁");
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
//如果当前线程持有锁,返回 true
if(reentrantLock.isHeldByCurrentThread()){
reentrantLock.unlock(); //如果线程没有获取锁,释放会抛出异常
}
}
}
}
public static void main(String[] args) {
Tlt tlt = new Tlt();
new Thread(tlt).start();
new Thread(tlt).start();
}
}
tryLock()真正意义上避免死锁
lockInterruptibly()虽然可以通过结束某线程的方法,结束死锁,而tryLock()可以通过不断尝试的方法结束锁。
实际上就是while()不断执行,获取锁,获取不到就释放所有锁,在执行。这样其它线程就不会因为锁被占用造成假死
static class IntLock implements Runnable{
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock(); private int lockNum; //用于控制锁的顺序
public IntLock(int lockNum) {
this.lockNum = lockNum;
}
@Override
public void run() {
if ( lockNum % 2 == 0 ) { //偶数先锁 1,再锁 2
while (true){
try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得 锁 1, 还想获得锁 2");
Thread.sleep(new Random().nextInt(100));
try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁 1 与锁 2 ----完成任务了");
return; //结束 run()方法执行,即当前线程 结束
}
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
}else { //奇数就先锁 2,再锁 1
while (true){
try {
if (lock2.tryLock()){
System.out.println(Thread.currentThread().getName() + "获得 锁 2, 还想获得锁 1");
Thread.sleep(new Random().nextInt(100));
try {
if (lock1.tryLock()){
System.out.println(Thread.currentThread().getName() + "同时获得锁 1 与锁 2 ----完成任务了");
return; //结束 run()方法执行,即当前线程 结束
}
} finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
}
}
}
public static void main(String[] args) {
IntLock intLock1 = new IntLock(11);
IntLock intLock2 = new IntLock(22);
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start(); t2.start();
}
1.2) condition 使用
Lock.newCondition()返回一个Condition对象,通过Condition对象能使用 等待/通知机制
特点
- notify()通知,jvm会随机唤醒某个等待的线程,而使用Condition类可以选择性通过
解释:
synchronized中调用等待格式为 锁对象.wait(),唤醒为 锁对象.notify()。由于锁对象都是一样的,也就无法区分了。而lock的condition()对象,可以通过new得到。
如:Condition c1 = lock.newCondition(),Condition c1 = lock.newCondition(),c1执行c1.await()后c2执行c2.signal()是唤醒不了的
synchronized
static Lock lock = new ReentrantLock();
//获得conditon对象
static Condition condition = lock.newCondition();
//定义线程子类
static class SubThread extends Thread{
@Override
public void run() {
try {
lock.lock(); //await的操作也要获得锁后执行
System.out.println("method lock");
condition.await(); //设置等待
System.out.println("methon awit");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
System.out.println("method unlock");
}
}
}
public static void main(String[] args) throws InterruptedException {
SubThread t = new SubThread();
t.start();
Thread.sleep(3000);
lock.lock(); //唤醒线程同样需要获取锁
condition.signal();
lock.unlock();
}
1.3)公平锁与非公平锁
1.3.1)概述
非公平锁:如果线程A与线程B都申请锁C,锁C空闲时,随机在线程A与线程B选择一个执行,如synchronized就是一个非公平锁
公平锁:会按照时间先后顺序保证先到先得,公平锁的这一特点不会出现线程饥饿现象
如果创建ReentrantLock时传入true对象获取一个公平锁,由于公平锁需要维护一个有序队列,公平锁成本高
例:
public class lls {
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true){
try {
reentrantLock.lock();
System.out.println(Thread.currentThread().getName()+" 获得了锁对象");
}finally {
reentrantLock.unlock();
}
}
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable).start();
}
}
}
结果如下:
new ReentrantLock()或者new ReentrantLock(false)获取一个非公平锁结果: Thread-0 获得了锁对象 ....... | new ReentrantLock(true)获取一个公平锁结果:
Thread-0 获得了锁对象 |
1.3.2)公平锁与非公平锁区别:
- 如果是非公平锁,系统倾向于让一个线程再次获得已经持有的锁,这种分配策略是高效非公平的,如上0线程释放了锁,在获得比其它线程获得的机率大
- 如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能,保证了公平性
1.4)常用的方法
getHoldCount() | 返回当前线程调用lock()方法的次数 因为lock()是可重入的,实际上返回的是重入的次数 | ===》 1 2 1 |
int getQueueLength() | 返回等待获得锁的线程数 | |
int getWaitQueueLength(Condition) | 返回与Condition相关正在等待的线程数 | ==== 正在等待 |
boolean hasWaiters(Condition) | 返回是否有线程正在等待当前Condition条件。与getWaitQueueLength()类 | ==== 正在等待 |
boolean hasQueuedThread(Thread thread) | 查询参数指定的线程是否在等待获得锁 | ===== 线程是否在等待获得锁:true |
boolean hasQueuedThreads() | 查询是否还有线程在等待获得锁 | ==== 是否还有线程在等待获得锁:true |
boolean isFair() | 判断是否为公平锁 | ==== reentrantLock是否为公平锁:true |
boolean isHeldByCurrentThread() | 当前线程是否占用此锁,否则在unLock()时使用,如果线程没有占用锁,使用锁.unLock()会报错 | === 当前线程是否占用reentrantLock锁true |
boolean isLocked() | 查看锁是否被线程持有(不仅本线程) | === reentrantLock锁是否被线程持有true |
2) ReentrantReadWriteLock读写锁
2.1)概述
ReentrantReadWriteLock在读时共享锁,写时排他锁提升了程序的运行效率。如:A,B线程需要读数据,C线程写数据,如果读锁共享,A,B可以同时读取数据提升了程序的运行效率,而C写数据时,具有排他性,A,B无法获得锁,保证了数据安全。
问题:如果实现 a = a+1;如果A,B都执行+1操作,写入的数据也是不对的
读数据使用锁,就是为了保证数据的正确性,如读User{name,age},容易出现脏读,所以只要保证读数据时,没有线程在写数据就可以了。
获得条件 | 排他性 | 作用 | |
读锁 | 写锁未被任意线程持有 | 对读线程是共享的,对写线程是排他的 | 允许多个读线程可以同时读取共享数据,保证在读共享数据时,没有其他线程对共享数据进行修改 |
写锁 | 该写锁未被其他线程持有,并且相应的读锁也未被其他线程持有 | 对读线程或者写线程都是排他的 | 保证写线程以独占的方式修改共享数据 |
读写锁允许读读共享(提高读取数据的效率),读写互斥,写写互斥
在 java.util.concurrent.locks 包中定义了 ReadWriteLock 接口,该接口中定义了 readLock()返回读锁,定义 writeLock()方法返回写锁. 该接口的实现类是 ReentrantReadWriteLock.注意 readLock()与 writeLock()方法返回的锁对象是同一个锁的两个不同的角色, 不是分别获得两个不同的锁. ReadWriteLock 接口实例可以充当两个角色.读写锁的其他使用方法
2.2)读写锁的基本使用
//定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//获得读锁
Lock readLock = rwLock.readLock();
//获得写锁
Lock writeLock = rwLock.writeLock();
//读数据
readLock.lock();
try {
//读取共享数据
}finally {
readLock.unlock(); //释放锁
}
//写数据
writeLock.lock();
try {
//写共享数据
}finally {
writeLock.unlock(); //释放锁
}