Ticket
synchronized基本用法 (重要!)
Java中的任意对象实例都可以作为唯一的锁标志,简单理解就是每一个对象都维护着一个moniter,这个东西跟操作系统底层的mutex互斥量有关,所以任何对于对象锁的争抢可以简单理解为对moniter的争抢,映射到系统底层就是对mutex的争抢(相关深入知识可百度)
1 synchronized 修饰普通方法,则当前线程获得的是调用该方法的对象锁
2 synchronized 修饰静态方法,则当前线程获得的是当前类的Class对象锁
3 synchronized (Object obj) {…} 作用于代码块时比较灵活,获得的是当前 obj 这个对象锁
设计思路
**非最佳思路
1多线程 操作 多个资源
2 多线程 操作 一个资源 但使用了多把锁**
前者显然不符合题目的要求,这个设计本质就是意义不大 不予讨论,重点看很多同学实现出的后一个思路:
下面是部分同学的写法:
class Mythread implements Runnable {
static int i=100;
@Override
public void run() {
while(i>0) {
synchronized (this) {
if (i > 0) {
System.out.println(Thread.currentThread().getName()+"-------"+ i);
i--;
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Sold extends Thread {
public static void main(String args[]) throws InterruptedException {
Mythread d1=new Mythread();
Mythread d2=new Mythread();
Mythread d3=new Mythread();
Mythread d4=new Mythread();
Mythread d5=new Mythread();
Thread t1=new Thread(d1);
t1.start();
Thread t2=new Thread(d2);
t2.start();
Thread t3=new Thread(d3);
t3.start();
Thread t4=new Thread(d4);
t4.start();
Thread t5=new Thread(d5);
t5.start();
}
}
问题:
1.定义了5个 :d1 d2 d3 d4 d5 , 分别用5个线程去操作它们,本质是不符合多线程访问共享变量的题目要求
2.根据 synchronized 的作用,此代码按理说每个线程不存在锁争抢情况,因为它们都有各自的锁(di),那么理论上应该是出现如下打印情况: 每个线程分别打印100 - 1,相当于总共卖了500个票
3.而实际输出效果如下:
为什么会这样?
因为虽然这里有5把锁,线程不会争抢锁,但是资源定义是 static int i=100; 静态变量属于类变量,所有对象实例同享同一份,如果把静态变量改成普通变量,就会出现最初的效果:(共打印500此 5个 100 - 1)
上述这些问题是部分同学在设计出现问题的情况下,跑出来的运行效果以及原因解释。
最佳思路
多线程 操作一个资源,这个资源只用一把锁管理
public class Ticket extends Thread{
static int num=100; //剩余票数
@Override
public void run() {
while(num>0) {
//临界区
synchronized (this) {
if (num > 0) {
System.out.println(Thread.currentThread().getName()+"_____________"+num);
num--;
}
}
//模拟网络延时
try {
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Ticket tic = new Ticket(); //一把锁
for(int i=1;i<=5;i++) {
new Thread(tic,""+i).start();
}
}
}
可以看到这样 5个线程在访问 唯一共享资源num的时候,使用同一把锁 tic
问题:
1 输出票的序号100 - 1 乱序了,原因:程序逻辑不正确,参考本代码中的处理逻辑
2 票都被一个线程给抢完了,原因:你的CPU太快了 ,没有模拟网络延时,用Thread.sleep
3 输出的序号中最后有 0 -1 -2 …原因:程序逻辑不对,进入临界区后的每个线程先要判断票数合法才执行操作,参考本代码
问题3的解释:假如代码这么写:
public void run() {
while(num>0) {
//1
synchronized (this) {
//2
System.out.println(Thread.currentThread().getName()+"_____________"+num);
num--;
}
//模拟网络延时
try {
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
当num = 1时,是不是可以有多个线程判断num > 0 成立而进入到1的位置,那么它们后续会依次进入临界区执行 num --,所以就出现了这个错误。
后附上一些可以参考的代码写法
/**
* 资源类
*/
class TicketOutlet {
public int nums = 100;
public void sellTicket() {
synchronized (this) {
if (nums > 0) {
//站点卖票
System.out.println(Thread.currentThread().getName() + "买到了第" + nums +
"号票,剩余票数为" + --nums);
}
}
}
}
public class ScrambleTicket {
public static void main(String[] args) {
//创建一个资源 一个售票点
TicketOutlet ticketOutlet = new TicketOutlet();
//5个黄牛抢票
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
//每个黄牛只要发现售票点有票 就一直抢票
while (ticketOutlet.nums > 0) {
//抢票
ticketOutlet.sellTicket();
//休息
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "黄牛" + i).start();
}
}
}
public class Ticket implements Runnable {
private int number;
public Ticket() {
super();
// TODO Auto-generated constructor stub
this.number = 100;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (this) {
notifyAll();
if (number > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---------" + number);
number--;
try {
wait(); // 仅为了效果更明显,实际开发售票系统千万别wait()
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
break;
}
}
}
}
}