volatile、synchronized、lock学习笔记

volatile

  • volatile自身特性

java中使用volatile关键字来保证可见性。
可见性:当对一个变量修改后,会立即更新到主存中,其它线程使用该变量时,会读取主存中的最新值。
相对于没有volatile修饰的共享变量,其它线程读取该变量的值有可能是缓存中的值。下面是一个用于理解的例子:

//共享变量
boolean stopFlag=false;

        //线程1
        new Thread(
                new Runnable() {

                    public void run() {
                        while(!stopFlag){//操作2
                            System.out.println("do something...");
                        }
                    }
                }
                ).start();

        //线程2
        new Thread(
                new Runnable() {

                    public void run() {
                        stopFlag=true;//操作1
                    }
                }
                ).start();

如果没有volatile ,操作1会将stopFlag保存到工作内存,不确定何时刷新到主存区。此时线程1读取将stopFlag=false读入到自己的工作内存,会继续执行循环。而使用volatile修饰后,stopFlag修改时会立即更新到主存,此时线程1工作内存中的stopFlag失效,必需从主内存中重新读取。

  • volatile对内存可见性影响
    一个简单的例子:
     static int a=0;//普通共享变量
     static boolean flag = false;

    public static void main(String[] args) {
        //线程1
        new Thread(
                new Runnable() {
                    public void run() {
                 if(flag){//操作1
                     int x=100/a;//操作2
                 }
                    }
                }
                ).start();;

        //线程2
        new Thread(
                new Runnable() {
                    public void run() {
                    a = 10;//操作3
                    flag = true;//操作4
                    }
                }
                ).start();
    }

代码执行时,编译器和处理器有可能对代码执行顺序进行重排列。如果代码按照 操作4-操作1-操作2这样的顺序执行,程序会抛出异常。解决方法是用volatile修饰flag变量,用volatile修饰后,编译器和处理器不会对代码的运行顺序重排列。实现原理简单理解:使用volatile修饰后,会对flag加屏障,保证操作3完成并写入主内存后才执行操作4。

synchronized

看过大牛们写的synchronized底层实现的文章,可惜自己基础知识不过关,看不懂。暂且总结一下synchronized使用规则,待以后再学习底层实现:

  1. 使用synchronized产生的对象锁。例如synchronized修饰对象方法或属性,修饰代码块synchronized(Object object)(object为对象参数)。
  2. 使用synchronized产生的类锁。例如synchronized来修饰静态方法或属性,修饰代码块 synchronized(Class class)。
//定义一个类,包含一个synchronized修饰的静态方法、一个synchronized修饰的对象方法、一个没有synchronized修饰的对象方法
 class SynchronsizedBlock {
    //修饰静态方法
    public  static synchronized void staticMethod() {
        for(int i = 0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+": 线程在执行 staticMethod 第"+i+"次");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //修饰对象方法
    public synchronized void objectMethod(){

        for(int i = 0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+": 线程在执行 objectMethod 第"+i+"次");

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //普通方法用于对比
    public  void referenceMethod(){

        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+": 线程在执行 referenceMethod 第"+i+"次");
        }
    }
}

写一个测试类,产生两个线程,调用上面的方法:

 public class ClientTest{
        public static void main(String[] args) {
        final SynchronsizedBlock synchronsizedBlock = new SynchronsizedBlock();
        //线程1获取synchronsizedBlock对象的锁
        new Thread(new Runnable() {
            public void run() {
                synchronized(synchronsizedBlock){
                    synchronsizedBlock.objectMethod();
                }

            }
        },"线程1").start();

        //线程1获取锁后,让线程2访问synchronsizedBlock对象的静态方法
        new Thread(new Runnable() {
            public void run() {
        SynchronsizedBlock.staticMethod();
            }
        },"线程2").start();
    }
}

这里写图片描述
这个结果很好理解,线程1执行synchronized(synchronsizedBlock) 获取synchronsizedBlock该对象的对象级别的锁,而staticMethod()方法属于类级别的方法,所以线程2可以访问。如果让线程1去调用referenceMethod()方法,线程2去调用objectMethod()方法的结果:

这里写图片描述
简单理解上面代码中synchronized(synchronsizedBlock) 获取对象锁,获取对象锁后,其它线程不能访问该对象中使用synchronsized修饰的对象方法。synchronized(this){}道理相同,获取当前对象的对象锁。

最后经典的Double-Check例子:

 //懒汉模式double check
 class SingletonFactory{
     private static SingletonTest singletonTest;
     public SingletonTest getInstance(){
     //firstcheck
     if(singletonTest==null){
         synchronized (this) {
         //secondcheck
             if(singletonTest==null){
                 return new SingletonTest();
             }
        }
     }
     return singletonTest;
     }
 }
 class SingletonTest{
 }

- Lock

Lock是个接口,实现类ReentrantLock,主要的方法有:lock(),lockInterruptibly(), tryLock(), tryLock(long paramLong, TimeUnit paramTimeUnit), unlock(),newCondition()
lock():获取锁
lockInterruptibly():获取所,和lock()相比线程等待时,可被中断
tryLock():如果得到锁返回true,没有得到返回false
tryLock(long paramLong, TimeUnit paramTimeUnit):线程等待一段事件后重新获取锁
unlock():释放锁
newCondition():绑定条件
用法举例:

static Lock lock = new ReentrantLock();
//使用tryLock()获取锁
public void lockMethod(){
    try{
    if(lock.tryLock()){
        for(int i= 0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"--执行lockMethod()"+i+"次");
                Thread.sleep(500L);
        } 
        lock.unlock();//释放锁
    }else{
        System.out.println(Thread.currentThread().getName()+"获取锁失败");
    }
    }catch(Exception e){
        e.printStackTrace();
    }
}

//使用lock获取锁
public  void lockMethod2(){

    try {
    lock.lock();
    for(int i= 0;i<5;i++){
        System.out.println(Thread.currentThread().getName()+"--执行LocktescLock2第"+i+"次");
            Thread.sleep(500L);
    }
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }finally{
        lock.unlock();//释放锁
    }
}

tryLock()比synchronized使用更加灵活,比如一个抢单的方法,只要保证有一个线程进入抢单即可,其它线程可直接返回。
使用lock中Condition实现 生产者-消费者问题:

产品类:

public class Product {
    private int id;
    public Product(int id){

        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @Override
    public String toString() {
        return "Product [id=" + id + "]";
    }

仓库类:

public class Warehouse {
    private int MaxProduct = 10;//仓库最大容量
    List<Product> products = new ArrayList<Product>();
    public List<Product> getProducts() {
        return products;
    }
    public void setProducts(List<Product> products) {
        this.products = products;
    }
    public int getMaxProduct() {
        return MaxProduct;
    }
    public void setMaxProduct(int maxProduct) {
        MaxProduct = maxProduct;
    }
}

生产者类:(负责生产产品)

public class Producer implements Runnable{
    Lock lock = null;
    Condition notFull = null;
    Condition notEmpty = null;
    Warehouse warehouse = null;
    public Producer(Lock lock,Condition notFull,Condition notEmpty){
        this.lock = lock;
        this.notFull = notFull;
        this.notEmpty = notEmpty;
    }

    //生产产品的方法
    public void produce(){
        while(true){
        try{
        lock.lock();
        //仓库已满
        while(warehouse.products.size()==warehouse.getMaxProduct()){
            System.out.println("仓库已满...");
            notFull.await();
        }
        System.out.println("仓库未满,生产产品...");
        //仓库未满,生产产品
        Product product = new Product((int)(Math.random()*100));
        warehouse.products.add(product);
        notEmpty.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
            System.out.println("生产者释放资源");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }
    }
    public void run() {
        produce();
    }
    public Warehouse getWarehouse() {
        return warehouse;
    }
    public void setWarehouse(Warehouse warehouse) {
        this.warehouse = warehouse;
    }
}

消费者类:(负责消费产品)

public class Consumer  implements Runnable{

    Lock lock = null;
    Condition notFull = null;
    Condition notEmpty = null;
    Warehouse warehouse = null;
    public Consumer(Lock lock,Condition notFull,Condition notEmpty){
        this.lock = lock;
        this.notFull = notFull;
        this.notEmpty = notEmpty;
    }
    public void consume(){
        while(true){
        try{
            lock.lock();
        //仓库中库存为0
        while(warehouse.products.size()==0){
            System.out.println("没有库存......");
            notEmpty.await();
        }
        //有库存
        System.out.println("有库存,消费产品......"+warehouse.products.get(0).toString());
        warehouse.products.remove(0);
        notFull.signal();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
            System.out.println("消费者释放资源");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }
    }
    @Override
    public void run() {
         consume();
    }
    public Warehouse getWarehouse() {
        return warehouse;
    }
    public void setWarehouse(Warehouse warehouse) {
        this.warehouse = warehouse;
    }
}

测试方法:

    public static void main(String[] args) {
    Lock lock = new ReentrantLock();//产生锁
    Condition notEmpty = lock.newCondition();//绑定条件
    Condition notFull = lock.newCondition();
    Warehouse wareHouse = new Warehouse();//仓库

    Producer producer = new Producer(lock, notFull, notEmpty);
    producer.setWarehouse(wareHouse);
    Consumer consumer = new  Consumer(lock, notFull, notEmpty);
    consumer.setWarehouse(wareHouse);
    Thread  thread1 = new Thread(producer);
    Thread  thread2 = new Thread(consumer);
    thread1.start();
    thread2.start();
    }

执行结果:
仓库未满,生产产品…
生产者释放资源
仓库已满…
有库存,消费产品……Product [id=40]
消费者释放资源
仓库未满,生产产品…
生产者释放资源
仓库已满…
有库存,消费产品……Product [id=20]
消费者释放资源
仓库未满,生产产品…
生产者释放资源
仓库已满…
有库存,消费产品……Product [id=53]
消费者释放资源
仓库未满,生产产品…
生产者释放资源

ReadWriteLock

ReadWriteLock读写锁接口,实现类有ReentrantReadWriteLock。接口:

public abstract interface ReadWriteLock {
    public abstract Lock readLock();//获取读锁
    public abstract Lock writeLock();//获取写锁
}

ReadWriteLock使用规则:
1)线程可以同时获取读锁,如果有线程获取读锁,其它线程不能获取写锁
2)只有一个线程可获得写锁,其它线程不能获取读锁和写锁。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值