承上启下
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,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
随时随地看视频