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

    线程安全

    2.1 线程安全

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样

    的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    我们通过一个案例,演示线程的安全问题:

    电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个

    (本场电影只能卖100张票)。

    我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)

    需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟

    ![](E:云盘个人总结1_Java基础语法day06_线程、同步 总结img多线程安全问题.png)

    模拟票

    public class RunnableImpl implements Runnable{
        // 定义一个多线程共享的票源
        private int ticket = 6;
        // 设置线程任务,卖票
        @Override
        public void run(){
            // 使用死循环让卖票重复执行
            while(true){
                if(ticket>0){// 有票可卖
                    // 提高线程安全问题出现的概率,让程序睡眠
                    try{
                        Thread.sleep(100);
                    }catch(InterruptedException e){
                         e.printStackTrace();
                    }
                    // 票存在,卖票 ticket--
                    String name = Thread.currentThread().getName();
                    System.out.println(name+"正在卖第"+ticket--)
                }
            }
        }
    }
    

    实现类

    public static void main(String[] args){
        // 创建Runnable接口的实现类对象
     	RunnableImpl run = new RunnableImpl();
        // 创建thread类对象,构造方法中传递Runnable接口的实现类对象
        // public Thread() :分配一个新线程对象
        Thread t1 = new Thread(run,"窗口1" );
        Thread t2 = new Thread(run,"窗口2" );
        Thread t3 = new Thread(run,"窗口3" );
        
        // 调用Static方法开启多线程
        t1.start();
        t2.start();
        t3.start();
    }
    // 运行结果
    窗口2正在卖第6
    窗口3正在卖第6
    窗口1正在卖第5
    窗口1正在卖第4
    窗口3正在卖第3
    窗口2正在卖第4
    窗口1正在卖第2
    窗口2正在卖第0
    窗口3正在卖第1
    窗口2正在卖第-1
    

    发现程序出现了两个问题:

    1. 相同的票数,比如6这张票被卖了两回。
    2. 不存在的票,比如0票与-1票,是不存在的。

    这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

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

    ![](E:云盘个人总结1_Java基础语法day06_线程、同步 总结img线程安全问题产生的原理.png)

    2.2 线程同步

    当我们使用多个线程访问统一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题

    要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制

    (synchronized)来解决。

    根据案例简述

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

    同步操作

    有三种方式完成同步操作:

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

    2.3 解决线程安全方法一 同步代码块

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

    格式

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

    注意

    1. 通过代码块中的锁对象,可以使用任意的对象

    2. 但是必须保证多个线程使用的锁对象是同一个

    3. 锁对象作用:

      把同步代码块锁住,只让一个线程在同步代码块中执行

    使用同步代码块解决卖票案例出现的线程安全问题,卖出了不存在的票和重复票的问题

    Ticket类

    public class RunnableImpl implements Runnable{
       // 创建一个共享数据的票源
        private int ticket = 10;
        // 创建一个锁对象
        Object obj = new object();
        
        // 设置线程任务,卖票
        @Override
        public void run(){
            // 使用死循环让带吗重复操作
            while(true){
                // 同步代码块
                synchronized(obj){
                    // 先判断票是否存在
                if(tick>0){
                    // 提高安全问题出现的概率,让程序睡眠
                    try{
                        Thread.sleep(100);
                    }catch(InterrutedException e){
                        e.printStackTrace();
                    }
                    
                    // 票存在  卖票 ticket--
                	System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                    ticket--;
                }
                
            }
        }
    }
    

    测试类

    public static void main(String[] args){
        RunnableImpl run = new RunnableImpl();
        // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);
        // 调用start方法开启多线程
            t3.start();
            t1.start();
            t2.start();
    }
    // 运行结果一部分
    Thread-2正在卖第64张票
    Thread-1正在卖第63张票
    Thread-1正在卖第62张票
    Thread-1正在卖第61张票
    Thread-0正在卖第60张票
    Thread-1正在卖第59张票
    Thread-2正在卖第58张票
    Thread-2正在卖第57张票
    
    

    原理

    ![](E:云盘个人总结1_Java基础语法day06_线程、同步 总结img同步技术的原理.png)

    2.4 解决线程安全方法二 同步方法

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

    格式

    修饰符  synchronize 返回值类型 方法名称(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
    

    同步锁是谁?

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

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

    注意

    1. 通过代码块中的锁对象,可以是任意的对象

    2. 但必须保证多个 线程使用的锁对象是同一个

    3. 锁对象作用:

      ​ 把同步代码块锁住,只让一个线程在同步代码块中执行

    原理

    定义一个同步方法

    同步方法会把方法的内部的代码锁住

    只让一个线程执行

    同步方法的锁对象就是实现对象 new RunnableImpl()

    也就是this

    使用同步方法代码如下

    public class RunnableImpl implements Runnable{
        privat int ticked = 100;
        // 执行卖票操作
        @Override
        public void run(){
            while(true){
                
            }
        }
        // 锁对象 是 谁调用这个方法 就是谁
        // 隐含 锁对象 就是 this
        public synchronized void sellTicked(){
            if(ticked>0){// 有票可以卖
                //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //获取当前线程对象的名字 
                String name = Thread.currentThread().getName(); 
                System.out.println(name+"正在卖:"+ticket‐‐);
            }
        }
    }
    

    2.5 解决线程安全方法三 Lock锁

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

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

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

    • public void lock() :加同步锁。
    • public void unlock() :释放同步锁。
    public class Ticket implements Runnable{ 
        private int ticket = 100;
        Lock lock = new ReentrantLock();
        /** 执行卖票操作 */
        @Override public void run() {
            //每个窗口卖票的操作 
            //窗口 永远开启 
            while(true){ 
                lock.lock(); 
                if(ticket>0){//有票 可以卖
                    //出票操作 
                    //使用sleep模拟一下出票时间
                    try {Thread.sleep(50); 
                        } catch (InterruptedException e) {
                        // TODO Auto‐generated catch block 
                        e.printStackTrace();
                    }
                    //获取当前线程对象的名字
                    String name = Thread.currentThread().getName(); 
                    System.out.println(name+"正在卖:"+ticket‐‐); 
                }lock.unlock();
            } 
        }
    }
    
  • 相关阅读:
    Eclipse / android studio 添加第三方jar包 步骤
    Android checkbox 自定义点击效果
    Android 程序打包和安装过程
    Android 基础
    (转)Genymotion安装virtual device的“unable to create virtual device, Server returned Http status code 0”的解决方法
    (转)eclipse 导入Android 项目 步骤
    微信开放平台注册 步骤
    Android Studio 初级安装
    数组
    作用域问题代码
  • 原文地址:https://www.cnblogs.com/anke-z/p/12604283.html
Copyright © 2011-2022 走看看