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锁的排它性实现
    是否会指令重排
    是否造成线程阻塞
     
     
  • 相关阅读:
    BEC listen and translation exercise 44
    中译英12
    BEC listen and translation exercise 43
    中译英11
    BEC listen and translation exercise 42
    中译英10
    BEC listen and translation exercise 41
    中译英9
    BEC listen and translation exercise 40
    中译英8
  • 原文地址:https://www.cnblogs.com/BlueStarWei/p/11703653.html
Copyright © 2011-2022 走看看