并发编程之关键字(synchronized、volatile)

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

synchronized

   synchronized是通过JMV种的monitorenter和monitorexit指令实现同步。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锁的排它性实现

是否会指令重排



是否造成线程阻塞


原文地址:https://www.cnblogs.com/BlueStarWei/p/11703653.html

时间: 2024-11-29 06:27:01

并发编程之关键字(synchronized、volatile)的相关文章

【Java并发编程】6、volatile关键字解析&amp;内存模型&amp;并发编程中三概念

转自:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来

Java并发编程(三)volatile域

相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Android多线程(一)线程池 Android多线程(二)AsyncTask源代码分析 前言 有时仅仅为了读写一个或者两个实例域就使用同步的话,显得开销过大,volatile关键字为实例域的同步訪问提供了免锁的机制.假设声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被还有一个线程并发更新的. 再讲到volatile关键字之前我们须要了解一下内存模型的相关概念以及并发编程中的三个特性:原子性,可见

【Java并发编程实战】-----synchronized

在我们的实际应用当中可能经常会遇到这样一个场景:多个线程读或者.写相同的数据,访问相同的文件等等.对于这种情况如果我们不加以控制,是非常容易导致错误的.在java中,为了解决这个问题,引入临界区概念.所谓临界区是指一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问. 在java中为了实现临界区提供了同步机制.当一个线程试图访问一个临界区时,他将使用一种同步机制来查看是不是已经有其他线程进入临界区.如果没有则他就可以进入临界区,否则他就会被同步机制挂起,指定进入的线程离开这个临界区

Java并发编程底层实现原理 - volatile

Java语言规范第三版中对volatile的定义如下: Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致性的更新,线程应该确保通过排他锁 单独获得这个变量. volatile有时候比锁更加方便,比如一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的 值是一致性的. volatile是如何来保证可见性的?  需要查看Java代码转换成汇编代码之后,具体执行的过程可参考<Java并发编程的艺术> 第二章,或者其他资料.(主要是我对汇编不太熟) 还涉

【Java并发编程实战】—–synchronized

在我们的实际应用其中可能常常会遇到这样一个场景:多个线程读或者.写相同的数据,訪问相同的文件等等.对于这样的情况假设我们不加以控制,是非常easy导致错误的. 在java中,为了解决问题,引入临界区概念.所谓临界区是指一个訪问共用资源的程序片段,而这些共用资源又无法同一时候被多个线程訪问. 在java中为了实现临界区提供了同步机制.当一个线程试图訪问一个临界区时,他将使用一种同步机制来查看是不是已经有其它线程进入临界区. 假设没有则他就能够进入临界区,否则他就会被同步机制挂起,指定进入的线程离开

转: 【Java并发编程】之五:volatile变量修饰符—意料之外的问题(含代码)

转载请注明出处:     volatile用处说明     在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要. 在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写.这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致. 要解决这个问题,就需要

Java并发编程_volatile关键字的用法(二)

被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象. 根据下面实例理解: package sync; public class VolatileTest extends Thread{ //全局变量isRunning加不加Volatile的效果 private /*volatile*/ boolean isRunning = true; private void setRunning(boolean isRunning) { this.isRunnin

Java并发编程的艺术(六)——线程间的通信

多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java并发编程的艺术(三)--volatile 1.1 如何实现通信? 这两种方式都采用了同步机制实现多条线程间的数据通信.与其说是"通信",倒不如说是"共享变量"来的恰当.当一个共享变量被volatile修饰 或 被同步块包裹后,他们的读写操作都会直接操作共享内存,从而各个

Java并发编程(四)Java内存模型

相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 前言 此前我们讲到了线程.同步以及volatile关键字,对于Java的并发编程我们有必要了解下Java的内存模型,因为Java线程之间的通信对于工程师来言是完全透明的,内存可见性问题很容易使工程师们觉得困惑,这篇文章我们来主要的讲下Java内存模型的相关概念. 1.共享内存和消息传递 线程之间的通信机制有两种:共享内存和消息传递:在共享内存的并发模型里,线程之间共享程序的