Lock接口的主要方法
//不像synchronized在异常时释放锁,最佳实践是finaly里面释放锁
lock()
//尝试获取锁,超时就放弃当前锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//相当于把超时时间设置为无限,在等待的过程中,线程可以被打断
void lockInterruptibly() throws InterruptedException;
//注意不遵守公平原则,会获取到锁不管是否会有其它线程等待
boolean tryLock();
//最佳实践
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
}
可见性保证
happens-befor原则:在一个线程解锁以后,另一个线程加锁时可以看见之前解锁线程的操作。
锁的分类
公平锁和非公平锁
定义:公平指的是按照线程的请求顺序,来分配锁。非公平指的是,不完全按照线程的请求顺序,在一定情况下可以插队。
为什么有非公平锁?
避免唤醒的空闲时间。
代码示例:
package lock.reentrantlock;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述: 演示公平和不公平两种情况
*/
public class FairLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread thread[] = new Thread[10];
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Job(printQueue));
}
for (int i = 0; i < 10; i++) {
thread[i].start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Job implements Runnable {
PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始打印");
printQueue.printJob(new Object());
System.out.println(Thread.currentThread().getName() + "打印完毕");
}
}
class PrintQueue {
private Lock queueLock = new ReentrantLock(true);
public void printJob(Object document) {
queueLock.lock();
try {
int duration = new Random().nextInt(10) + 1;
System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration);
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
queueLock.lock();
try {
int duration = new Random().nextInt(10) + 1;
System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
}
}
共享锁(读锁)和排它锁
读写锁的规则:
要么是多个或者一个线程持有读锁,要么是一个线程有写锁,二者不会同时出现。
代码示例:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 描述: TODO
*/
public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}
private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(()->read(),"Thread1").start();
new Thread(()->read(),"Thread2").start();
new Thread(()->write(),"Thread3").start();
new Thread(()->write(),"Thread4").start();
}
}
读写锁插队策略
- 公平锁:不允许插队
- 非公平锁:写锁可以随时插队,读锁仅在等待队列的头节点,不是想获取写锁的线程时可以插队
锁的升降级:ReentrantReadWriteLock支持锁的降级,获取写锁的同时可以获取读锁。
自旋锁和阻塞锁
自旋锁的定义:如果物理机有多个cpu,能够让两个或两个以上的线程并行执行代码,我们就可以让后面那个请求的线程不放弃cpu的执行时间,看看持有锁的线程是否很快释放锁。而为了让线程稍等一下,我们需要让当前线程进行自旋。如果在自旋完成后前面锁定的同步资源线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获得同步资源。从而避免切换线程的开销。
阻塞锁的定义:如果线程没有拿到锁,会直接把线程阻塞,直到被唤醒
自旋锁的缺点:自旋时间长消耗cpu。
锁优化
jvm对锁的优化:自旋锁和自适应,锁销除
代码层面优化:缩小同步代码块,不要锁住方法,减少锁的次数