最近一直在学习和实践JAVA并发编程,也从书中总结了一些经验,在这里书写一下可以马上上手利用的内容,日后再慢慢补充完善。
1.在构建守护线程时,不能依靠finally块中的内容,来确保执行关闭或清理资源的逻辑。
有如下示例代码:
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("理论上,是执行不了这个方法的,因为线程是守护线程");
}
}
});
thread.setDaemon(true);
thread.start();
System.out.println("Main线程执行完毕");
}
}
运行结果是只会打印出“Main线程执行完毕这一句话”
2.java.util.concurrent.atomic包提供了高效的,线程安全地更新一个变量方式。
2.1原子更新基本类型类:原子方式更新基本数据类型
AtomicBoolean,AtomicInteger,AtomicLong
2.2原子更新数组:通过原子的方式更新数组里某个元素
AtomicBoolean,AtomicInteger,AtomicLong
2.3原子更新引用类型:原子更新多个变量
AtomicReference,
AtomicReferenceFieldUpdater等
2.4原子更新字段类:如果需原子地更新某个类里的某个字段时,就需要用到原子更新字段类
AtomicIntegerFieldUpdater等
当然,有人可能会问,什么是原子方式更新呢?这里不展开讨论(因为要涉及到内存模型,以及一个变量经历加载和写入等过程),仅贴两段代码,表示一下差异:
public class Main {
// 哪怕这里加了volatile也是没用的,因为volatile可以保证线程之间的可见性,并不能保证原子性
static volatile int n = 0;
static AtomicInteger m = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
Thread t1 = new Thread() {
@Override
public void run() {
// 这里结束的数值不能设置得太小,因为太小的话,速度太快,无法体现出效果
for (int i = 0; i < 10000; i++) {
// 你这里写成n = n + 1或者n +=1,也是一样基本得不到正确结果的
n++;
m.incrementAndGet();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
n++;
m.incrementAndGet();
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("n" + n);
System.out.println("m" + m.get());
}
}
运行结果:
毫无疑问的,基本上你再怎么运行,n都是得不到正确答案(偶尔会有正确答案,但是随着结束值越大,出现的几率就越小),而m总是能得到正确答案,这就是原子性的一个体现,具体使用请看API。
3.等待多线程完成的CountDownLatch(更巧妙的Join)
可以指定,等待N个点(线程)完成(到达指定位置)后,阻塞的线程才继续运行下去。也可以指定只阻塞多长一段时间。

4.同步屏障CyclicBarrier

12.适合做缓存和定时任务调度的队列DelayQueue
Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时,将发生到期。即使无法使用 take 或 poll 移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size 方法同时返回到期和未到期元素的计数。此队列不允许使用 null 元素。
TIP:需要注意的是,这个玩意只能使用一次。也就是说,下次还要使用时,必须重新new一个新的。
示例代码(可与上面的用了join的例子对比一下,仔细想一想会发现,比使用Join要灵活和方便很多):
public class Main {
static volatile CountDownLatch latch = new CountDownLatch(2);
public static void main(String[] args) throws Exception {
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("线程1准备睡眠");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("线程1睡眠结束");
latch.countDown();
}
};
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("线程2准备睡眠");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("线程2睡眠结束");
latch.countDown();
}
};
t1.start();
t2.start();
System.out.println("main线程要准备被阻塞了");
latch.await();
System.out.println("main线程得以继续执行");
}
}
运行结果:
4.同步屏障CyclicBarrier
可以实现,直到N个线程都到达屏障,就是每个线程都调用await()的之后,所有调用了await()的线程才不会被阻塞,才会继续运行下去,但是这个时候,并不能保证这N个线程的执行顺序。
TIP:这个玩意有reset()方法,而且还能设置一个回调,在阻塞线程重新执行下去之前,可以先执行一下回调函数的里面的方法,所以可以处理更复杂的业务场景。
可能说得不是很清楚,所以请看示例代码:
// 为了显示时间方便,直接使用了new Date().toLocaleString(),但是这是一个被废弃的方法,而且实际开发中,不应该这样写
public class Main {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
public void run() {
System.out.println("已经有两个线程执行了await()方法了"
+ new Date().toLocaleString());
}
});
public static void main(String[] args) throws Exception {
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("线程1准备睡眠" + new Date().toLocaleString());
try {
Thread.sleep(1000);
System.out.println("线程1睡眠结束" + new Date().toLocaleString());
cyclicBarrier.await();
} catch (Exception e) {
}
System.out.println("线程1await结束" + new Date().toLocaleString());
}
};
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("线程2准备睡眠" + new Date().toLocaleString());
try {
Thread.sleep(2000);
System.out.println("线程2睡眠结束" + new Date().toLocaleString());
cyclicBarrier.await();
} catch (Exception e) {
}
System.out.println("线程2await结束" + new Date().toLocaleString());
}
};
t1.start();
t2.start();
System.out.println("main线程要准备被阻塞了" + new Date().toLocaleString());
System.out.println("main线程得以继续执行" + new Date().toLocaleString());
}
}
5.控制并发线程数Semaphore
简单来说就是,最多同时只允许多少个线程获取许可证,只有获取到了许可证的,才能执行接下来的方法,其他的都在等待获取许可证。
6.线程之间交换数据Exchanger
它提供了一个同步点,在这个同步点,两个线程可以交换彼此的数据。如果第一个线程先执行exchange()方法,它会一直等待等到第二个线程也执行exchange()方法。
7.CachedThreadPool
这个线程池,使用的是一个没有容量的SynchronousQueue,这是一个没有容量的阻塞队列,每个插入操作必须等待另一个线程的对应移除操作。而且,这个线程池的maximumPool是无界的,这就意味着,如果主线程提交任务的速度非常快的话,这个线程池是会一直不断的创建新线程,最终有可能耗尽CPU和资源。
8.合理配置线程池
cpu密集型任务,应该配置尽可能小的线程,推荐配置cpu个数+1个线程数的线程池。由于IO密集型任务并不是一直在执行任务,则应配置尽可能多的线程,比如2*cpu个数。
9.避免死锁的常见方法
- 避免一个线程同时获取多把锁
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
public class Main {
static Object object = new Object();
public static void main(String[] args) throws Exception {
methodA();
}
public static void methodA() {
System.out.println("methodA想锁住了object");
synchronized (object) {
System.out.println("methodA锁住了object,并且准备调用methodB");
methodB();
}
}
public static void methodB() {
// 因为JAVA的锁是可重入的,所以这里另开一个线程
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("methodB想锁住了object");
synchronized (object) {
System.out.println("methodB锁住了object,并且准备调用methodA");
methodA();
}
}
};
thread.start();
try {
// methodB想等待线程执行完之后,才结束,但是它不知道methodA调用methodB的时候,已经锁上了object,而methodB又在线程中企图锁object,于是就发送了死锁。这是一种容易被忽略的死锁情况
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
10.队列同步器AbstractQueuedSynchronizer
这个玩意是用来构建锁或者其他同步组件的基础框架,用了一个int成员表示同步状态,通过内置的先进先出队列完成资源获取线程的排队工作。它是实现锁的关键,简化了锁的实现方式。
//一个独占锁的简易实现(不可重入)
class Mutex implements Lock {
private static class Sync extends AbstractQueuedSynchronizer {
/**
*
*/
private static final long serialVersionUID = -642099341499014369L;
/**
* 是否处于占用状态
*/
@Override
protected boolean isHeldExclusively() {
// 1表示锁被占用
return getState() == 1;
}
/**
* 当状态为0的时候获取锁
*/
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 释放锁并将状态设置为0
*/
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
Condition newCondition() {
return new ConditionObject();
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public boolean tryLock(long time, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
}
支持多个线程同时访问的锁(不可重入)
// 支持共享式访问(即同一时刻支持多个线程访问)
class ArbitraryCountLock implements Lock {
private int lockCount = 2;
private final Sync sync;
public ArbitraryCountLock(int lockCount) {
super();
this.lockCount = lockCount;
sync = new Sync(lockCount);
}
public void lock() {
sync.acquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryLock() {
return sync.tryAcquireShared(1) > 0 ? true : false;
}
public boolean tryLock(long time, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public int getLockCount() {
return lockCount;
}
private static class Sync extends AbstractQueuedSynchronizer {
/**
*
*/
private static final long serialVersionUID = -642099341499014369L;
Sync(int count) {
if (count < 1) {
throw new RuntimeException("锁的数量最小为1");
}
setState(count);
}
// 在失败时返回负值;如果共享模式下的获取成功但其后续共享模式下的获取不能成功,则返回
// 0;如果共享模式下的获取成功并且其后续共享模式下的获取可能够成功,则返回正值
@Override
protected int tryAcquireShared(int reduceCount) {
while (true) {
int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int returnCount) {
while (true) {
int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
Condition newCondition() {
return new ConditionObject();
}
}
}
11.LockSupport工具
static Object | getBlocker(Thread t) 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。 |
static void | park() 为了线程调度,禁用当前线程,除非许可可用。 |
static void | park(Object blocker) 为了线程调度,在许可可用之前禁用当前线程。 |
static void | parkNanos(long nanos) 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。 |
static void | parkNanos(Object blocker, long nanos) 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。 |
static void | parkUntil(long deadline) 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。 |
static void | parkUntil(Object blocker, long deadline) 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。 |
static void | unpark(Thread thread) 如果给定线程的许可尚不可用,则使其可用。 |
Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部 是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且 poll 将返回 null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于等于 0 的值时,将发生到期。即使无法使用 take 或 poll 移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size 方法同时返回到期和未到期元素的计数。此队列不允许使用 null 元素。