00103_死锁、Lock接口、等待唤醒机制
1、死锁
(1)同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉;
synchronzied(A锁){ synchronized(B锁){ } }
(2)代码演示
①定义锁对象
1 public class MyLock { 2 public static final Object lockA = new Object(); 3 public static final Object lockB = new Object(); 4 }
②线程任务类
1 import java.util.Random; 2 3 public class ThreadTask implements Runnable { 4 int x = new Random().nextInt(1);// 0,1 5 6 // 指定线程要执行的任务代码 7 @Override 8 public void run() { 9 while (true) { 10 if (x % 2 == 0) { 11 // 情况一 12 synchronized (MyLock.lockA) { 13 System.out.println("if-LockA"); 14 synchronized (MyLock.lockB) { 15 System.out.println("if-LockB"); 16 System.out.println("if大口吃肉"); 17 } 18 } 19 } else { 20 // 情况二 21 synchronized (MyLock.lockB) { 22 System.out.println("else-LockB"); 23 synchronized (MyLock.lockA) { 24 System.out.println("else-LockA"); 25 System.out.println("else大口吃肉"); 26 } 27 } 28 } 29 x++; 30 } 31 } 32 }
③测试类
1 public class ThreadDemo { 2 public static void main(String[] args) { 3 // 创建线程任务类对象 4 ThreadTask task = new ThreadTask(); 5 // 创建两个线程 6 Thread t1 = new Thread(task); 7 Thread t2 = new Thread(task); 8 // 启动线程 9 t1.start(); 10 t2.start(); 11 } 12 }
2、Lock接口
(1)Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作;
(2)Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能;
(3)我们使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对卖票案例中Ticket类进行如下代码修改:
1 public class Ticket implements Runnable { 2 //共100票 3 int ticket = 100; 4 5 //创建Lock锁对象 6 Lock ck = new ReentrantLock(); 7 8 @Override 9 public void run() { 10 //模拟卖票 11 while(true){ 12 //synchronized (lock){ 13 ck.lock(); 14 if (ticket > 0) { 15 //模拟选坐的操作 16 try { 17 Thread.sleep(10); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); 22 } 23 ck.unlock(); 24 //} 25 } 26 } 27 }
3、等待唤醒机制
(1)线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同;
(2)通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制;
(3)等待唤醒机制所涉及到的方法:
①wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中;
②notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的;
③notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
(4)其实,所谓唤醒的意思就是让线程池中的线程具备执行资格;
(5)这些方法都是在同步中才有效,同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程;
(6)仔细查看JavaAPI之后,发现这些方法 并不定义在 Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中;
(7)模拟等待唤醒机制的实现,代码案例:
①模拟资源类
1 public class Resource { 2 private String name; 3 private String sex; 4 private boolean flag = false; 5 6 public synchronized void set(String name, String sex) { 7 if (flag) 8 try { 9 wait(); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 // 设置成员变量 14 this.name = name; 15 this.sex = sex; 16 // 设置之后,Resource中有值,将标记该为 true , 17 flag = true; 18 // 唤醒output 19 this.notify(); 20 } 21 22 public synchronized void out() { 23 if (!flag) 24 try { 25 wait(); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 // 输出线程将数据输出 30 System.out.println("姓名: " + name + ",性别: " + sex); 31 // 改变标记,以便输入线程输入数据 32 flag = false; 33 // 唤醒input,进行数据输入 34 this.notify(); 35 } 36 }
②输入线程人任务类
1 public class Input implements Runnable { 2 private Resource r; 3 4 public Input(Resource r) { 5 this.r = r; 6 } 7 8 @Override 9 public void run() { 10 int count = 0; 11 while (true) { 12 if (count == 0) { 13 r.set("小明", "男生"); 14 } else { 15 r.set("小花", "女生"); 16 } 17 // 在两个数据之间进行切换 18 count = (count + 1) % 2; 19 } 20 } 21 }
③输出线程任务类
1 public class Output implements Runnable { 2 private Resource r; 3 4 public Output(Resource r) { 5 this.r = r; 6 } 7 8 @Override 9 public void run() { 10 while (true) { 11 r.out(); 12 } 13 } 14 }
④测试类
1 public class ResourceDemo { 2 public static void main(String[] args) { 3 // 资源对象 4 Resource r = new Resource(); 5 // 任务对象 6 Input in = new Input(r); 7 Output out = new Output(r); 8 // 线程对象 9 Thread t1 = new Thread(in); 10 Thread t2 = new Thread(out); 11 // 开启线程 12 t1.start(); 13 t2.start(); 14 } 15 }