Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构可以使用Lock锁进行具体的锁定操作类 ,提供了具体的实现类:ReentrantLock
加锁并且去释放锁public class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// Object obj = new Object();
// Jdk5.0以后,java提供了一个具体的锁: 接口:Lock
private Lock lock= new ReentrantLock(); //显示获取锁的前提,一定要创建Lock接口对象
@Override
public void run() {
while (true) {
try { //try...finally
lock.lock(); // 获取锁 syncrhonized(obj)
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} finally {//释放锁
if(lock!=null) {
lock.unlock();
}
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket() ;
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
解决了多线程安全问题,但是还是有些问题:
1)执行效率低
2)会产生死锁
两个或两个以上的线程,在执行的过程中出现互相等待的情况,就叫做死锁!
比如接下来的这个例子:
public class MyLock {
//两个锁对象分别是objA 和objB
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 ;
}
//重写run方法
@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 ObjA
else objB
第二种情况
else objB
if ObjA
第三种情况:理想状态
else objB
else objA
if ObjA
if objB
或
if ObjA
if objB
else objB
else objA
两个线程之间相互等待,就会出现死锁情况。死锁会影响线程间的通信,解决线程之间的通信问题:生产消费者模式
通过下面的案例,我们来感受一下生产消费者模式:
Student类: 资源类
SetThread:设置学生的数据(生产者线程)
GetThread:获取(输出)学生数据(消费者线程)
StudentDemo:测试类
需求:SetThread线程给学生对象进行赋值,再通过消费者线程输出该学生数据,设计这样一个程序!
线程死锁的注意事项:要保证生产者线程和消费者线程针对同一个对象进行操作的!
在外部创建一个学生对象,将这个学生对象通过构造方法传入到各个线程中
public class Student {
String name ;
int age ;
}
//生产者线程
public class SetThread implements Runnable {
private Student s ;
public SetThread(Student s) {
this.s = s ;
}
@Override
public void run() {
//设置学生数据
//Student s = new Student() ;
while(true) {
s.name = "高圆圆" ;
s.age = 27 ;
}
}
}
//消费者线程
public class GetThread implements Runnable {
private Student s ;
public GetThread(Student s) {
this.s = s ;
}
@Override
public void run() {
//输出该学生数据
//Student s = new Student() ;
while(true) {
System.out.println(s.name +"----"+s.age);
}
}
}
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();
}
}
在运行过程中又会出现以下问题:
1)同一个人的姓名和年龄出现多次
2)姓名和年龄不符
为什么?
1)CPU的一点点时间片,在某一个时间点,足够它执行很多次
2)线程具有随机性
解决方案:
1)是否是多线程环境 是
2)是否有功共享数据 是
3)是否有多条语句对共享数据进行操作 有
同步机制(同步代码块/同步方法)
开发中,使用synchronized(Lock锁也可以)同步代码块将多条语句对共享数据的操作包起来!
//生产者线程
public class SetThread implements Runnable {
private Student s ;
public SetThread(Student s) {
this.s = s ;
}
//定义一个变量
private int x = 0 ;
@Override
public void run() {
//设置学生数据
//Student s = new Student() ;
while(true) {
synchronized (s) {
if(x%2 ==0) {
s.name = "张三" ;
s.age = 13 ;
}else {
s.name = "李四";
s.age = 14 ;
}
x++ ;
}
}
}
}
//消费者线程
public class GetThread implements Runnable {
private Student s ;
public GetThread(Student s) {
this.s = s ;
}
@Override
public void run() {
//输出该学生数据
//Student s = new Student() ;
while(true) {
synchronized (s) {
System.out.println(s.name +"----"+s.age);
}
}
}
}
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();
}
}
继续改进:
上面的代码改进之后,虽然加入了同步机制,但是打印一打印一大片,让数据依次打印数据!
如何解决:
就使用的是Java的等待唤醒机制
刚才程序会出现的问题:
1)如果GetThread的消费者线程先抢到CPU的执行权,意味着会执行默认值,打印默认值是没有任何意义的,所以得在这块进行改进!
2)如果是SetThread的生产者线程先抢到CPU的执行权,生成一些学生数据,此时还具有执行权,又会不断地产生数据,还是有问题,应该需要等待消费者线程先获取这些数据之后再产生数据。
解决思路:
有数据的情况下,通知(唤醒)消费者线程来消费数据!
没有数据的情况下,通知(唤醒)生产者线程来生产数据!
public class Student {
String name ;
int age ;
boolean flag; //默认没有数据,如果是true,说明有数据
}
//生产者线程
public class SetThread implements Runnable {
private Student s ;
public SetThread(Student s) {
this.s = s ;
}
//定义一个变量
private int x = 0 ;
@Override
public void run() {
//设置学生数据
//Student s = new Student() ;
while(true) {
synchronized (s) {
//判断有没有数据的情况
if(s.flag) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x%2 ==0) {
s.name = "张三" ;
s.age = 13 ;
}else {
s.name = "李四";
s.age = 14 ;
}
x++ ;
//如果有数据了,更改flag值
s.flag = true ;//有数据了
//通知t2线程消费数据,唤醒
s.notify(); //唤醒t2线程,唤醒之后t1,t2都互相抢占
}
}
}
}
//消费者线程
public class GetThread implements Runnable {
private Student s ;
public GetThread(Student s) {
this.s = s ;
}
@Override
public void run() {
//输出该学生数据
//Student s = new Student() ;
while(true) {
synchronized (s) {
//如果本身消费者有数据
if(!s.flag) {
try {
s.wait();//和网络编程中TCP编程里面的accept() 都属于阻塞式方法
//消费线程等待,等待该线程先输出这些数据(立即释放锁对象)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name +"----"+s.age);
//如果没有数据类
s.flag = false ;
//通知t1线程,赶紧产生数据
s.notify(); //唤醒单个线程
}
}
}
}
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();
}
}
面试题:wait(),notify(),notifyAll() 这些方法为什么会定义在Object类中呢?
这些方法看起来是属于线程的方法,但是Thread类中并没有这些方法,多线程中同步锁对象可以是任意的Java类,这些方法都和锁对象有关系,所以定义在Object类
wait()和sleep(long times) 的区别?
wait():属于Object类,当一个线程执行到wait方法时,它就进入到一个和该对象相关的线程池,同时会释放出锁对象,使得其他对象能够访问,可以通过notify()、notifyAll()方法来唤醒等待的进程。