一网打尽atomic, volatile, synchronized

本文的目的不在于阐述这些知识点的原理或者是底层实现,而是想从使用的角度,给予读者一种直观的感受。毕竟语言是工具,想要精通,先学会用,再学会工具是怎么制造设计的,才是一种正确的学习路线。


直接看代码和注释:

package Chapter3.AboutThreadPool;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.junit.Test;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class useReentrantLock {
	// 阿里规范方法:创建线程池
    private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
    private final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("bx-Thread-%d").setDaemon(true).build();
    ExecutorService es = new ThreadPoolExecutor(5, 25, 0L, TimeUnit.MILLISECONDS, queue, threadFactory);

    private int count = 0;
    private final Lock lock = new ReentrantLock();
    private volatile int vcount = 0;
    private AtomicInteger aicount = new AtomicInteger(0);

    /**
     * 不加锁,输出少于100000
     */
    @Test
    public void test1() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; ++i) {
            es.submit(()->{
                add();
            });
        }

        long endTime = System.currentTimeMillis();

        System.out.printf("Time consume: %d ms, count: %d\n", endTime - startTime, count);

    }

    /**
     * 使用synchronized加锁。
     * count数量正常,时间消耗并没有增加,可能因为任务简单吧
     */
    @Test
    public void test2() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; ++i) {
            es.submit(()->{
                synchronized (this) {
                    add();
                }
            });
        }
        long endTime = System.currentTimeMillis();
        System.out.printf("Time consume: %d ms, count: %d\n", endTime - startTime, count);
    }

    /**
     * 使用Reentrantlock的tryLock()
     * 输出不满100000
     */
    @Test
    public void test3() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; ++i) {
            es.submit(()->{
                // tryLock()触发时可以获得锁即获得,不可获得直接返回false不阻塞。
                if (lock.tryLock()) {
                    try {
                        add();
                    } finally {
                        lock.unlock();
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + "can't get the lock");
                }
            });
        }
        long endTime = System.currentTimeMillis();
        System.out.printf("Time consume: %d ms, count: %d\n", endTime - startTime, count);
    }

    /**
     *  使用ReentrantLock 的 lock() 
     * 输出正常到达100000
     */
    @Test
    public void test4() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; ++i) {
            es.submit(()->{
                lock.lock();
                try {
                    add();
                } finally {
                    lock.unlock();
                }
            });
        }
        long endTime = System.currentTimeMillis();
        System.out.printf("Time consume: %d ms, count: %d\n", endTime - startTime, count);
    }

    /**
     * !!!
     * 为什么输出是错的?
     * volatile不能控制并发吗?
     * 我记得volatile是简易版的synchronize啊。
     * 解析见下面:
     */
    @Test
    public void test5() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; ++i) {
            es.submit(()->{
                vadd();
            });
        }
        long endTime = System.currentTimeMillis();
        System.out.printf("Time consume: %d ms, vcount: %d\n", endTime - startTime, vcount);
    }

    /**
     * 使用AtomicInteger变量
     * 执行原子操作
     * 成功到达1000000
     */
    @Test
    public void test6() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000; ++i) {
            es.submit(()->{
                aiadd();
            });
        }
        long endTime = System.currentTimeMillis();
        System.out.printf("Time consume: %d ms, aicount: %d\n", endTime - startTime, aicount.get());
    }

    public void add() {
        // 一共三步:
        // 1、取count
        // 2、newCount = count + 1
        // 3. count = newCount
        count += 1;
    }

    public void vadd() {
        // 常见的错误写法:在volatile变量上进行非原子操作,并不能满足线程安全。
        // volatile的功能是刷新local memory满足可见性,然而他不能解决竞争性。
        vcount += 1;
    }

    public void aiadd() {
        aicount.getAndIncrement();
    }

}


针对上面的问题,附上一篇好文:
What is the difference between atomic / volatile / synchronized?

以下内容摘取自上面的链接:

使用volatile却无法同步的情况

private volatile int counter;

public int getNextUniqueIndex() {
  return counter++; 
  // counter+=n类似
}

该代码不正确。 它解决了可见性问题(volatile确保其他线程可以看到对计数器所做的更改),但仍然存在竞争状况。 这已被多次解释:增量前/后增量不是原子的。

volatile的唯一副作用是“刷新”缓存,以便所有其他方都可以看到最新版本的数据。 在大多数情况下,这太严格了; 这就是为什么volatile不被java设置为默认的原因。

多个synchronize锁导致的不同步

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

首先,synchronized()加锁的对象不能是将要改变的对象。(i是原始对象,所以应该是正在通过自动装箱创建的临时Integer上进行同步)。没有两个线程可以使用相同的锁进入相同的同步块。 在上述代码中,锁定对象在每次执行时都会更改,因此同步无效。

即使您使用了 finalthis 进行同步,代码仍然不正确。 两个线程可以首先同步读取i到temp(在temp中本地具有相同的值),然后第一个线程为i分配一个新值(例如,从1到6),而另一个线程执行相同的操作(从1到6)。 。

同步块必须覆盖从读取到分配值。 正确的形式应该为:

void synchronized incIBy5() {
  i += 5 
}

void incIBy5() {
  synchronized(this) {
    i += 5 
  }
}

void incIBy5() {
  synchronized(this) {
    int temp = i;
    i = temp + 5;
  }
}

不过我想,没有人会把应该同步进行操作的一个代码段故意拆成两半加synchronized锁吧(狗头

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值