案例需求
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
实现步骤
代码实现
public class Ticket implements Runnable { //票的数量 private int ticket = 100; @Override public void run() { while(true){ if(ticket <= 0){ //卖完了 break; }else{ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票"); } } } } public class Demo { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
运行结果出现了重复票,负号票问题:
1.卖票出现了问题
相同的票出现了多次
出现了负数的票
2.问题产生原因
线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
1.安全问题出现的条件
**会被共享的数据:**在Java中,所有实例域(对象的字段)、静态域(静态字段)和数组元素都存储在堆内存中,堆内存在线程之间共享
**哪些数据不会被共享:**局部变量(Local Variables),方法定义参数(局部变量(Local Variables)和异常处理器参数(ExceptionHandler Parameters)不会在线程之间共享)
2.卖票问题出现的原因(回顾)?如何解决呢?
线程的随机执行。如何解决呢?控制不同线程间操作发生相对顺序的机制线程同步
3.如何实现线程同步呢?
同步代码块格式:
synchronized(任意对象) { 多条语句操作共享数据的代码 }
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
4.synchronized同步代码块的特点?
默认情况下是打开的,只要有一个线程进去执行代码了,锁就会关闭
当线程执行完出来时,锁才会自动打开
代码演示
public class Ticket implements Runnable { //票的数量 private int ticket = 100; private Object obj = new Object(); @Override public void run() { while(true){ synchronized (obj){//多个线程必须使用同一把锁. if(ticket <= 0){ //卖完了 break; }else{ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票"); } } } } } public class Demo { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
5.同步的好处和弊端
1.同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体; }
2.同步方法的锁对象是什么呢?
this
3.静态同步方法的格式
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体; }
4.同步静态方法的锁对象是什么呢?
类名.class
代码演示
public class MyRunnable implements Runnable { private static int ticketCount = 100; @Override public void run() { while(true){ if("窗口一".equals(Thread.currentThread().getName())){ //同步方法,锁对象为类名.class boolean result = synchronizedMthod(); if(result){ break; } } if("窗口二".equals(Thread.currentThread().getName())){ //同步代码块,这里设置为类名.class就是为了验证同步方法的锁对象为类名.class synchronized (MyRunnable.class){ if(ticketCount == 0){ break; }else{ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticketCount--; System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票"); } } } } } private static synchronized boolean synchronizedMthod() { if(ticketCount == 0){ return true; }else{ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } ticketCount--; System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票"); return false; } } } public class Demo { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); t1.setName("窗口一"); t2.setName("窗口二"); t1.start(); t2.start(); } }
1.如何手动开关锁呢?
使用lock
2.如何使用Lock?
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
boolean tryLock() | 尝试获得锁 |
void unlock() | 释放锁 |
代码演示
import java.util.concurrent.locks.ReentrantLock; public class Ticket implements Runnable { //票的数量 private int ticket = 100; private Object obj = new Object(); private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { //synchronized (obj){//多个线程必须使用同一把锁. try { lock.lock(); if (ticket <= 0) { //卖完了 break; } else { Thread.sleep(100); ticket--; System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } // } } } } public class Demo { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
3.synchronized与Lock的区别
synchronized | Lock |
---|---|
Java的关键字,在jvm层面上 | 是一个类 |
1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
锁的细粒度和灵活度低 | 锁的细粒度和灵活度高 |
4.如何选择?
除非synchronized不满足需要时,才选择Lock