JDK同步控制工具,JAVA高并发程序设计
程序员文章站
2022-05-22 12:46:42
...
同步控制是并发程序的重要手段之一,我们平常用过最多的Synchronized就是其中一种简单的方法,此外还有Object.wait(),Object.notify()等方法。在JDK之中,还有其他好用的工具。
重入锁_ReentrantLock:
一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。ReentrantLock将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用lock的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用isHeldByCurrentThread()和getHoldCount()方法来检查此情况是否发生。举个栗子:
public class Demo_1 implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
lock.lock();
try {
i++;
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws Exception {
Demo_1 A = new Demo_1();
Thread t1 = new Thread(A);
Thread t2 = new Thread(A);
t1.start();t2.start();
t1.join();t2.join();
}
}
使用重入锁,在并发时,保护i的安全性。重入锁还有一个特点,跟它的名字一样,可以反复进如入:
for (int j = 0; j < 1000000; j++) {
lock.lock();
lock.lock();
try {
i++;
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
lock.unlock();
}
}
跟synchronized相比,重入锁有显示操作,必须手动指定何时加锁和释放锁,对逻辑控制的灵活性要好于synchronized,唯一注意的就是必须释放锁。另外,重入锁还可以提供中断功能。使用synchronized时,一个线程在等待锁,最后结果就两种,要么获得锁,要么一直等待下去,这种情况对于实际业务需求来讲,并不友好。比如你朝思暮想的女朋友来看你了,在这种干柴烈火,风花雪月,月黑风高的时候,你正准备干点什么,女朋友亲戚来了,这时候你难道要一直等下去吗,那肯定不行,当然是停下来,然后开始学习
public class Demo_2 implements Runnable {
public static ReentrantLock rLockA = new ReentrantLock();
public static ReentrantLock rLockB = new ReentrantLock();
int lock;
public Demo_2(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
rLockA.lockInterruptibly();
System.out.println("占用lock="+lock+":"+System.currentTimeMillis());
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
rLockB.lockInterruptibly();
} else {
System.out.println("占用lock="+lock+":"+System.currentTimeMillis());
rLockB.lockInterruptibly();
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
rLockA.lockInterruptibly();
}
} catch (Exception e) {
// TODO: handle exception
} finally {
if (rLockA.isHeldByCurrentThread())
rLockA.unlock();
if (rLockB.isHeldByCurrentThread())
rLockB.unlock();
System.out.println(Thread.currentThread().getId() + " :线程退出 \b lock="+lock);
}
}
public static void main(String[] args) throws Exception {
Demo_2 demoA = new Demo_2(1);
Demo_2 demoB = new Demo_2(2);
Thread rA = new Thread(demoA);
Thread rB = new Thread(demoB);
rA.start();rB.start();
Thread.sleep(1000);
rB.interrupt();
}
}
当rA和rB同时执行,rA先占用lockA,再占用lockB,而rB先占用lock2,再占用lockA,这样就形成了相互等待。在这里,我们使用了 lockInterruptibly() 这个方法,这是一个可以对中断,休眠的线程进行响应的锁申请操作。
在上面的列子中,lockInterruptibly() 的优先作用是响应中断,就好比有个特工深入敌后,随时执行外面给的命令,因为外面的人进不来。最后我们调用了interrupt 方法,来中断rB线程,那么rB就会放弃lockA锁的申请,同时释放lockB的锁,这样rA就可以继续执行下去了。
除了这种等待外部通知外,还提供了一种其他方法:限时等待。顾名思义,就是在指定时间内,线程会一直等待锁的获取,超出时间就不再等待了。这里我们用 tryLock() 来实现:
public class Demo_3 implements Runnable {
public static ReentrantLock rLock = new ReentrantLock();
public Demo_3() {
System.out.println("初始化:"+Thread.currentThread().getId());
}
@Override
public void run() {
try {
if (rLock.tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(6000);
System.out.println("执行完成:"+Thread.currentThread().getId());
} else {
System.out.println("获取锁失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
}
public static void main(String[] args) {
Demo_3 demo = new Demo_3();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.start();
t2.start();
}
}
tryLock() 的两个参数也很好理解,一个是等待时长,另一个就是时间单位。如果不带参数,就表示不等待,如果当前锁没有被其他线程占用,就直接返回true,反之直接返回false。
Condition 条件:
使用Synchronized的时候,我们可以用wait()和notify()方法来达到控制线程的目的。同样的在重入锁中,也有类似的方法,await(),awaitUninterruptibly()和singal()。
- await():当前线程等待,同时释放锁,其他线程使用singal()或者singalAll()方法时,重新获得锁并继续执行。当线程中断时,能跳出等待。
- awaitUninterruptibly():和await()功能差不多,但是它无法等待过程中响应中断。
- singal():唤醒另一个在等待中的线程。