并发编程之关键字(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 getBlockedThreads();
}
public class BooleanLock implements Lock {

    /**
     * 记录取得锁的线程
     */
    private Thread currentThread;
    /**
     * Bollean开关,标志锁是否已经被获取
     */
    private boolean locked = false;

    private List 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 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锁的排它性实现
是否会指令重排
是否造成线程阻塞
 
 

你可能感兴趣的:(并发编程之关键字(synchronized、volatile))