本文的目的不在于阐述这些知识点的原理或者是底层实现,而是想从使用的角度,给予读者一种直观的感受。毕竟语言是工具,想要精通,先学会用,再学会工具是怎么制造设计的,才是一种正确的学习路线。
直接看代码和注释:
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上进行同步)。没有两个线程可以使用相同的锁进入相同的同步块。 在上述代码中,锁定对象在每次执行时都会更改,因此同步无效。
即使您使用了 final
或 this
进行同步,代码仍然不正确。 两个线程可以首先同步读取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
锁吧(狗头