美文网首页
Java基础-锁

Java基础-锁

作者: tom_xin | 来源:发表于2019-09-28 09:16 被阅读0次

        为了提高系统的资源利用率,促使了进程,线程的出现。进程和线程提高了系统CPU利用率的同时,又引出了一些其他的问题。
        这里仅讨论线程安全性的问题,因为多个线程中操作执行顺序是不可预测的,甚至会产生一些奇怪的结果。


    多线程造成的安全性问题

    下面通过几个简单的示例来看一下,在没有锁的情况下会存在什么问题。
    1、错误的单例模式

    public class SingleInstance {
        // 实例对象
        private static Object instance;
        
        /**
         * 获取该对象的实例
         */
        public Object getInstance() {
            if (instance == null) {
               instance = new Object();
            }
            return instance;
        }
    }
    

        该类提供的方法本意是,多次请求getInstance()方法,instance对象只初始化一次。在单线程的情况下貌似没什么问题,但是在多线程的情况下,就会存在问题了。
        假定线程A和线程B同时调用getInstance方法,A线程判断instance对象当前为null,实例化了一个对象。在A线程没有实例化完之前,执行了线程调度,B线程也访问当了if的判断条件中,发现instance也为null,也实例化了一个对象并赋值给了instance。最终结果就是A,B线程拿到了不同的对象实例。
    2、共享变量被访问

    public class Counter{
       private integer count = 0;
        
       public static Integer addCount() {
            return count++;
       }
    }
    

        上述是一个技术器类,它提供了统计每个线程访问服务器次数的作用。但是当多个线程同时并发访问,它的统计出来的数据就会存在误差。为什么会有这个问题呢?
        count++并不是一个原子操作,它主要包含这几步,读取count变量在内存中的值,将count值加一,得到的结果再赋值给count变量,这是一个 读取 -> 修改 -> 写入。假设现在有两个线程A和B,当前count变量的值为1,A线程对count变量进行了读取,修改的操作,在没有执行写入操作之前发生了系统调度将线程A挂起,线程B开始执行自己的操作,将B线程读取了count变量的值(count = 1),并进行了后续的操作,将计算的count = 2的结果赋值给了count,执行完毕。线程A继续执行,此时同样将count = 2 写入到count中。这样得到最后的结果count就会少记了一次线程的访问。


    如何保证线程安全性

        先来看一下线程安全是如何定义的?
        在线程安全性的定义中要求,多个线程之间的操作无论采用何种执行时序或交替方式,都要保证不变性条件不被破坏。
        Java中提供了锁来保证线程安全性,通过在指定的代码块中添加synchronized或ReentrantLock关键字来保证操作的原子性。
        当线程要访问一个被加锁的对象、方法或者代码块时,会自动获得锁,如果当前的锁被其他线程持有,则会将线程先挂起,等持有锁的线程将锁被释放后会发起信号唤醒阻塞中的线程,去抢占该锁。


    synchronized的用法

        synchronized是Java提供的一种内置锁,synchronized的用法可以分为三种
        1. 修饰类
        2. 修饰方法(静态方法|非静态方法)
        3. 修饰代码块(变量)

    1. 修饰类
    public class SynchronizedClass{
    
        public static Object instance = null;
    
         public Object getInstance() {
             synchronized(SynchronizedClass.class) {
                 if (null == instance) {
                   return new Instance();
                 }
                 return instance;
             }
         }
    }
    

    当synchronized修饰类时,不管是访问SynchronizedClass类的哪个实例对象,所有线程都会竞争同一把锁。

    1. 修饰静态方法
    public class LockStaticMethod{
    
        public static Object instance = null;
    
         public synchronized static Object getInstance() {
                if (null == instance) {
                   return new Instance();
                }
                return instance;
         }
    }
    

    当synchronized修饰静态方法时,不管是访问LockStaticMethod类的哪个实例对象中的getInstance方法,所有线程都会竞争同一把锁。

    1. 修饰非静态方法
    public class LockMethod{
    
        public static Object instance = null;
    
         public synchronized Object getInstance() {
                if (null == instance) {
                   return new Instance();
                }
                return instance;
         }
    }
    

    当synchronized修饰非静态方法时,不同实例对象持有的锁是相互不影响的。

    1. 修饰代码块(变量)
    public class LockVariable{
    
        public static Object instance = null;
    
         public synchronized Object getInstance() {
                synchronized(instance) {
                  if (null == instance) {
                     return new Instance();
                  }
                  return instance;
               }
         }
    }
    

    synchronized修饰变量同样也分静态变量和非静态变量,他们的含义同静态方法和非静态方法类似。
    下面看一下,当线程访问一个被加锁的方法时,执行过程是怎样的。


    image.png

    ReentrantLock的用法

        ReentrantLock类是Java 5.0才出现的新的加锁机制,ReentrantLock相比于synchronized提供的加锁机制更加灵活,并且提供了一些synchronized不具备的功能。例如:可中断的锁获取操作、公平队列、非块结构的锁,可定时的锁,可轮训的锁。下面我们来看一下这些特性的使用示例。

    1. 可中断的锁
      可中断的锁提供了对可取消任务加锁的需求,具体使用方式如下所示
        private ReentrantLock lock = new ReentrantLock();
    
        public void interruptedLock() throws InterruptedException {
            lock.lockInterruptibly();
            try {
                throw new InterruptedException();
            }finally {
                lock.unlock();
            }
        }
    
    1. 公平队列
      ReentrantLock提供了公平锁,线程按照它们发出请求的顺序来获得锁。ReentrantLock类提供了ReentrantLock(boolean fair)构造函数来初始化公平锁。使用时只要将参数设置为true即可。
    ReentrantLock fairLock = new ReentrantLock(true);
    
    1. 非块结构的锁
    
    
    1. 可定时的锁
      ReentrantLock提供了定时的方法tryLock(long timeout, TimeUnit unit),他能根据剩余时间来提供一个时限,如果操作不能在指定的时间内给出结果,那么就会使程序提前结束,示例如下:
    public class TimeTryLock {
    
        public static void main(String[] args) throws Exception {
            ReentrantLock reentrantLock = new ReentrantLock();
            if (reentrantLock.tryLock(100L, TimeUnit.SECONDS)) {
                System.out.println("success");
            }
        }
    }
    
    1. 可轮训的锁
      tryLock方法实现了有条件的获取锁的模式,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。可轮训锁提供了另一种选择:避免发生死锁。
      先来看一下在synchronized模式下死锁的情况
    public class SyncDeadLock {
    
        public void deadLock(Integer v1, Integer v2) {
            synchronized (v1) {
                System.out.println(Thread.currentThread() + "get object lock " + v1);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (v2) {
                    System.out.println(Thread.currentThread() + "get object lock " + v2);
                }
            }
        }
    
        public static void main(String[] args) {
            Integer v1 = 0;
            Integer v2 = 2;
            SyncDeadLock syncDeadLock = new SyncDeadLock();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    syncDeadLock.deadLock(v1, v2);
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    syncDeadLock.deadLock(v2, v1);
                }
            }).start();
    
        }
    }
    

    有了tryLock之后,通过tryLock就可以有效的避免死锁,tryLock可以尝试获取锁,如果没有获取到锁,可以主动将当前持有的锁释放掉,这样可以避免死锁发生的条件,看下面的例子:

    public class ReentrantLockDeadLock {
    
        private static Integer count = 10;
    
        public boolean tryLock(ReentrantLock lock1, ReentrantLock lock2) {
            while (true) {
                if (lock1.tryLock()) {
                    try {
                        System.out.println(Thread.currentThread() + " get " + lock1);
                        if (lock2.tryLock()) {
                            try {
                                System.out.println(Thread.currentThread() + " get " + lock2);
                            } finally {
                                System.out.println(Thread.currentThread() + " unlock " + lock2);
                                lock2.unlock();
                                System.out.println("count = " + --count);
                                if (count < 0) {
                                    return true;
                                }
                            }
                        }
                    } finally {
                        System.out.println(Thread.currentThread() + " unlock " + lock1);
                        lock1.unlock();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            ReentrantLock lock1 = new ReentrantLock();
            ReentrantLock lock2 = new ReentrantLock();
            ReentrantLockDeadLock mainLock = new ReentrantLockDeadLock();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        TimeUnit.MICROSECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    mainLock.tryLock(lock1, lock2);
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mainLock.tryLock(lock2, lock1);
                }
            }).start();
    
        }
    }
    

    相关文章

      网友评论

          本文标题:Java基础-锁

          本文链接:https://www.haomeiwen.com/subject/qrsysctx.html