线程安全是指保证多线程环境下共享的、可修改的状态的正确性。
保证线程安全的两个办法:
线程安全需要保证几个基本特性:
关键字synchronized可以用来修饰方法和代码块,修饰方法也就相当于将方法放在代码块中。synchronized修饰实例方法时(对象锁),相当于synchronized (this) {}
。修饰静态方法时(类锁,也叫静态锁),相当于synchronized (ClassName.class) {}
。
对象有锁计数器,一个任务可以多次获得对象的锁,每获取一次,计数加1,每释放一次,计数减1,当计数为0时,其他任务才能获取该对象的锁(可重入性)。
java.util.concurrent.locks.Lock
被synchronized修饰的代码块,如果有一个线程获得了对应的锁,其他线程只能一直等待,直到锁被释放。原线程释放锁的情况有:
其他时候,如果代码块执行时间很长,或者等待IO、sleep等发生阻塞,一直没有释放锁,其他线程只能一直等待。
本文学习完毕之后,你将能够理解Lock的优势包括:
但是
private Lock lock = new ReentrantLock();
public void lockedMethod() {
lock.lock();;
try{
// do something
return; // return必须在try子句中,确保unlock不会过早发生
} finally {
lock.unlock();
}
}
package java.util.concurrent.locks;
public interface Lock {
void lock();
// 尝试获取锁,如果失败,等待的过程中可以响应中断(threadWait.interrupt())
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,如果获取成功,就马上返回true,否则(锁已经被其他线程获取)马上返回false
boolean tryLock();
// 尝试获取锁,如果获取失败,会等待unit时间,等待期间还拿不到锁就马上返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock,重入锁,Lock接口的实现类,提供了更多的方法。
isFair()
,判断锁是否是公平锁isLocked()
,判断锁是否被任何线程获取了isHeldByCurrentThread()
,判断锁是否被当前线程获取了hasQueuedThreads()
,判断是否有线程在等待该锁public interface ReadWriteLock {
// 获取读锁
Lock readLock();
// 获取写锁
Lock writeLock();
}
ReentrantReadWriteLock,重入读写锁,实现了ReadWriteLock接口。
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
final Main main = new Main();
new Thread(() -> main.testRWL(Thread.currentThread())).start();
new Thread(() -> main.testRWL(Thread.currentThread())).start();
// 输出的结果是两个thread交替输出“正在读”
}
public void testRWL(Thread thread) {
rwl.readLock().lock();
try {
long finish = System.currentTimeMillis() + 1;
while (System.currentTimeMillis() <= finish) {
System.out.println(thread.getName() + "正在读");
}
System.out.println(thread.getName() + "读结束");
} finally {
rwl.readLock().unlock();
}
}
Condition将Object类的wait
、notify
、notifyAll
等操作转化为相应的条件对象操作await
、signal
、signalAll
,将复杂而晦涩的同步操作转变为直观可控的对象行为。看JDK源码注释中的一个例子:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition(); // 非满条件对象
final Condition notEmpty = lock.newCondition(); // 非空条件对象
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
// 当数组已满,等待非满条件
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
// 添加元素,就发送一个非空条件信号
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
// 当数组已空,等待非空条件
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
// 添加元素,就发送一个非满条件信号
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
对象o的wait方法是让执行线程放弃o的锁并阻塞,而Condition对象的await方法放弃的是Lock锁,因此,一个Lock锁可以同时调整多个条件对象的状态,而synchronized锁只能调整锁隶属的对象的状态。
重入锁的分配机制是:基于线程的分配,而不是基于方法调用的分配。同一个线程获得锁之后,在其他方法中,不需要再次申请就可以获取锁。synchronized是重入锁,它锁定的是实例的this对象或者静态类的类对象,当锁方法A中又调用同类中的锁方法B时,锁计数增加1,而不用重新申请锁。加锁的ReentrantLock是同一个对象时,Lock锁也满足重入性。
线程A如果在等待sychronized的锁,那么其他线程中用A.interrupt()来中断线程A时,A是不会响应的。这种就是不可中断锁。ReentrantLock使用lockInterrupt
方法加锁时,此线程是可中断锁。
如果同时有多个线程在等待一个锁时,当锁被释放时,等待时间最长的线程(FIFO)会获得该锁。满足这个条件的就是公平锁。非公平锁无法保证按请求顺序来分配锁。synchronized是非公平锁。ReentrantLock和ReentrantReadWriteLock默认是非公平锁,但是在构造时可以设置为公平锁。
public ReentrantLock() { // 默认非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) { // true表示公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
使得多个线程之间的读操作不会发生冲突。比如ReadWriteLock接口就是读写锁。
上一篇:Jdk1.8集合框架之LinkedHashMap源码解析
下一篇:JDK1.8并发之生产者消费者问题