虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,
在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,
JDK5以后提供了一个新的锁对象Lock
Lock 接口
ReentrantLock 实现类
void lock()
void unlock()
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*锁Lock
* void lock 加锁
* void unlock 释放锁
*
* ReentrantLock lock的实现类
*
* */
public class MyLock implements Runnable {
private int piao=100;
private Lock lock=new ReentrantLock();
@Override
public void run() {
while(true){
//加锁
lock.lock();
if(piao>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在卖出第"+piao--+"票");
}
//释放锁
lock.unlock();
}
}
}
public class LockDemo {
public static void main(String[] args) {
MyLock l=new MyLock();
Thread t1=new Thread(l,"窗口1");
Thread t2=new Thread(l,"窗口2");
Thread t3=new Thread(l,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
死锁问题
同步弊端:
A 效率低
B 如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码
A 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
B 同步代码块的嵌套案例
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;
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 d=new DieLock(true);
DieLock d2=new DieLock(false);
d.start();
d2.start();
}
}
线程间通信:
针对同一个资源的操作有不同种类的线程
举例:卖票有进的,也有出的。
通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作
public class Student {
String name;
int age;
}
public class GetThread implements Runnable {
private Student s;
GetThread(Student s){this.s=s;}
@Override
public void run() {
//Student s=new Student();
System.out.println(s.name+"---"+s.age);
}
}
public class SetThread implements Runnable {
private Student s;
SetThread(Student s){this.s=s;}
public void run() {
//Student s=new Student();
s.name="周杰伦";
s.age=27;
}
}
/*
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。
*
*
* */
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();
}
}
这里有一个问题,虽然我们模拟了,当有生产后,其值会输出,但是因为线程存在随机性,所以当线程GetThread第一抢到执行权的时候,这时参数还没有设置,他的值同样还是nul---0,如 如下所示 我们故意将其调度权限设置为一个最低(设置) 一个最高(获取),得到如下画面。
改进:
public class GetThread implements Runnable {
private Student s;
GetThread(Student s){this.s=s;}
@Override
public void run() {
while(true){
synchronized(s){
System.out.println(s.name+"---"+s.age);
}
}
}
}
public class SetThread implements Runnable {
private Student s;
SetThread(Student s){this.s=s;}
public void run() {
int x=0;
while(true){
synchronized(s){
if(x%2==0){
s.name="周杰伦";
s.age=27;
}else{
s.name="张杰";
s.age=18;
}
x++;
}
}
}
}
public class Student {
String name;
int age;
}
/*
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。
*
*
* 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
* A:同一个数据出现多次
* B:姓名和年龄不匹配
*
* 原因:
* A:同一个数据出现多次
* cpu一点点时间片的执行权,就足够执行很多次
* B:姓名和年龄不匹配
* 线程运行的随机性
*
* 线程安全问题:
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
*
*
* 解决方案:
* 加锁。
* 注意:
* A:不同种类的线程都要加锁。
* B:不同种类的线程加的锁必须是同一把。
*
*
*
*
* */
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.setPriority(1);
//t2.setPriority(10);
//启动线程
t1.start();
t2.start();
}
}
等待唤醒机制:
利用等待唤醒机制,我们可以实现上述问题
public class GetThread implements Runnable {
private Student s;
GetThread(Student s){this.s=s;}
@Override
public void run() {
while(true){
synchronized(s){
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name+"---"+s.age);
//获取完成后,将状态改为false
s.flag=false;
//唤醒线程
s.notify();
}
}
}
}
public class SetThread implements Runnable {
private Student s;
SetThread(Student s){this.s=s;}
public void run() {
int x=0;
while(true){
synchronized(s){
if(s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x%2==0){
s.name="周杰伦";
s.age=27;
}else{
s.name="张杰";
s.age=18;
}
x++;
s.flag=true;
s.notify();
}
}
}
}
public class Student {
String name;
int age;
boolean flag;//默认情况下是没有数据的
}
/*
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。
*
* 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
* A:同一个数据出现多次
* B:姓名和年龄不匹配
* 原因:
* A:同一个数据出现多次
* CPU的一点点时间片的执行权,就足够你执行很多次。
* B:姓名和年龄不匹配
* 线程运行的随机性
* 线程安全问题:
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
* 解决方案:
* 加锁。
* 注意:
* A:不同种类的线程都要加锁。
* B:不同种类的线程加的锁必须是同一把。
*
* 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
* 如何实现呢?
* 通过Java提供的等待唤醒机制解决。
*
* 等待唤醒:
* Object类中提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
* 所以,这些方法必须定义在Object类中。
*/
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();
}
}
但是这里同样有一个问题,在资源类Student中,我们将成员没有封装起来,这个是不对的,不符合保密要求的,那么应该怎样进行改进呢?
public class GetThread implements Runnable {
private Student s;
GetThread(Student s){this.s=s;}
@Override
public void run() {
while(true){
s.get();
}
}
}
package Test_16;
public class Student {
private String name;
private int age;
private boolean flag;//默认情况下是没有数据的
public synchronized void set(String name,int age){
if(this.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;
private int x=0;
SetThread(Student s){this.s=s;}
public void run() {
// int x=0;
while(true){
if(x%2==0){
s.set("周杰伦", 27);
}else{
s.set("张杰", 18);
}
x++;
}
}
}
/*
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*
* 问题1:按照思路写代码,发现数据每次都是:null---0
* 原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
* 如何实现呢?
* 在外界把这个数据创建出来,通过构造方法传递给其他的类。
*
* 问题2:为了数据的效果好一些,我加入了循环和判断,给出不同的值,这个时候产生了新的问题
* A:同一个数据出现多次
* B:姓名和年龄不匹配
* 原因:
* A:同一个数据出现多次
* CPU的一点点时间片的执行权,就足够你执行很多次。
* B:姓名和年龄不匹配
* 线程运行的随机性
* 线程安全问题:
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
* 解决方案:
* 加锁。
* 注意:
* A:不同种类的线程都要加锁。
* B:不同种类的线程加的锁必须是同一把。
*
* 问题3:虽然数据安全了,但是呢,一次一大片不好看,我就想依次的一次一个输出。
* 如何实现呢?
* 通过Java提供的等待唤醒机制解决。
*
* 等待唤醒:
* Object类中提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
* 所以,这些方法必须定义在Object类中。
*
* 最终版代码中:
* 把Student的成员变量给私有的了。
* 把设置和获取的操作给封装成了功能,并加了同步。
* 设置或者获取的线程里面只需要调用方法即可。
*/
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();
}
}
线程状态转换图