zoukankan      html  css  js  c++  java
  • 第二章 线程安全

    第二章 线程安全

    2.1 线程安全

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
    我们通过一个案例,演示线程的安全问题:
    电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。
    我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟

     多线程访问了共享的数据,会产生线程安全问题。

    代码示例:

    RunnableImpl.java:

     1 /*
     2     实现卖票案例
     3  */
     4 public class RunnableImpl implements Runnable{
     5     //定义一个多个线程共享的票源
     6     private  int ticket = 100;
     7 
     8 
     9     //设置线程任务:卖票
    10     @Override
    11     public void run() {
    12         //使用死循环,让卖票操作重复执行
    13         while(true){
    14             //先判断票是否存在
    15             if(ticket>0){
    16                 //提高安全问题出现的概率,让程序睡眠
    17                 try {
    18                     Thread.sleep(10);
    19                 } catch (InterruptedException e) {
    20                     e.printStackTrace();
    21                 }
    22 
    23                 //票存在,卖票 ticket--
    24                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    25                 ticket--;
    26             }
    27         }
    28     }
    29 }

    Demo01Ticket.java(非线程安全):

     1 /*
     2     模拟卖票案例
     3     创建3个线程,同时开启,对共享的票进行出售
     4  */
     5 public class Demo01Ticket {
     6     public static void main(String[] args) {
     7         //创建Runnable接口的实现类对象
     8         RunnableImpl run = new RunnableImpl();
     9         //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    10         Thread t0 = new Thread(run);
    11         Thread t1 = new Thread(run);
    12         Thread t2 = new Thread(run);
    13         //调用start方法开启多线程
    14         t0.start();
    15         t1.start();
    16         t2.start();
    17     }
    18 }

      线程安全问题的概述

    线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    2.2 线程同步

    当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
    要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

    根据案例简述:

    窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

    为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
    那么怎么去使用呢?有三种方式完成同步操作

    1. 同步代码块。
    2. 同步方法。
    3. 锁机制。

    2.3 同步代码块

    • 同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

    格式:

    synchronized(同步锁){
    需要同步操作的代码
    }

    同步锁:
    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
      1. 锁对象 可以是任意类型。
      2. 多个线程对象 要使用同一把锁。

    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

    代码示例:

    RunnableImpl.java:

     1 /*
     2     卖票案例出现了线程安全问题
     3     卖出了不存在的票和重复的票
     4 
     5     解决线程安全问题的一种方案:使用同步代码块
     6     格式:
     7         synchronized(锁对象){
     8             可能会出现线程安全问题的代码(访问了共享数据的代码)
     9         }
    10 
    11     注意:
    12         1.同步代码块中的锁对象,可以使用任意的对象
    13         2.但是必须保证多个线程使用的锁对象是同一个
    14         3.锁对象作用:
    15             把同步代码块锁住,只让一个线程在同步代码块中执行
    16  */
    17 public class RunnableImpl implements Runnable{
    18     //定义一个多个线程共享的票源
    19     private  int ticket = 100;
    20 
    21     //创建一个锁对象
    22     Object obj = new Object();
    23 
    24     //设置线程任务:卖票
    25     @Override
    26     public void run() {
    27         //使用死循环,让卖票操作重复执行
    28         while(true){
    29            //同步代码块
    30             synchronized (obj){
    31                 //先判断票是否存在
    32                 if(ticket>0){
    33                     //提高安全问题出现的概率,让程序睡眠
    34                     try {
    35                         Thread.sleep(10);
    36                     } catch (InterruptedException e) {
    37                         e.printStackTrace();
    38                     }
    39 
    40                     //票存在,卖票 ticket--
    41                     System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    42                     ticket--;
    43                 }
    44             }
    45         }
    46     }
    47 }

    Demo01Ticket.java:

     1 /*
     2     模拟卖票案例
     3     创建3个线程,同时开启,对共享的票进行出售
     4  */
     5 public class Demo01Ticket {
     6     public static void main(String[] args) {
     7         //创建Runnable接口的实现类对象
     8         RunnableImpl run = new RunnableImpl();
     9         //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    10         Thread t0 = new Thread(run);
    11         Thread t1 = new Thread(run);
    12         Thread t2 = new Thread(run);
    13         //调用start方法开启多线程
    14         t0.start();
    15         t1.start();
    16         t2.start();
    17     }
    18 }

    同步的原理:

    2.4 同步方法

    • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

    格式:

    public synchronized void method(){
    可能会产生线程安全问题的代码
    }

    同步锁是谁?

    对于非static方法,同步锁就是this。

    对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

    代码示例:

    RunnableImpl.java:

     1 /*
     2     卖票案例出现了线程安全问题
     3     卖出了不存在的票和重复的票
     4 
     5     解决线程安全问题的二种方案:使用同步方法
     6     使用步骤:
     7         1.把访问了共享数据的代码抽取出来,放到一个方法中
     8         2.在方法上添加synchronized修饰符
     9 
    10     格式:定义方法的格式
    11     修饰符 synchronized 返回值类型 方法名(参数列表){
    12         可能会出现线程安全问题的代码(访问了共享数据的代码)
    13     }
    14  */
    15 public class RunnableImpl implements Runnable{
    16     //定义一个多个线程共享的票源
    17     private static int ticket = 100;
    18 
    19 
    20     //设置线程任务:卖票
    21     @Override
    22     public void run() {
    23         System.out.println("this:"+this);//this:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1
    24         //使用死循环,让卖票操作重复执行
    25         while(true){
    26             payTicketStatic();
    27         }
    28     }
    29 
    30     /*
    31         静态的同步方法
    32         锁对象是谁?
    33         不能是this
    34         this是创建对象之后产生的,静态方法优先于对象
    35         静态方法的锁对象是本类的class属性-->class文件对象(反射)
    36      */
    37     public static /*synchronized*/ void payTicketStatic(){
    38         synchronized (RunnableImpl.class){
    39             //先判断票是否存在
    40             if(ticket>0){
    41                 //提高安全问题出现的概率,让程序睡眠
    42                 try {
    43                     Thread.sleep(10);
    44                 } catch (InterruptedException e) {
    45                     e.printStackTrace();
    46                 }
    47 
    48                 //票存在,卖票 ticket--
    49                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    50                 ticket--;
    51             }
    52         }
    53 
    54     }
    55 
    56     /*
    57         定义一个同步方法
    58         同步方法也会把方法内部的代码锁住
    59         只让一个线程执行
    60         同步方法的锁对象是谁?
    61         就是实现类对象 new RunnableImpl()
    62         也是就是this
    63      */
    64     public /*synchronized*/ void payTicket(){
    65         synchronized (this){
    66             //先判断票是否存在
    67             if(ticket>0){
    68                 //提高安全问题出现的概率,让程序睡眠
    69                 try {
    70                     Thread.sleep(10);
    71                 } catch (InterruptedException e) {
    72                     e.printStackTrace();
    73                 }
    74 
    75                 //票存在,卖票 ticket--
    76                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    77                 ticket--;
    78             }
    79         }
    80 
    81     }
    82 }

    2.5 Lock锁

    java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,

    同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

    Lock锁也称同步锁,加锁与释放锁方法化了,如下:

    • public void lock() :加同步锁。
    • public void unlock() :释放同步锁。

    解决线程安全问题的三种方案:使用Lock锁

        java.util.concurrent.locks.Lock接口

        Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

        Lock接口中的方法:

    • void lock()获取锁。
    • void unlock()  释放锁。

        java.util.concurrent.locks.ReentrantLock implements Lock接口

    RunnableImpl.java:

     1 /*
     2     卖票案例出现了线程安全问题
     3     卖出了不存在的票和重复的票
     4 
     5     解决线程安全问题的三种方案:使用Lock锁
     6     java.util.concurrent.locks.Lock接口
     7     Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
     8     Lock接口中的方法:
     9         void lock()获取锁。
    10         void unlock()  释放锁。
    11     java.util.concurrent.locks.ReentrantLock implements Lock接口
    12 
    13 
    14     使用步骤:
    15         1.在成员位置创建一个ReentrantLock对象
    16         2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
    17         3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
    18  */
    19 public class RunnableImpl implements Runnable{
    20     //定义一个多个线程共享的票源
    21     private  int ticket = 100;
    22 
    23     //1.在成员位置创建一个ReentrantLock对象
    24     Lock l = new ReentrantLock();
    25 
    26     //设置线程任务:卖票
    27     @Override
    28     public void run() {
    29         //使用死循环,让卖票操作重复执行
    30         while(true){
    31             //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
    32  l.lock();
    33 
    34             //先判断票是否存在
    35             if(ticket>0){
    36                 //提高安全问题出现的概率,让程序睡眠
    37                 try {
    38                     Thread.sleep(10);
    39                     //票存在,卖票 ticket--
    40                     System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    41                     ticket--;
    42                 } catch (InterruptedException e) {
    43                     e.printStackTrace();
    44                 }finally {
    45                     //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
    46                     l.unlock();//无论程序是否异常,都会把锁释放
    47                 }
    48             }
    49         }
    50     }
    51 
    52     /*//设置线程任务:卖票
    53     @Override
    54     public void run() {
    55         //使用死循环,让卖票操作重复执行
    56         while(true){
    57            //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
    58            l.lock();
    59 
    60             //先判断票是否存在
    61             if(ticket>0){
    62                 //提高安全问题出现的概率,让程序睡眠
    63                 try {
    64                     Thread.sleep(10);
    65                 } catch (InterruptedException e) {
    66                     e.printStackTrace();
    67                 }
    68 
    69                 //票存在,卖票 ticket--
    70                 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
    71                 ticket--;
    72             }
    73 
    74             //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
    75             l.unlock();
    76         }
    77     }*/
    78 }
  • 相关阅读:
    AC自动机
    KMP、扩展KMP、MANACHER
    Docker用户身份登录和管理员权限
    Response.Redirect和Server.Transfer比较--(转)
    SQLServer中char、varchar、nchar、nvarchar的区别--(转)
    MsSQL的字段类型--(转)
    读取UEditor编辑框内容到数据库和上传图片的配置
    以做产品的思想分析男女相处之道
    springBoot创建定时任务
    Runnable和Thread的区别
  • 原文地址:https://www.cnblogs.com/116970u/p/11487448.html
Copyright © 2011-2022 走看看