一、使用锁的好处和坏处
好处:程序的执行效率会降低,因为存在关锁和开锁的过程---这些过程是需要时间的
坏处:提高了程序的安全性
根据线程的安全性和Synchronized同步衍生的一些额外知识
StringBuffer和StringBuidler的区别
StringBuffer:jdk1.0 同步的,线程安全,但是效率低
StringBuilder:jdk1.5 不同步的,安全性比StringBuffer低,效率高。*/
HashMap和Hashtable的区别
Hashtable:jak1.0 ,同步的,线程安全,效率低 它的key和value都不可以是null值
HashMap:jdk1.5 ,不同步的,线程不安全,效率高 它的key和value是都可以为null值的*/
两者都是属于Hash表数据结构的内容
开发中一般会使用HashMap,如果对安全性有要求,那么后期会使用一些特别的技术进行安全性解决
二、Synchronized同步代码块和同步方法
同步代码块演示代码
package com.bianyiit.cast;
import static java.lang.Thread.sleep;
public class SolveProblemDemo {
public static void main(String[] args) {
MaiPiao mp=new MaiPiao();
Thread t1=new Thread(mp);
t1.setName("窗口一正在卖第");
Thread t2=new Thread(mp);
t2.setName("窗口二正在卖第");
Thread t3=new Thread(mp);
t3.setName("窗口三正在卖第");
t1.start();
t2.start();
t3.start();
}
}
class MaiPiao implements Runnable{
int ticket=25;
@Override
public void run() {
while (true) {
//锁对象:线程共享的资源对象
//使用同步代码块synchronized将可能会出问题的代码包起来,参数是当前任务类的对象this
synchronized (this) { //锁对象使用this:代表的就是当前任务类的对象mp
if(ticket>0){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ticket+"张票");
ticket--;
}else{
break;
}
}
}
}
}
同步方法演示代码
//将同步代码块中的内容抽取出来,使用一个方法包起来,然后run()里面调用该方法
//标准写法:把同步代码块变成同步方法
//相比同步代码块的优点:没有加锁对象
package com.bianyiit.cast;
public class TongbuFangFaDemo2 {
public static void main(String[] args) {
MaiPiao mp = new MaiPiao();
Thread t1 = new Thread(mp);
t1.setName("窗口一正在卖");
Thread t2 = new Thread(mp);
t2.setName("窗口一正在卖");
Thread t3 = new Thread(mp);
t3.setName("窗口一正在卖");
t1.start();
t2.start();
t3.start();
}
}
class MaiPiao implements Runnable{
int ticket=100;
@Override
public void run() {
saleTicket();
}
public synchronized void saleTicket(){
while (ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+(ticket--)+"张票");
}
}
}
同步代码块和同步方法的区别:
1.同步代码块是有锁对象的,同步方法没有锁对象
2.同步代码块想同步哪些代码就直接添加到同步代码块中
3.而使用同步方法,方法里面所有的代码都会被同步
4.假如方法中只有部分代码需要被同步,使用同步方法就不太方便,所以建议使用同步代码块
三、23种设计模式之单例设计模式
1.什么是设计模式??
1.1 概念:经验的累积,相当于是一种思想,碰到某个特定的场景,立马能够设计出相关的执行代码使用某种技术解决这个问题
1.2 分类:Java中一共有23种设计模式
2.单例模式
2.1 概念:做一个项目的时候,使用到某一个类,但是在整个项目中,只允许这个类创建一个对象
2.2 分类:饿汉式设计模式和懒汉式设计模式
3.饿汉式设计模式详解
//单例模式之饿汉式的使用步骤
第二步:然后创建一个static修饰的对象属性用来接收对象
private static SingleDemo instance=new SingleDemo();
第一步:先把把构造方法私有化
private SingleDemo(){}
第三步:最后创建一个静态方法用来获取对象
public static SingleDemo getInstance(){
return instance;
}
//这样做完之后通过类名.方法创建两个对象,比较两个对象的地址值,发现都是一样的,说明不管创建了几个对象,最终它还是同一个对象
//代码演示
package com.bianyiit.cast;
public class SheJiMoShiDemo3 {
public static void main(String[] args) {
//单例模式分为两种:
/*饿汉式:只要类一加载就马上创建了对象
//弊端: 有时不需要用到对象或者想延迟使用对象,但是饿汉式是只要类一加载就马上创建了对象,会浪费内存资源*/
//单例模式:要求只能创建一个对象,不能创建多个对象
SingleDemo instance = SingleDemo.getInstance();
SingleDemo instance1 = SingleDemo.getInstance();
//比较地址值
System.out.println(instance==instance1);
//结果为true,代表instance和instance1是同一个对象
}
}
class SingleDemo{
//单例模式的使用步骤
//第二步:创建一个static修饰的对象属性用来接收对象
private static SingleDemo instance=new SingleDemo();
//第一步:把构造方法私有化
private SingleDemo(){}
//第三步:创建一个静态方法用来获取对象
public static SingleDemo getInstance(){
return instance;
}
}
注意:使用饿汉式单例设计模式的弊端
有时不需要用到对象或者想延迟使用对象,但是饿汉式是只要类一加载就马上创建了对象,会浪费内存资源,于是这是就引入了懒汉式设计模式了
4.懒汉式设计模式详解
package com.bianyiit.cast;
public class SheJiMoShiDemo3 {
public static void main(String[] args) {
//要求只能创建一个对象,不能创建多个对象
SingleDemo instance = SingleDemo.getInstance();
SingleDemo instance1 = SingleDemo.getInstance();
//比较地址值
System.out.println(instance==instance1);
//结果为true,代表instance和instance1是同一个对象
}
}
class SingleDemo{
//第二步:创建一个static修饰的对象属性用来定义对象
private static SingleDemo instance;
//第一步:把构造方法私有化
private SingleDemo(){}
//第三步:懒汉式--重新创建一个方法用来封装构造方法并获取对象
public static SingleDemo getInstance(){
//第一步:先要判断instance是否为空,只有为空的情况下才能创建对象
//为了避免同步锁的使用频率过多,导致执行速度过慢,这时需要再进行一次判断
if (instance==null) { //第一个判断是为了减少同步代码块的使用
synchronized (SingleDemo.class) { //这里需要一个锁对象,每一个字节码文件也可以看成是一个对象---字节码对象
//每一个类加载到方法区中,加载的是字节码文件,其实加载的是字节码对象(类名.class)
if(instance==null){ //第二个判断才是真正为了只生成一个对象
//假如两个线程同时进入判断条件,也会产生不同的两个对象,所以需要把产生对象的相关代码锁住
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleDemo(); //在这里使用构造方法创建对象
}
}
}
return instance;
}
}
懒汉式设计模式详解
1.为了避免饿汉式设计模式的类一加载就马上创建了对象,会浪费内存资源,于是这是就引入了懒汉式设计模式了
2.为了只创建一个对象,那么需要在方面里面的构造函数的外面加上如果对象不等于空就创建对象,否则就不创建对象了
if(instance==null){
instance = new SingleDemo(); //在这里使用构造方法创建对象
}
3.由于这是线程,会同时执行如果一个线程发现instance==null,那么它会创建对象,如果在创建对象之前先休眠了一段时间,那么第二个线程一进来发现instance同样为null,它也创建对象,那么就会创建两个对象,而这两个对象不是同一个对象
if(instance==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleDemo(); //在这里使用构造方法创建对象
}
4.所以需要使用同步代码块将上述代码包起来,让一个线程在进入的时候,就将代码锁住,其它的线程必须等待第一个线程完全执行完之后才能进入上述代码块
//注意1:synchronized括号里面需要一个锁对象,每一个字节码文件也可以看成是一个对象---字节码对象
//注意2:每一个类加载到方法区中,加载的是字节码文件,其实加载的是字节码对象(类名.class)
synchronized (SingleDemo.class) {
if(instance==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleDemo(); //在这里使用构造方法创建对象
}
}
5.由于每次线程进入都需要调用同步代码块synchronized,而关锁和开锁是需要时间的,会影响实际的开发效率,所以我们在上述代码的最外层加一个条件判断如果对象为空则调用里面的代码,如果对象不为空说明已经创建了一个对象,那么if里面的代码就都不需要执行了,这样才是完整的代码片段
if (instance==null) {
synchronized (SingleDemo.class) {
if(instance==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new SingleDemo(); //在这里使用构造方法创建对象
}
}
}
6.两个同样的if判断的解释:第一个if判断是为了减少同步代码块的使用,第二个判断才是真正为了只生成一个对象
7.两种单例设计模式的对比和总结:
7.1 单例模式中懒汉式比饿汉式的代码量更多,逻辑性要更强,
7.2 但是它的执行效率比饿汉式的要高,且不会随着类的加载就创建对象,
7.3 当类只需要调用其他静态方法时,只需要类名.方法就行了,不会创建对象
7.4 当用户需要创建对象时调用创建对象的方法进行对象的创建,所以不会浪费内存资源
四、死锁----哲学家之饿死案例
哲学家借筷子
扫描二维码关注公众号,回复:
8578032 查看本文章
每一个哲学家手里只有一只筷子,每一个哲学家向其它的哲学家借筷子,都没有借到,一直处于等待回复的状态
代码演示---这里使用两个哲学家借筷子为例
package com.bianyiit.cast;
public class SiSuoDemo4 {
public static void main(String[] args) {
/*死锁:哲学家之饿死案例---哲学家借筷子
* 每一个哲学家手里只有一只筷子,每一个哲学家向其它的哲学家借筷子,都没有借到,一直处于等待回复的状态
* */
//创建借筷子的任务对象
JieKuaiZi jkz = new JieKuaiZi();
//创建两个线程(创建两个哲学家对象)
Thread t1 = new Thread(jkz);
t1.setName("哲学家1");
Thread t2 = new Thread(jkz);
t2.setName("哲学家2");
//执行线程---两个哲学家互相借筷子
t1.start();
t2.start();
}
}
//创建一个借筷子的类
class JieKuaiZi implements Runnable{
//定义两只筷子
String k1="筷子1";
String k2="筷子2";
//用来借筷子的方法
@Override
public void run() {
//判断是哪一个线程(哲学家)进来了
if(Thread.currentThread().getName().equals("哲学家1")){
//哲学家1向哲学家2借筷子
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if(Thread.currentThread().getName().equals("哲学家2")){
//哲学家2向哲学家1借筷子
try {
method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//定义两个方法,
//一个是哲学家1向哲学家2借筷子
public void method1() throws InterruptedException {
//相当于是哲学家1自己拥有了筷子1这个资源
synchronized (k1){
Thread.sleep(100);
//借筷子2
System.out.println("哲学家2,请你把筷子借给我...");
//如果借到--相当于拥有了筷子2这个资源
synchronized (k2){
System.out.println("已经借到了筷子2...");
}
}
}
//一个是哲学家2向哲学家1借筷子
public void method2() throws InterruptedException {
//相当于是哲学家2自己拥有了筷子2这个资源
synchronized (k2){
Thread.sleep(100);
//借筷子1
System.out.println("哲学家1,请你把筷子借给我...");
//如果借到了---相当于拥有了筷子1这个资源
synchronized (k1){
System.out.println("已经借到了筷子1...");
}
}
}
}
五、解决死锁的方式
解决死锁的方式:对资源进行排序
//两个线程(两个哲学家)必须先拿筷子1,才能拿筷子2
//产生死锁是因为哲学家1拿着筷子1,然后问哲学家2借筷子2,而哲学家2同样拿着筷子2不给,向哲学家1借筷子1,这样循环下去,导致两个哲学家都没有一双筷子去夹菜最终会饿死
//代码中体现的是哲学家1占有k1而想要k2,而哲学家2占用k2想要k1
//哲学家1
synchronized (k1){
Thread.sleep(100);
//借筷子2
System.out.println("哲学家2,请你把筷子借给我...");
//如果借到--相当于拥有了筷子2这个资源
synchronized (k2){
System.out.println("已经借到了筷子2...");
}
}
//哲学家2
synchronized (k2){
Thread.sleep(100);
//借筷子1
System.out.println("哲学家1,请你把筷子借给我...");
//如果借到了---相当于拥有了筷子1这个资源
synchronized (k1){
System.out.println("已经借到了筷子1...");
}
}
//解决方法:同时占用k1申请k2,或者同时占用k2申请k1
//哲学家1
synchronized (k1){
Thread.sleep(100);
//借筷子2
System.out.println("哲学家2,请你把筷子借给我...");
//如果借到--相当于拥有了筷子2这个资源
synchronized (k2){
System.out.println("已经借到了筷子2...");
}
}
//哲学家2
synchronized (k1){ //只改动了这里的值
Thread.sleep(100);
//借筷子1
System.out.println("哲学家1,请你把筷子借给我...");
//如果借到了---相当于拥有了筷子1这个资源
synchronized (k2){
System.out.println("已经借到了筷子1...");
}
}
package com.bianyiit.cast;
public class SolveSiSuoDemo5 {
public static void main(String[] args) {
/*死锁:哲学家之饿死案例---哲学家借筷子
* 每一个哲学家手里只有一只筷子,每一个哲学家向其它的哲学家借筷子,都没有借到,一直处于等待回复的状态
* */
//创建借筷子的任务对象
JieKuaiZi2 jkz = new JieKuaiZi2();
//创建两个线程(创建两个哲学家对象)
Thread t1 = new Thread(jkz);
t1.setName("哲学家1");
Thread t2 = new Thread(jkz);
t2.setName("哲学家2");
//执行线程---两个哲学家互相借筷子
t1.start();
t2.start();
}
}
//创建一个借筷子的类
class JieKuaiZi2 implements Runnable{
//定义两只筷子
String k1="筷子1";
String k2="筷子2";
//用来借筷子的方法
@Override
public void run() {
//判断是哪一个线程(哲学家)进来了
if(Thread.currentThread().getName().equals("哲学家1")){
//哲学家1向哲学家2借筷子
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if(Thread.currentThread().getName().equals("哲学家2")){
//哲学家2向哲学家1借筷子
try {
method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//定义两个方法,
//一个是哲学家1向哲学家2借筷子
public void method1() throws InterruptedException {
//相当于是哲学家1自己拥有了筷子1这个资源
synchronized (k1){
Thread.sleep(100);
//借筷子2
System.out.println("哲学家2,请你把筷子借给我...");
//如果借到--相当于拥有了筷子2这个资源
synchronized (k2){
System.out.println("已经借到了筷子2...");
}
}
}
//一个是哲学家2向哲学家1借筷子
public void method2() throws InterruptedException {
//相当于是哲学家2自己拥有了筷子2这个资源
synchronized (k1){
Thread.sleep(100);
//借筷子1
System.out.println("哲学家1,请你把筷子借给我...");
//如果借到了---相当于拥有了筷子1这个资源
synchronized (k2){
System.out.println("已经借到了筷子1...");
}
}
}
}
六、生产者和消费者问题以及线程间通信—wait()和notify()
package com.bianyiit.cast;
//线程间通信问题
/*厨师:负责炒菜
* 服务员:负责端菜
* 需求:厨师一次只能炒一个菜,服务员一次只能端一个菜
* 而且只有服务员把菜端走之后厨师才能进行炒菜*/
public class WaitAndNotifyDemo6 {
public static void main(String[] args) {
//创建菜盘对象
CaiPan cp = new CaiPan();
//创建线程对象
Thread t1=new Thread(new Cooker(cp));
Thread t2=new Thread(new Waiter(cp));
t1.start();
t2.start();
}
}
//菜盘类---用来临时放菜用的
class CaiPan{
//默认菜盘里面先没有菜
boolean flag=false;
//定义一个字符串数组用来放菜
String[] caipan=new String[1];
//厨师放菜到菜盘上
public void set(String name){
caipan[0]=name;
}
//服务员把菜端走
public String get(){
return caipan[0];
}
}
//厨师类---负责炒菜
class Cooker implements Runnable{
//将菜盘中的对象放到厨师类里面
CaiPan cp;
public Cooker(CaiPan cp){
this.cp=cp;
}
@Override
public void run() {
int i=1;
//炒菜
while(true){
//如果菜盘里面有菜
synchronized (cp) {
if(cp.flag){
//先休息,处于等待状态,然后等待服务员呼唤厨师继续炒菜
try {
cp.wait(); //必须用在同步代码块/同步方法里面使用,不能随随便便使用
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果菜盘里面没有菜
}else{
//定义菜名
String name="啤酒鸭"+i;
cp.set(name);
i++;
System.out.println("厨师嗨炒好了一个菜---"+name);
cp.flag=true; //炒好了之后放入菜盘,菜盘由空变为有菜
cp.notify(); //呼唤服务员来端菜
}
}
}
}
}
//服务员类--负责端菜
class Waiter implements Runnable{
//将菜盘中的对象放到服务员类里面
CaiPan cp;
public Waiter(CaiPan cp){
this.cp=cp;
}
@Override
public void run() {
//取菜
while (true) {
synchronized (cp) {
//如果菜盘里面没有菜的时候,服务员处于等待状态,等待厨师炒好菜
if(!cp.flag){
try {
cp.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
String s = cp.get();
System.out.println("服务员端走了一个菜---" + s);
cp.flag=false; //这时菜盘里面没有菜了,然后去叫厨师去炒菜
cp.notify();
}
}
}
}
}
想实现的效果:厨师炒一个菜,服务员就端一个菜
厨师炒好了一个菜,服务员把菜端走,假如厨师处于了等待状态,那么需要服务员把厨师唤醒,然后厨师继续炒菜
唤醒了之后,厨师继续炒好了一个菜,然后继续处于等待状态,服务员过来把菜端走,然后唤醒厨师,告知厨师继续炒菜
如何解决炒菜重复或者端菜重复的问题
因为厨师炒菜的案例本身就是一个生产者与消费者模式,只能厨师炒好一个菜,服务员就马上端走
在服务员未端走菜之前,厨师要停下来等待服务员把菜端走,所以需要呼唤服务员过来把菜端走
厨师炒菜的代码解释
1.需要有一个厨师类,一个服务员类,一个菜盘类,一个测试类代码(包含主方法的)
2.厨师线程和服务员线程是同时执行的,看谁先抢占到CPU就执行谁
3.最开始菜盘里面是没有菜的,也就是boolean flag=false;
4.假设第一次是厨师线程先执行(这一部分是最难懂的)
--->假设第一次是厨师线程先执行,那么执行判断语句 if(cp.flag){},它为false,不执行if里面的wait()休息方法,而是执行else里面的炒菜方法,炒了一盘菜,并将这个菜放入菜盘,将菜盘里面的菜改为 flag=true,然后厨师运行notify()唤醒服务员,由于notify()不会释放资源,这时因为还是处于synchronized锁住的状态,服务员线程进不来,
--->这时再次执行厨师线程,执行判断语句 if(cp.flag){},这个时候它为true了,所以执行if里面的语句---wait(),厨师进入休息状态,他不炒菜了,这个时候才打开锁释放资源
5.假设第一次是服务员线程先执行
--->假设第一次是服务员线程先执行,那么执行判断语句 if(!cp.flag){},它为true,执行if 里面的wait()等待方法,进行休息状态,不会去端菜,而使用wait()方法会打开synchronized锁,去释放它所占用的资源
6.假设第一次厨师线程先执行完,进入wait()等待状态,同时释放它所占用的资源,这个时候由于两个线程是同时执行的,厨师线程进入阻塞状态不会再抢占CPU的资源,那么这个时候就只有服务员线程一个线程去占用CPU资源了
7.这时服务员线程来了,发现菜盘里面有菜了,就会执行端菜的程序,之后会执行notify()去唤醒厨师线程,这时服务员线程还没有打开锁释放资源,它本身会占用资源,厨师线程如果被唤醒,由于资源被锁住,不可能执行。
8.还是服务员线程执行,因为菜盘被端走了,会执行等待的程序,而执行等待的程序会释放它所占用的资源,那么这个时候服务员进入阻塞状态,就休息了,且资源可用
9.这时厨师线程来了,发现菜盘是空的,它去炒菜.....
特别注意:
1.线程是同时执行的,看谁先抢占到CPU就执行谁
2.Wait方法再调用的时候必须是在同步代码块中
3.执行notify()并不会释放当前线程所持有的锁对象,而只有Wait方法执行后才会释放当前线程所持有的锁对象
七、使用互斥锁Lock代替synchronized锁
package com.bianyiit.cast;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//线程间通信问题
/*厨师:负责炒菜
* 服务员:负责端菜
* 需求:厨师一次只能炒一个菜,服务员一次只能端一个菜
* 而且只有服务员把菜端走之后厨师才能进行炒菜*/
public class LockDemo7 {
public static void main(String[] args) {
//创建菜盘对象
CaiPan1 cp = new CaiPan1();
//创建线程对象
Thread t1=new Thread(new Cooker1(cp));
Thread t2=new Thread(new Waiter1(cp));
t1.start();
t2.start();
}
}
//菜盘类---用来临时放菜用的
class CaiPan1{
//默认菜盘里面先没有菜
boolean flag=false;
//定义一个字符串数组用来放菜
String[] caipan=new String[1];
//厨师放菜到菜盘上
public void set(String name){
caipan[0]=name;
}
//服务员把菜端走
public String get(){
return caipan[0];
}
//***使用互斥锁代替synchronized锁
//创建互斥锁对象
Lock lock=new ReentrantLock();
//创建与互斥锁结合使用的用于等待唤醒机制的对象
final Condition con1=lock.newCondition();
final Condition con2=lock.newCondition();
}
//厨师类---负责炒菜
class Cooker1 implements Runnable{
//将菜盘中的对象放到厨师类里面
CaiPan1 cp;
public Cooker1(CaiPan1 cp){
this.cp=cp;
}
@Override
public void run() {
int i=1;
//炒菜
while(true){
//如果菜盘里面有菜
cp.lock.lock(); //调用方法锁
if(cp.flag){
//先休息,处于等待状态,然后等待服务员呼唤厨师继续炒菜
try {
cp.con1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果菜盘里面没有菜
}else{
//定义菜名
String name="啤酒鸭"+i;
cp.set(name);
i++;
System.out.println("厨师嗨炒好了一个菜---"+name);
cp.flag=true; //炒好了之后放入菜盘,菜盘由空变为有菜
cp.con2.signal();
}
cp.lock.unlock(); //解锁
}
}
}
//服务员类--负责端菜
class Waiter1 implements Runnable{
//将菜盘中的对象放到服务员类里面
CaiPan1 cp;
public Waiter1(CaiPan1 cp){
this.cp=cp;
}
@Override
public void run() {
//取菜
while (true) {
cp.lock.lock();
//如果菜盘里面没有菜的时候,服务员处于等待状态,等待厨师炒好菜
if(!cp.flag){
try {
cp.con2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
String s = cp.get();
System.out.println("服务员端走了一个菜---" + s);
cp.flag=false; //这时菜盘里面没有菜了,然后去叫厨师去炒菜
cp.con1.signal();
}
cp.lock.unlock();
}
}
}
注意:
1.使用互斥锁Lock代替synchronized锁
2.互斥锁的使用步骤
//创建互斥锁对象
Lock lock=new ReentrantLock();
//创建与互斥锁结合使用的用于等待唤醒机制的对象
final Condition con1=lock.newCondition();
final Condition con2=lock.newCondition();
//线程对象1
cp.lock.lock(); //上锁
cp.con2.signal(); //唤醒
cp.con1.await(); //等待
cp.lock.unlock(); //开锁
//线程对象2
cp.lock.lock(); //上锁
cp.con1.signal(); //唤醒
cp.con2.await(); //等待
cp.lock.unlock(); //开锁
Lock锁相对于Synchronized锁的好处
1. Synchronized锁里面的内容需要执行两次代码,线程1第一次执行完之后会调用notify唤醒线程2,第二次执行完之后调用wait()进入阻塞状态并释放资源
2. Lock锁只需要执行一次就够了,因为线程1执行完之后就调用unlock()释放资源了,这时如果线程1又抢占了CPU执行,这时的条件变变成true,执行await()
3. Lock锁相比Synchronized锁效率更高,也更容易理解