卖票案例

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class SellTicketRunnableImpl implements Runnable { private int ticket = 20; @Override public void run() { while(ticket > 0){ System.out.println("线程 "+ Thread.currentThread().getName() +" 出售第" + ticket + "张票"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; } } }
|
1 2 3 4 5 6 7 8 9 10
| public class Main { public static void main(String[] args) { SellTicketRunnableImpl stri = new SellTicketRunnableImpl(); new Thread(stri).start(); new Thread(stri).start(); new Thread(stri).start(); new Thread(stri).start(); new Thread(stri).start(); } }
|
卖票结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 线程 Thread-2 出售第20张票 线程 Thread-4 出售第20张票 线程 Thread-0 出售第20张票 线程 Thread-1 出售第20张票 线程 Thread-3 出售第20张票 线程 Thread-2 出售第19张票 线程 Thread-4 出售第17张票 线程 Thread-1 出售第16张票 线程 Thread-0 出售第17张票 线程 Thread-3 出售第17张票 线程 Thread-2 出售第15张票 线程 Thread-3 出售第14张票 线程 Thread-4 出售第13张票 线程 Thread-2 出售第12张票 线程 Thread-0 出售第10张票 线程 Thread-1 出售第11张票 线程 Thread-3 出售第9张票 线程 Thread-4 出售第8张票 线程 Thread-2 出售第8张票 线程 Thread-1 出售第5张票 线程 Thread-3 出售第6张票 线程 Thread-0 出售第5张票 线程 Thread-2 出售第3张票 线程 Thread-4 出售第3张票 线程 Thread-1 出售第1张票 线程 Thread-3 出售第2张票
进程已结束,退出代码 0
|
找出问题
多线程访问共享数据就会出现类似的不安全问题:一张票被卖掉了多次,本来20张票,买了26张,这样用户一定是不愿意的。
出现的根本问题是多线程在进行相同的操作或者操作相同的数据的时候,是异步的,他们之间互不关心,是假定了操作前判断的时候的值在操作的时候并不改变,也就是假定了不存在别的线程,所以一定是出现问题的。
解决问题的办法就是采用同步技术:多个线程进行相同的操作或者操作相同的数据的时候要关注是否有别的线程在进行相同的操作,有的话就阻塞等待,也就是保证共享的数据的修改或者使用必须是单线程进行,这样保证了安全
同步代码块
使用同步代码块卖票
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class SellTicketRunnableImpl implements Runnable { private int ticket = 20; private Object obj = new Object(); @Override public void run() { while(true){ synchronized (obj){ if(ticket>0){ System.out.println("线程 "+ Thread.currentThread().getName() +" 出售第" + ticket + "张票"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; } } } } }
|
1 2 3 4 5 6 7 8 9 10
| public class Main { public static void main(String[] args) { SellTicketRunnableImpl stri = new SellTicketRunnableImpl(); new Thread(stri).start(); new Thread(stri).start(); new Thread(stri).start(); new Thread(stri).start(); new Thread(stri).start(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| //结果 线程 Thread-0 出售第20张票 线程 Thread-0 出售第19张票 线程 Thread-0 出售第18张票 线程 Thread-4 出售第17张票 线程 Thread-2 出售第16张票 线程 Thread-2 出售第15张票 线程 Thread-2 出售第14张票 线程 Thread-2 出售第13张票 线程 Thread-2 出售第12张票 线程 Thread-2 出售第11张票 线程 Thread-2 出售第10张票 线程 Thread-2 出售第9张票 线程 Thread-2 出售第8张票 线程 Thread-2 出售第7张票 线程 Thread-2 出售第6张票 线程 Thread-2 出售第5张票 线程 Thread-2 出售第4张票 线程 Thread-2 出售第3张票 线程 Thread-2 出售第2张票 线程 Thread-2 出售第1张票
|
使用同步方法
和同步代码块类似:同步方法也会把方法内部的代码锁住,只让一个线程执行
使用步骤:
- 把访问了共享数据的代码抽取出来,放到一个方法中
- 在方法上添加synchronized修饰符
比如卖票
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override public void run(){ while(true){ sellTicket(); } }
public synchronized void sellTicket(){ if(ticket>0){ ticket--; } }
|
问题:同步方法的锁对象是谁,没有直接显示,其实是对象自己,也就是this
同步方法也可以写成
1 2 3 4 5 6 7 8
| public void sellTicket(){ synchronized(this){ if(ticket>0){ ticket--; } } }
|
静态同步方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class RunnableImpl implements Runnable { private static int ticket = 100;
@Override public void run() { while(true){ sellTicket(); } }
public static synchronized void sellTicket(){ if(ticket>0){ System.out.println("线程 "+ Thread.currentThread().getName() +" 出售第" + ticket + "张票"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; } } }
|