zoukankan      html  css  js  c++  java
  • 并发编程之关键字(synchronized、volatile)

      并发编程主要设计两个关键字:一个是synchronized,另一个是volatile。下面主要讲解这两个关键字,并对这两个关机进行比较。

    synchronized

       synchronized是通过JMV种的monitorentermonitorexit指令实现同步。monitorenter指令是在编译后插入到同步代码的开始位置,而monitorexit插入到同步代码的结束位置和异常位置。每一个对象都与一个monitor相关联,当monitor被只有后,它将处于锁定状态。

      当一个线程试图访问同步代码时,它必须先获得锁;退出或者抛出异常时,必须释放锁。Java中,每一个对象都可以作为锁。具体的表现形式有3种:

    • 对于普通的同步方法,锁是当前的实例对象(this对象)
    权限修饰符 synchronized 返回值类型 函数名(形参列表..){
           //函数体
    }
    • 对于静态同步方法,锁是当前类的Class对象
    权限修饰符 static synchronized 返回值类型 函数名(形参列表..){
           //函数体
    }
    • 对于同步方法块,锁是Synchronized括号中配置的对象
      • 锁对象必须是多线程共享的对象,否则锁不住
    Synchronized(锁){
       //需要同步的代码块
    }

      注意:在同步代码块/同步方法中调用sleep()不会释放锁对象,调用wait()会释放锁对象

      Synchronized提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候可能会被阻塞,而这种阻塞有两个明显的缺陷:1. 无法控制阻塞时长; 2. 阻塞不能被中断

    public class SyncDefect {
    
        /**
         *线程休眠一个小时
         */
        public synchronized void syncMethod(){
            try {
                TimeUnit.HOURS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    
        public static void main(String[] args) throws InterruptedException {
            SyncDefect defect = new SyncDefect();
            new Thread(defect::syncMethod,"t1").start();
    
            //休眠3毫秒后启动线程t2,确保t1先进入同步方法
            TimeUnit.MILLISECONDS.sleep(3);
            Thread t2 = new Thread(defect::syncMethod, "t2");
            t2.start();
    
            //休眠3毫秒后中断线程t2,确保t2已经启动
            TimeUnit.MILLISECONDS.sleep(3);
            t2.interrupt();
    
            System.out.println(t2.isInterrupted()); //true
            System.out.println(t2.getState());  //BLOCKED
        }
    }

       针对synchronized的两个缺点,可以使用BooleanLock来解决

    public interface Lock {
    
        void lock() throws InterruptedException;
    
        /**
         * 指定获取锁的超时时间
         * @param mills 等待获取锁的最大时间
         * @throws InterruptedException
         * @throws TimeoutException
         */
        void lock(long mills) throws InterruptedException, TimeoutException;
    
        void unlock();
    
        List<Thread> getBlockedThreads();
    }
    public class BooleanLock implements Lock {
    
        /**
         * 记录取得锁的线程
         */
        private Thread currentThread;
        /**
         * Bollean开关,标志锁是否已经被获取
         */
        private boolean locked = false;
    
        private List<Thread> blockedList = new ArrayList<>();
    
        @Override
        public void lock()  {
            //使用同步代码块的方式获取锁
            synchronized (this) {
                Thread currentThread = Thread.currentThread();
                //当锁已经被某个线程获取,将当前线程加入阻塞队列,并使用this.wait()释放thisMonitor
                while (locked){
                    try {
                        if(!blockedList.contains(currentThread)){
                            blockedList.add(currentThread);
                        }
                        this.wait();
                    } catch (InterruptedException e) {
                        blockedList.remove(currentThread);
                        e.printStackTrace();
                    }
                }
    
                blockedList.remove(currentThread);
                this.locked = true;
                this.currentThread = currentThread;
            }
        }
    
        @Override
        public void lock(long mills) throws InterruptedException, TimeoutException {
            synchronized (this){
                if(mills <= 0) {//时间不合法,调用默认的lock()
                    this.lock();
                } else {
                    long remainingMills = mills;
                    long endMills = System.currentTimeMillis() + remainingMills;
                    while (locked) {
                        if (remainingMills <= 0) {//在指定的时间内未获取锁或者当前线程被其它线程唤醒,抛出异常
                            throw new TimeoutException(Thread.currentThread().getName()+" can't get lock during "+mills);
                        }
                        if(!blockedList.contains(Thread.currentThread())){
                            blockedList.add(Thread.currentThread());
                        }
                        //等待remainingMills后重新尝试获取锁
                        this.wait(remainingMills);
                        remainingMills = endMills - System.currentTimeMillis();
                    }
                    blockedList.remove(Thread.currentThread());
                    this.locked = true;
                    this.currentThread = Thread.currentThread();
                }
            }
        }
    
        @Override
        public void unlock() {
            synchronized (this) {
                if(Thread.currentThread() == currentThread) {
                    this.locked = false;
                    this.notifyAll();
                }
            }
        }
    
        @Override
        public List<Thread> getBlockedThreads() {
            return Collections.unmodifiableList(blockedList);
        }
    }
    /**
    * 测试阻塞中断
    */
    public class BooleanLockInterruptTest {
    
        private final Lock lock = new BooleanLock();
    
        public void syncMethod() {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName()+" get lock.");
                TimeUnit.HOURS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("BLOCKED THREAD :"+lock.getBlockedThreads());
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            BooleanLockInterruptTest test = new BooleanLockInterruptTest();
    
            new Thread(test::syncMethod,"t1").start();
            TimeUnit.MILLISECONDS.sleep(3);
            Thread t2 = new Thread(test::syncMethod, "t2");
            t2.start();
            TimeUnit.MILLISECONDS.sleep(3);
            t2.interrupt();
            System.out.println(t2.isInterrupted()); //true
            System.out.println(t2.getState());  //RUNNABLE
        }
    }
    /**
    * 测试超时
    */
    public class BooleanLockTimeOutTest {
    
        private final Lock lock = new BooleanLock();
    
        public void syncTimeOutMethod() {
            try {
                lock.lock(1000);
                System.out.println(Thread.currentThread().getName()+" get lock.");
                TimeUnit.HOURS.sleep(1);
            } catch (InterruptedException | TimeoutException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            BooleanLockTimeOutTest test = new BooleanLockTimeOutTest();
    
            new Thread(test::syncTimeOutMethod,"t1").start();
            TimeUnit.MILLISECONDS.sleep(3);
            new Thread(test::syncTimeOutMethod, "t2").start();
        }
    }

       针对是synhronized还有一些概念及相关知识点需要补充

    • Monitor
      • 每一个对象都与一个Monitor相关联,一个monitor的lock在某一刻只能被一个线程获取。
      • monitor有一个计数器,当为0时,该monitor的lock未被获取;当有线程持获取monitor时,则monitor计数器加一,释放时减一。
      • Monitor分为This Monitor和Class Monitor。This Monitor对应类的实例方法,Class Monitor对应类的静态方法。
      • synchronized关键字实例方法时,争取的是同一个monitor的锁,与之关联的引用是ThisMonitor的实例引用。即:同一个类中的不同多线程方法,使用的是同一个锁
      • 将静态方法声明为synchronized。该静态方法被调用后,对应的class对象将会被锁住(使用的是ClassMonitor)。其他线程无法调用该class对象的所有静态方法, 直到资源被释放。
    • Synchronized使用wait()进入条件对象的等待集,使用notifyAll()/notify()唤醒等待集中的线程。
    public synchronized void transfer(int from , int to,
       double amount) throws InterruptedException{
            while(accounts[from] < amount){
                //wait on intrinsic object lock’s single condition
                 wait();
            }
            accounts[from] -= amount;
            accounts[to] += amount;
            //notify all threads waiting on the condition
            notifyAll();
    }

    volatile

      volatile是轻量级的synchronized,它为实例域的同步访问提供了一种免锁机制,不会引起线程上下文的切换和调度。它在多处理器开发中保证了共享变量的“可见性“,如果一个属性被声明成volatile,Java模型会确保所有的线程看到这个变量的值时一致的。【volatile变量不能提供原子性】

      volatile主要用来锁住一个属性,在对该属性的值进行写操作时,会将数据写回主存,并将CPU里缓存了该内存地址的数据无效。【线程在对volatile修饰的变量进行读写操作时,会首先检查线程缓存的值是否失效,如果失效,就会从主存中把数据读到线程缓存里】。 volatile本质上就是告诉JVM当前变量的值需要从主存中读取,当前变量的值被修改后直接刷新到主存中,且不需要被编译器优化(即:禁止命令重排)。

      使用示范:

    private volatile boolean done;
    public boolean isDone(){
        return done;
    }
    public void setDone(boolean done){
        this.done = done;
    }
    
    // Same as
    
    private boolean done;
    public synchronized boolean isDone(){
        return done;
    }
    public synchronized void setDone(boolean done){
        this.done = done;
    }

    比较synchronized和volatile

     
    volatile
    synchronized
    作用对象
    实例变量、类变量
    方法、代码块
    原子性
    不具备
    具备
    可见性
    具备
    具备
    可见性原理
    使用机器指令的方式迫使其它工作内存中的变量失效
    利用monitor锁的排它性实现
    是否会指令重排
    是否造成线程阻塞
     
     
  • 相关阅读:
    P3254 圆桌问题
    P4868 Preprefix sum
    2021sd省选游记
    P4145 上帝造题的七分钟2 / 花神游历各国
    P2801 教主的魔法
    P4147 玉蟾宫(悬线法)
    P1944 最长括号匹配
    CF1214D Treasure Island
    Loadrunner与kylinPET的能力对比测试--web动态请求
    Summer——从头开始写一个简易的Spring框架
  • 原文地址:https://www.cnblogs.com/BlueStarWei/p/11703653.html
Copyright © 2011-2022 走看看