Lock方法实现同步及死锁的解决

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) 的区别?

sleep():属于Thread类,sleep方法不会释放出锁对象,它会让调用进程进入睡眠状态,让出执行机会给其他进程,等到睡眠时间结束后,线程进入就绪状态和其他线程一起竞争CPU的执行权。
wait():属于Object类,当一个线程执行到wait方法时,它就进入到一个和该对象相关的线程池,同时会释放出锁对象,使得其他对象能够访问,可以通过notify()、notifyAll()方法来唤醒等待的进程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值