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

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


synchronized


 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原理

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

  • 编码
package concurrency;

public class SynchronizedDemo {

    public static void test() {
        synchronized (SynchronizedDemo.class) {
            System.out.println("Synchronized Demo");
        }
    }

    public static void main(String[] args) {
       test();
    }
}
  • 查看反编译汇编命令
javap -c SynchronizedDemo.class

 

锁对象

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

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

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

 

synchronized的缺陷及解决方案

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();
    }
}

   

 


volatile


 volatile特性

  • 内存可见性:是指线程修改了变量的值之后,其他线程能立即知道此值发生了改变。而线程在对volatile修饰的变量进行读写操作时,每次都会从主存中把数据读到线程缓存里(保证访问到的值是最新的)。【确保所有的线程看到这个变量的值时一致的
//没有volatile,if判断时,flag一直会读取本地缓存中的值,一直是false。
//private static  boolean flag = false;
//有volatile,if判断时,flag会从内存中重新读取值,flag被线程修改后会读取到新的值。
private static volatile boolean flag = false;

@Test
public void testVolatile() throws InterruptedException {
    new Thread(() -> {
        flag = false;
        System.out.println("Flag is modified");
    }).start();


    while (true) {
        if (flag) {
            System.out.println("Flag is true.");
            break;
        }
    }
}
  • 禁止命令重排:指令重排是指在执行程序时,编译器和处理器常常会对指令进行重排序,已到达提高程序性能的目的。volatile会告诉JVM当前变量的值不需要被编译器优化。
private static int a,b,x,y;

@Test
public void testResort() throws InterruptedException {
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        new Thread(() -> {
           // 有可能发生指令重排,先 x=a 再 b=1
            b=1;
            x=a;
        }).start();
        new Thread(() -> {
            // 有可能发生指令重排,先 y=b 再 a=1
            a=1;
            y=b;
        }).start();


        Thread.sleep(100);


        if(x==0 && y==0) {
            System.out.printf("第%d次,x==0 && y==0\n",i);
            break;
        }else {
            System.out.printf("第%d次,x==%d && y==%d\n",i,x,y);
        }
    }
  • volatile不是线程安全的,不能提供原子性【volatile不适用于修饰用于算数运算的变量,多次运行结果都不相同】
private static volatile int num = 0;
@Test
public voidtestAtomic() throws InterruptedException {
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                num++;
            }
        }).start();
    }

    Thread.sleep(2000);

    System.out.println(num);
}

实现原子性,需要使用Atomic【Atomic是线程安全的。每次执行结果都等于100000】

private static AtomicInteger count = new AtomicInteger(0);

@Test
public void testAtomic() throws InterruptedException {
    for (int i = 0; i < 100; i++) {
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                count.incrementAndGet();
            }
        }).start();
    }

    Thread.sleep(2000);

    System.out.println(count);
}

 

 


比较synchronized和volatile


 

 
volatile
synchronized
作用对象
实例变量、类变量
方法、代码块
原子性
不具备
具备
可见性
具备
具备
可见性原理
使用机器指令的方式迫使其它工作内存中的变量失效
利用monitor锁的排它性实现
是否会指令重排
是否造成线程阻塞
 
 
posted @ 2019-10-19 14:22  blue星空  阅读(303)  评论(0编辑  收藏  举报