zoukankan      html  css  js  c++  java
  • 解决线程安全问题的三种方法

    线程安全问题

    线程安全出现的根本原因:

        1.存在两个或者两个以上的线程对象共享同一个资源;

        2.多线程操作共享资源代码有多个语句。

    一、使用同步代码块

    如:卖票案例 出现了线程安全 重复的票不能出现

    步骤:成员位置建立锁对象;

    synchronized(锁对象){
      出现安全问题代码
    }

    注意事项:

    1 锁对象可以是任意对象 

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

    3 把{} 让一个线程进

    4.一个线程在同步代码块中sleep了,并不会释放锁对象;

      5.如果不存在线程安全问题,千万不要使用同步代码块;

      6.锁对象必须是多线程共享的一个资源,否则锁不住。

    例子:

    public class RunnableImpl implements  Runnable{
        // 定义共享资源   线程不安全
        private int ticket = 100;
        //在成员位置创建一个锁对象
        Object obj = new Object();
        // 线程任务  卖票
        @Override
        public void run() {
            while(true){
                //建立锁对象
                synchronized (obj){
                    if(ticket>0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //卖票操作
                        System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                        ticket--;
                    }
                }
            }
        }
    }

    二、使用同步方法(函数)解决多线程安全

    同步函数就是使用synchronized修饰一个函数

    步骤

    1 创建一个方法 修饰符添加synchronized
    2 把访问了 共享数据的代码放入到方法中
    3 调用同步方法

    注意事项

    同步函数注意事项:
    
            1.如果函数是一个非静态的同步函数,那么锁对象是this对象;
    
            2.如果函数是静态的同步函数,那么锁对象是当前函数所属的类的字节码文件(class对象);
    
            3.同步函数的锁对象是固定的,不能由自己指定。

    例子:

    public class RunnableImpl implements  Runnable{
        // 定义共享资源   线程不安全
        private int ticket = 100;
        // 线程任务  卖票
        @Override
        public void run() {
            while(true){
                payTicket();//调用下面synchronized修饰的方法
            }
        }
        public synchronized void payTicket(){
            if(ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //卖票操作
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
    推荐使用:同步代码块
    
        原因:
    
            1.同步代码块的锁对象可以由我们自由指定,方便控制;
    
            2.同步代码块可以方便的控制需要被同步代码的范围,同步函数必须同步函数的所有代码。

    三.使用lock锁

    原因:synchronized的缺陷

      synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?

     synchronized 的局限性 与 Lock 的优点 
    
      如果一个代码块被synchronized关键字修饰,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待直至占有锁的线程释放锁。事实上,占有锁的线程释放锁一般会是以下三种情况之一:
    
      1:占有锁的线程执行完了该代码块,然后释放对锁的占有;
    
      2:占有锁线程执行发生异常,此时JVM会让线程自动释放锁;
    
      3:占有锁线程进入 WAITING 状态从而释放锁,例如在该线程中调用wait()方法等。
    
      试考虑以下三种情况: 
    
    Case 1 :
    
      在使用synchronized关键字的情形下,假如占有锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程就只能一直等待,别无他法。
    这会极大影响程序执行效率。因此,就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间 (解决方案:tryLock(long time, TimeUnit unit))
    或者 能够响应中断 (解决方案:lockInterruptibly())),这种情况可以通过 Lock 解决。 Case 2 :   我们知道,当多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。
    但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。
    因此,需要一种机制来使得当多个线程都只是进行读操作时,线程之间不会发生冲突。同样地,Lock也可以解决这种情况 (解决方案:ReentrantReadWriteLock) 。 Case 3 :   我们可以通过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized无法办到的。 上面提到的三种情形,我们都可以通过Lock来解决,但 synchronized 关键字却无能为力。
    事实上,Lock 是 java.util.concurrent.locks包 下的接口,Lock 实现提供了比 synchronized 关键字 更广泛的锁操作,它能以更优雅的方式处理线程同步问题。也就是说,Lock提供了比synchronized更多的功能。
    总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:
    
      1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
    
      2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用
        而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象
    Lock接口实现类的使用
    Lock接口有6个方法:
    // 获取锁  
    void lock()   
    
    // 如果当前线程未被中断,则获取锁,可以响应中断  
    void lockInterruptibly()   
    
    // 返回绑定到此 Lock 实例的新 Condition 实例  
    Condition newCondition()   
    
    // 仅在调用时锁为空闲状态才获取该锁,可以响应中断  
    boolean tryLock()   
    
    // 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁  
    boolean tryLock(long time, TimeUnit unit)   
    
    // 释放锁  
    void unlock()

    注意
    lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用来获取锁的。
    unLock()方法是用来释放锁的。newCondition() 返回 绑定到此 Lock 的新的 Condition 实例 ,用于线程间的协作,详细内容请查找关键词:线程间通信与协作。

    参考:  https://www.cnblogs.com/myseries/p/10784076.html

            https://www.bilibili.com/video/BV1uJ411k7wy?p=323

  • 相关阅读:
    SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常见问题总结
    开涛spring3(9.4)
    开涛spring3(9.3)
    开涛spring3(9.2)
    开涛spring3(9.1)
    开涛spring3(8.4)
    分水岭算法——学习笔记
    【代码备份】pocs.m
    【代码备份】NLM插值
    【代码备份】原图降采样后进行NLM滤波
  • 原文地址:https://www.cnblogs.com/cy0628/p/14844768.html
Copyright © 2011-2022 走看看