31_1线程安全(买票案例):加锁方式(synchronized、Lock锁)【同步代码块、同步方法】

day31上

线程安全 – 加锁

注意:要想多个线程互斥住,就必须使用同一把锁(对象)!!!

加锁方式

synchronized

Lock

synchronized

学习思路:

1.使用线程类、任务类方式不同

2.加锁方式不同

3.对于加锁中同步代码块、同步方法不同

同步代码块:

synchronized(锁对象){//自动上锁

…想要互斥的代码…

}//自动解锁

同步方法

同步方法 – 成员同步方法:

注意:锁对象 -> this(原因:成员方法属于对象)

public synchronized void method(){//自动上锁

…想要互斥的代码…

}//自动解锁

同步方法 – 静态同步方法:

注意:锁对象 -> 类.class(原因:静态方法属于类)

public static synchronized void method(){//自动上锁

…想要互斥的代码…

}//自动解锁

Lock

补充: Doug Lea(道格·利)编写的util.concurrent包 ;个人开发线程安全

//锁对象

Lock lock = new ReentrantLock();

lock.lock();//手动上锁

…想要互斥的代码…

lock.unlock();//手动解锁

需求

铁道部发布了一个售票任务,要求销售1000张票,要求有3个窗口来进行销售,请编写多线程程序来模拟这个效果(该题涉及到线程安全,https://www.jb51.net/article/221008.htm)

i. 窗口001正在销售第1张票

ii. 窗口001正在销售第2张票

iii. 窗口002正在销售第3张票

iv. 。。。

v. 窗口002正在销售第1000张票

涉及到线程安全,要加锁

使用线程类解决需求

synchronized方式
使用同步代码块

day30

使用同步方法
public class MyThread extends Thread{
	
	private static int allTicket = 1000;
	private static int curTicket = 0;
	
	public MyThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		
		while(curTicket < allTicket){
			method();
		}
	}
	
	//锁对象:MyThread.class
	public static synchronized void method(){
		if(curTicket < allTicket){
			curTicket++;
			System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");
		}
		if(curTicket >= allTicket){
			System.out.println("窗口" +  Thread.currentThread().getName() + "票已经售完");
		}
	}
	
}

public class Test01 {
	public static void main(String[] args) {
		
		MyThread t1 = new MyThread("001");
		MyThread t2 = new MyThread("002");
		MyThread t3 = new MyThread("003");
		
		t1.start();
		t2.start();
		t3.start();
		
	}
}

方法外面加锁,还是锁不住

原因:锁对象 -> this(原因:成员方法属于对象),new了三个对象,就有三把锁(对象)锁不住

解决:再把同步方法设置成静态变成一个锁(对象),就能锁锁上

Lock方式
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread{
	
	private static int allTicket = 1000;
	private static int curTicket = 0;
	
	private static Lock lock = new ReentrantLock();
	
	public MyThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		
		while(curTicket < allTicket){
			
			lock.lock();//手动上锁
			try {
				if(curTicket < allTicket){
					curTicket++;
					System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");
				}
				if(curTicket >= allTicket){
					System.out.println("窗口" +  Thread.currentThread().getName() + "票已经售完");
				}
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				lock.unlock();//手动解锁
			}
		}
	}
}

run()中new了锁对象,添加了手动上锁和手动解锁,还是锁不住

原因:线程new了三个对象,都调用run方法就有三把锁(对象)锁不住

解决:把new的锁放在run方法外面,再把new的锁对象设置成静态变成一个锁(对象),就能锁锁上

注意

考虑到想要互斥的代码有可能出现异常,如果出现异常就解不了锁,锁就用不了,用trycatch处理(鼠标右键Surround With)保证能解锁,下一次线程使用

使用任务类解决需求

synchronized方式
使用同步代码块
public class Task implements Runnable{

	private int allTicket = 1000;
	private int curTicket = 0;
	
	@Override
	public void run() {
		
		while(curTicket < allTicket){
			synchronized (this) {
				if(curTicket < allTicket){
					curTicket++;
					System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");
				}
			}
		}
		
		System.out.println("窗口" + Thread.currentThread().getName() + "票已售完");
	}

}

public class Test01 {
	public static void main(String[] args) {
		
		Task task = new Task();
		
		Thread t1 = new Thread(task, "001");
		Thread t2 = new Thread(task, "002");
		Thread t3 = new Thread(task, "003");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
代码实现出现的问题

与day30中的不太一样,day30要求是不会出现脏数据,而现在是要保证锁对象用得好

问题一:

三个窗口各卖1000张票,一共卖了3000张

出现原因:三个线程抢到CPU资源后都会调用run方法,curTicket和allTicket都是run方法里的局部变量,所以会调用3次

解决思路:curTicket和allTicket设置为成员属性,三个线程共用同一个任务

注意:

设置成静态属性和成员属性的区别:设置成成员属性,对于”铁道部发布了一个售票任务“设置成静态没有问题,但考虑到多个售票任务,静态对象就不好,售票任务之间是不同的

问题二:

有些票没有卖,有些票卖了重票

出现原因:当前线程抢到CPU资源后做了票的自增,但是还没来得及输出,时间片到了就退出CPU资源,然后其他线程抢到CPU资源了

解决方案:当前线程抢到CPU资源后,票的自增和输出执行完毕才能切换到其他线程运行 – 加锁

注意:

对于锁对象,问题1提到静态的是不好的,所以使用非静态的

通常用this,原因是this表当前任务的对象,对于外部有多个任务时,就会针对不同任务相应互斥内容进行互斥

问题三:

多卖了票

出现原因:curTicket到了临界点(999),三个线程都可以进判断,然后上锁

解决方案:在锁中再次判断

注意:

1.对于两个判断为什么不写出if-else的原因:如果写出if-else,if正常执行不会再执行else,就会有一个售完票的窗口输出不了“窗口xxx票已售完”

2.对于也不写成双if判断,把售完的判断不加锁的原因:以前的代码是存在问题的,有可能正好最后一个票卖完解锁,下一次curTicket不小于allTicket就进不去了,也没办法执行判断卖完的输出

使用同步方法

使用成员属性的原因同上

public class Task implements Runnable{

	private int allTicket = 1000;
	private int curTicket = 0;
	
	@Override
	public void run() {
		
		while(curTicket < allTicket){
			method();
		}
		
		System.out.println("窗口" + Thread.currentThread().getName() + "票已售完");
	}
	
	public synchronized void method(){
		if(curTicket < allTicket){
			curTicket++;
			System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");
		}
	}

}
Lock方式

加锁参考使用线程类解决需求的lock方式,lock注意trycatch

小结:

线程类方式和任务类方式的区别:线程类方式不灵活,任务类是新建的任务交给线程更灵活,后续普遍使用任务类方式

设置成静态属性和成员属性的区别:设置成成员属性,对于”铁道部发布了一个售票任务“设置成静态没有问题,但考虑到多个售票任务时,静态对象就不好,售票任务之间是不同的

  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值