最近在学习 《JAVA多线程编程实战指南》这本书,学到内部锁 synchronized 这里,自己就编写Demo演示模拟售票中超卖的线程安全问题,首先我的代码如下:
package com.sailing.thread.entity;
import com.sailing.thread.main.ThreadMain;
/**
* @author Baibing
* @project: java_thread
* @package: com.sailing.thread.entity
* @Description: java 创建线程之继承 Thread 方式
* @date 2018/8/17 09:37
*/
public class DemoThread extends Thread{
@Override
public void run() {
sellTicket2();
}
/**
* 模拟售票,多线程下会出现线程安全问题,会发生超卖
*/
public void sellTicket1(){
if(ThreadMain.num > 0){
ThreadMain.num--;
System.out.println(Thread.currentThread().getName() + "抢票成功");
}else{
System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
}
}
/**
* 方法上增加java 内部锁(synchronized)
*/
public synchronized void sellTicket2(){
if(ThreadMain.num > 0){
ThreadMain.num--;
System.out.println(Thread.currentThread().getName() + "抢票成功");
}else{
System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
}
}
}
启动类中创建了25个线程模拟用户抢票,总票数设置为20,正常情况应该有 5 个人抢不到票,代码如下:
package com.sailing.thread.main;
import com.sailing.thread.entity.DemoThread;
public class ThreadMain {
//总票数为 20 张
public static Integer num = 20;
public static void main(String[] args) {
//模拟 25 个人订票
for (int i = 0; i < 25; i++){
DemoThread thread = new DemoThread();
thread.start();
}
}
}
在 run() 方法中调用 sellTicket() 方法,这个因为没有加任何同步锁,多试几次肯定就会发生超卖,不过当我换成调用 sellTicket2() 方法时,我起初以为加了 synchronized 内部锁以后就会变成线程安全的,不会发生超卖现象,没想到啪啪啪的打脸 0.0 ,以下是试了好几次之后的结果,我们发现只有4个人没有抢到票,证明有一个人买到了票,还是发生了超卖。
于是乎我查找相关资料,发现 synchronized 是对 类的当前实例 进行加锁,当有一个线程正在访问一个实例的 synchronized 修饰的方法时,别的线程是无法访问这个实例的其他的 synchronized 方法,毕竟一个对象只有一把锁,但是其它线程可以访问此实例的其他的非 synchronized 方法,但是如果 线程A访问实例 Obj1 的synchronized 方法,线程B访问另一个实例 obj2 的 synchonized 方法,如果它们操作的共享变量,那么线程安全就无法保证了,这也就是我们上面虽然加了同步锁,依然出现超卖的原因,解决这种方法是将 synchornized 作用于 static 静态方法,这样的话,对象锁就是当前类对象,由于无论创建多少个实例对象,但对于的类对象拥有只有一个,所有在这样的情况下对象锁就是唯一的:
package com.sailing.thread.entity;
import com.sailing.thread.main.ThreadMain;
/**
* @author Baibing
* @project: java_thread
* @package: com.sailing.thread.entity
* @Description: java 创建线程之继承 Thread 方式
* @date 2018/8/17 09:37
*/
public class DemoThread extends Thread{
@Override
public void run() {
selTicket3();
}
/**
* 模拟售票,多线程下会出现线程安全问题,会发生超卖
*/
public void sellTicket1(){
if(ThreadMain.num > 0){
ThreadMain.num--;
System.out.println(Thread.currentThread().getName() + "抢票成功");
}else{
System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
}
}
/**
* 方法上增加java 内部锁(synchronized),这种方式是给当前实例对象加锁
*/
public synchronized void sellTicket2(){
if(ThreadMain.num > 0){
ThreadMain.num--;
System.out.println(Thread.currentThread().getName() + "抢票成功");
}else{
System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
}
}
/**
* 方法上增加java 内部锁(synchronized),这种方式是给当前类对象加锁
*/
public static synchronized void selTicket3(){
if(ThreadMain.num > 0){
ThreadMain.num--;
System.out.println(Thread.currentThread().getName() + "抢票成功");
}else{
System.out.println(Thread.currentThread().getName()+"票已经售罄,不好意思");
}
}
}
最后,当synchronized作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。