一、引入
我们刚刚学习完了同步代码块和同步方法,它们里面有个细节要注意。
这里以同步代码为例。我们发现,当有线程进入到同步代码块后,这里的锁是会自动关闭的。
当线程执行完里面的代码后,锁又会自动打开。
也就是说这里的锁的开关,我们是没有办法自己控制,都是自动的。
但如果我想手动去加锁,或者手动释放锁,有没有办法呢?
当然这必须是有的,在JavaJDK5以后提供了一个新的锁对象Lock。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
Lock中提供了获得锁、释放锁的方法,这样我们就可以去手动上锁、手动释放锁。
| 方法名 | 说明 |
|---|---|
| void lock() | 获得锁 |
| void unlock() | 释放锁 |
Lock是接口不能直接实例化(创建对象),这里采用它的实现类ReentrantLock来实例化
在创建对象的时候直接用它的空参构造就行了
| 方法名 | 说明 |
|---|---|
| ReentrantLock() | 创建一个ReentrantLock的实例 |
二、引出问题
将之前的代码使用 Lock 来改写
public class MyThread extends Thread{
static int ticket = 0;
//Lock是一个接口,在创建的时候需要创建它实现类的对象
Lock lock = new ReentrantLock();
@Override
public void run() {
//1.循环
while(true){
//由于上锁跟释放锁跟同步代码块是重复的,因此需要将同步代码块给注释掉
//2.同步代码块
//synchronized (MyThread.class){
lock.lock();
//3.判断
if(ticket == 100){
break;
//4.判断
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "在卖第" + ticket + "张票!!!");
}
// }
lock.unlock(); // 释放锁
}
}
}
如果此时直接运行代码,票依旧是有重复的,并且也会超出范围。这是为什么呢?
由于我们用的是第一种方式实现的多线程,MyThread 需要创建很多次
如果 MyThread 创建很多次,那么势必造成这里的 Lock锁 有多个对象,所以我们需要在它的前面加上 static静态关键字,这就表示所有的对象都共享同一把锁。
static Lock lock = new ReentrantLock();
运行后重复的票没有了,超出范围的票也没有了,但是新的问题又出现了:程序居然没停!
假设三个线程都在 run() 这里抢夺CPU的执行权,一开始假设线程1抢夺到了CPU的执行权。
然后执行到while循环、lock.lock(),此时相当于将锁给锁上了,线程1进来。
进来后做了一个判断:ticket == 100,发现不等于,就会执行 else 中的代码块,于是就会睡觉。
睡觉的时候CPU的执行权就会被抢走,给线程2或者线程3,它抢走后,它执行到了 lock.lock(),此时它无法获取到锁对象了,锁已经关闭了,因为线程1已经事先拿到了锁,一旦拿到了锁之后,锁就会关掉。其他的线程就算你抢到了CPU的执行权,它也会被关在外面。到这里还是跟之前是一样的。
继续往下分析,当线程1醒来后,执行下面的代码,直到调用 unlock(),即释放锁。
此时回到循环的上面,继续和线程2线程3抢夺CPU执行权。
假设现在每次都是线程1抢到的,因此线程1会把所有的票都卖完。
假设现在是最后一次,ticket = 100。假设还是线程1抢到了执行权,此时通过 lock.lock() 拿到了锁,然后进行 ticket == 100 ,判断为true,执行break,直接跳到了循环的外面,此时问题就出现了,它都没有执行到这里的 unlock() !
这就导致了线程1虽然结束了,但是它还拿着锁对象出去的,没有把锁给打开,这就导致了线程2和线程3一直停在 lock.lock(),此时程序就不会停止。
三、解决问题
但是这并不是我想要的,我想要的是:不管是什么样的情况,程序都能停止,即 lock.unlock() 都能执行到。
哪怕你循环结束了,你也得把这个锁释放了。
有的同学会说:在break前面再加一个 unlock() 不就行了吗?
这确实是可以的,但是 unlock() 算是扫尾代码,像这种扫尾代码我干嘛要写两遍呢?
有没有一种更为简单的办法?是有的,而且是最稳妥的。
在Java中异常的处理体系中,有个 finally,特性:不管怎么样,它里面的代码一定会执行,因此我们可以利用这个特性将 unlock() 写在 finally 中。
改写:将从锁开始全部的代码,全都放到 try-catch 中。
public class MyThread extends Thread{
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
//1.循环
while(true){
//2.同步代码块
//synchronized (MyThread.class){
lock.lock(); //2 //3
try {
//3.判断
if(ticket == 100){
break;
//4.判断
}else{
Thread.sleep(10);
ticket++;
System.out.println(getName() + "在卖第" + ticket + "张票!!!");
}
// }
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}

787

被折叠的 条评论
为什么被折叠?



