承上启下
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
一、JDK5中Lock锁的使用
void lock() 上锁
void unlock() 释放锁
代码示意:
public class SellTicket implements Runnable { private int ticket = 20; private Lock lock = new ReentrantLock(); @Override public void run() { while (true){ lock.lock(); if (ticket <= 0) { break; } //卖票这个动作不安全。 System.out.println(Thread.currentThread().getName() + "正在售卖第" + (ticket--) + "票"); lock.unlock(); System.out.println(Thread.currentThread().getName()+"结束"); } } }
首先我们要造一个锁
Lock lock = new ReentrantLock();
然后调用lock.lock()和lock.unlock()将需要上锁的代码包起来。
但是查看java的一些源码,还是synchronized用的多。
虽然线程有了锁解决了安全问题,但是偶尔也会因为失误操作出现死锁的情况。
同步弊端:
效率低
如果出现了同步嵌套,就容易产生死锁问题
什么是死锁:
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
示例:
首先造两个锁:
public class MyLock { // 创建两把锁对象 public static final Object objA = new Object(); public static final Object objB = new Object(); }
同步代码块嵌套:
public class DieLock extends Thread { private boolean flag; public DieLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { synchronized (MyLock.objA) { System.out.println("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } } } else { synchronized (MyLock.objB) { System.out.println("else objB"); synchronized (MyLock.objA) { System.out.println("else objA"); } } } } }
测试:
public class DieLockDemo { public static void main(String[] args) { DieLock dl1 = new DieLock(true); DieLock dl2 = new DieLock(false); dl1.start(); dl2.start(); } } 运行打印:if objAelse objB
二、线程间通信
生产者、消费者模式:
生产者没有就生产,有就等待消费者消费;消费者有就消费,没有就等待生产者生产。
java提供了等待唤醒的机制。
Object类中提供了三个方法:
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
代码示例:
public class Student { String name; int age; boolean flag; }
生产者:
public class SetThread implements Runnable { private Student s; public SetThread(Student s){ this.s = s; } private int x = 0; @Override public void run() { while (true){ synchronized (s) { if (s.flag){ try { s.wait();//t1等着,释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } if (x % 2 == 0) { s.age=20; s.name="徐繁韵"; } else { s.age=21; s.name="唐富平"; } x++; s.flag=true; s.notify();//唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。 } //t1有,或者t2有 } } }
消费者:
public class GetThread implements Runnable { private Student s; public GetThread(Student s){ this.s = s; } @Override public void run() { while (true){ synchronized (s) { if (!s.flag){ try { s.wait();//t2等待,立即释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name + ":" + s.age); s.flag=false; s.notify();//唤醒t1,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。 } } } }
测试:
public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } } 输出打印: 徐繁韵:20唐富平:21徐繁韵:20唐富平:21徐繁韵:20唐富平:21徐繁韵:20唐富平:21徐繁韵:20。 。 。
看的出是生产一条消费一条。为了实现线程间的通信,将共同操作的数据通过有参构造器传入线程。
思考一个问题,为什么等待唤醒的方法不定义在Thread里呢?
这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。所以,这些方法必须定义在Object类中。
栗子优化:
既然wait()、notify()、notifyAll()定义在锁对象里,那么我们把前面的栗子优化一下。
把Student的成员变量给私有化,把设置和获取的操作给封装成功能,并加上同步。设置或者获取的线程里面只需要调用方法即可。
public class Student { private String name; private int age; boolean flag; public synchronized void set(String name,int age){ if (flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name; this.age = age; this.flag = true; this.notify(); } public synchronized void get(){ if (!this.flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(this.name+":"+this.age); this.flag = false; this.notify(); } }public class SetThread implements Runnable { private Student s; public SetThread(Student s){ this.s = s; } private int x = 0; @Override public void run() { while (true){ if (x % 2 == 0) { s.set("徐繁韵",20); } else { s.set("唐富平",21); } x++; } } }public class GetThread implements Runnable { private Student s; public GetThread(Student s){ this.s = s; } @Override public void run() { while (true){ s.get(); } } }public class StudentDemo { public static void main(String[] args) { Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
线程的状态转换图:
作者:韵呀
链接:https://www.jianshu.com/p/38da355903a5