线程同步

卖票案例

  • 案例需求

    某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

  • 实现步骤

    • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下
    • 判断票数大于0,就卖票,并告知是哪个窗口卖的
    • 卖了票之后,总票数要减1
    • 票卖没了,线程停止
    • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    • 创建SellTicket类的对象
    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    • 启动线程
  • 代码实现

    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.如何实现线程同步呢?

  • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
  • Java提供了同步代码块的方式来解决

同步代码块格式:

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();     } } 

Lock锁

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