Java全家桶
集合
List
ArrayList
LinkedList
Set
Map
异常
异常的分类
Throwable、Exception、Error。Exception是可以被程序处理的异常,Error是不能被处理的错误,Exception又分别编译时Exception和运行时Exception,其中编译时Exception在代码调用处必须显示处理或抛出。
处理异常的最佳实现
- 涉及到外部资源的,要配合finally来释放资源;
- 在方法签名中声明异常要详细,不要直接抛出Exception或Throwable;
- 要再javadoc中提现异常信息;
- 正确描述异常信息,能够帮助快速定位问题,描述方式可以用特定类型的异常,也可以通过message来描述;
- 在编写处理异常的代码块时,要优先处理范围更小的特定异常;
- 不用吞掉异常,要记录、处理或抛出异常;
- 不要同时记录 并且 抛出异常,这会造成多个异常现场,不利益排查问题;
- 对于特定的模块、框架或工具,可以将异常统一包装成某个特定类型,方便统一处理和缩小排查问题的范围;
并发中的异常处理
异常的范围是线程,如果在一个线程中抛出了异常,这个线程就会中断。
- 利用try...catch进行处理;
设置线程级别的异常处理器;
class MyUncheckedExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("捕获到异常。异常栈信息为:"); e.printStackTrace(); } thread.setUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
设置全局异常处理器;
Thread.setDefaultUncaughtExceptionHandler(new MyUncheckedExceptionHandler());
通过FutureTask将线程内异常传递到调用线程;
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("子线程运行开始"); return 1 / 0; } });
- 对于一些比如基于spring mvc的web项目,一般会通过ExceptionHandler来处理异常;
IO
并发
当考虑在程序中引入多线程时,大部分情况是想解决程序的性能问题,另外也有可能是为了更好的编程模型,但引入一个新事物就会带来新的问题,并发编程也是如此。
多线程上下文切换会带来性能损耗
在线程数>cpu核数的情况下程序的并发执行是通过cpu切换时间片实现的,cpu在多个线程之间切换时间片时需要记录当前线程的状态,这就会带来时间和空间上的开销,所以,如果引入多线程带来的性能提升被线程上下文切换地消失,就需要重新考虑是否需要多线程。死锁问题
死锁会让线程一直处于wait状态,这会体现在业务上,所以死锁一般是不可接受的。出现死锁的原因在于多个线程之间互相等待对方是否资源,有以下几种方法可以在一定程度上避免出现死锁
a.避免在处理逻辑中获取多个锁资源或嵌套锁
b.使用超时锁来避免线程在死锁状态下一直wait
资源限制
程序性能提升的上限是所依赖的外部资源,而外部资源主要分为两种:硬件资源
和软件资源
,硬件资源例如cpu、磁盘读写速度、网卡带宽等,软件资源比如数据库连接和socket连接等,在资源限制范围之外使用多线程不仅不会带来性能提升,反而会让程序变得更慢。如果存在硬件资源限制,就要将并发数降低到资源可承受范围内,如果是软件资源限制,就要尽量将资源池化实现复用或尽量少的获取。
Java内存模型
并发编程的挑战主要来自两个方面,一是设计编程模型,二是解决并发安全问题,其中编程模型的设计属于程序员的工作,而解决并发安全问题是java的工作,换句话说,java要给程序员提供解决并发安全问题的工具。
在了解java内存模型之前先了解以下操作系统的内存模型,这有利于理解产生并发安全问题的根本原因。
现代计算机的cpu为了避免主内存的读写速度影响计算速度,都会引入多级高速缓存,在计算时先将数据从主内存加载到高速缓存中,计算完成后再写入主内存中,但在多cpu的硬件架构下会出现数据不一致的问题,为此引入了一种叫做MESI的缓存一致性协议,MESI的全称是Modified-Exclusive-Shared-Invalid,具体的含义如下:
假如有两个线程,要对主内存中的变量x执行x+=1操作;
1. 将线程A从主内存中将x变量读取到高速缓存时,总线上变量x的状态为E;
2. 线程B从主内存中将x变量读取到高速缓存总,总线上变量x的状态为S;
3. 线程A将计算结果写入主内存中,总线上变量x的状态为S;
4. 线程A写入x完成后,总线上变量x的状态为I,此时线程B再读取x时,会发现x已失效,
然后会重新从主内存中读取x到高速缓存;
除此之外,现代cpu还会使用一种叫做指令重排序
的技术来提高性能,即保证最终结果正确的情况下,改变cpu指令的执行顺序。java编译器的指令重排序实际也是这个道理。
以此为基础,我们所说的JMM(java内存模型)就可以从两个方面来理解:
- java线程模型
- 线程安全性保证
在java中,每个线程都有自己的工作内存,也就是线程栈
,整体的结构类似于cpu/多级缓存,然后通过JMM来解决数据的线程安全问题。
而这里的JMM简单理解就是java提供的各种同步工具,比如synchronized/volatile/final/ReentrantLock/CountdownLatch等。
线程基础
线程与进程
进程是操作系统分配资源的基本单位,线程是cpu运行的基本单位,一个进程内包含多个线程。
并发与并行
并发指的是两个或多个线程间隔发生;
并行指的是两个或多个线程在同一时刻同时发生;
并发得益于cpu时间片轮转,并行得益于多cpu和多核cpu的硬件架构;
创建线程的方式
new Thread().start();
new Runnable();
new Callable();
new FutureTask(Runnale/Callable)
守护线程
守护线程是一种支持线程,当主线程停止后,守护线程会立马终止。
package concurrent;
import java.util.concurrent.TimeUnit;
public class Daemon {
public static void main(String[] args) {
Thread t = new Thread(new DaemonRunner());
t.setDaemon(true);
t.start();
}
}
class DaemonRunner implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(10);
}catch (Exception ex) {}finally {
//无效,主线程退出后,守护线程会立马终止
System.out.println("Daemon finally");
}
}
}
线程中断
中断是一种线程是否被中断了的标识,通过Thread.interrupt()方法对某个线程进行中断,此时该线程的中断标识=true(Thread.isInterrupted()),但如果是调用Thread.interrupted(),它会返回中断状态的同时,复位中断状态,另外当线程抛出InterruptedException时,中断状态也会被复位。
wait/notify
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
static class Wait implements Runnable {
@Override
public void run() {
synchronized (lock) {
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true. wait@" +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
}catch (Exception ex) {}
}
System.out.println(Thread.currentThread() + " flag is false. running @" +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock. notify@" +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lock) {
System.out.println(Thread.currentThread() + "hold lock again. sleep@"
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
join
如果主线程通过join的方式将另一个线程A加入到执行序列中,那么主线程会一致等待线程A执行完成,才能继续往下执行。
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("join thread");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
thread.join();
System.out.println("main thread");
}
同步组件
synchronized
synchronized在java中是实现同步的最常用的工具,在java6之前它是一个重量级的锁,但在java6及以后引入了偏向锁和轻量级锁,性能得到了很大的提升。synchronized的用法主要有三种:
- 用在静态方法上,锁对象是当前类的class对象;
- 用在成员方法上,锁对象是当前对象;
- 同步代码块,锁对象是指定的对象;
synchronized在编译时java编译器会自动插入一组monitorenter和monitorexit指令,从而让cpu实现同步的功能。
下面说说synchronized的四种不同是锁状态:无锁、偏向锁、轻量级锁和重量级锁
,当只有一个线程获取锁时,锁状态为偏向锁
,此时并不会真正的加锁,而是设置锁对象的对象头的偏向线程id为当前线程,当有多个线程同时获取锁,但锁能够很快得到释放时,偏向锁会膨胀成轻量级锁,轻量级锁
的原理相比于偏向锁要复杂一些,首先尝试获取锁的线程会将锁对象的对象头复制到线程栈中,然后用cas的方式将锁对象的对象头指向自己线程栈中的锁对象头,抢占成功的线程就成功的获取了锁,但如果锁竞争继续加剧,轻量级锁就会膨胀为重量级锁,也就是会造成线程wait。java6引入了偏向锁和轻量级锁后,synchronized的性能得到了很大的提升,因为很多时候,synchronized都是处于这两种状态。
cas
cas是compare and set的缩写,是一种无锁实现线程安全的修改的方式,一般会配合while循环一起使用。juc中提供了的一系列atomic工具,底层就是基于cas实现无锁更新的,能够替换传统的重量级同步,提升程序的性能你。
volatile
被volatile修饰的变量能保证内存可见性,一般像一些状态变量都会用volatile来修饰。在原理方面,java编译器进行编译的时候,会插入一个Lock#指令,这个指令会使得cpu缓存中当前变量的副本值失效,从而重新从主内存中读取数据,可以理解成是基于SEMI协议的一种一致性保证。
atomic
atomic包中的类基本都是利用cas+自旋的方式实现无锁的线程安全操作的,主要的atomic工具类有如下这些:
AtomicInteger
AtomicBoolean
AtomicLong
AtomicReference
AtomicStampedReference
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceUpdater
Lock
Lock lock = new ReentrantLock();
lock.lock();
try {
....
}finally {
lock.unlock();
}
相比于sychronized,Lock锁具有如下优势:
- 能够非阻塞的尝试获取锁
- 锁能够响应中断
- 获取锁时支持超时
AQS
AQS的全称是AbsractQueuedSynchronizer,它是juc包中一个非常重要的同步器,很多高级的同步组件都是基于AQS实现的。在AQS内部有一个int类型的状态变量,然后通过内部的同步/等待队列来实现现在的互斥等待。使用AQS时,一般是提供了一个AQS的子类,然后通过AQS提供的方法来操作内部的状态变量,进而修改同步状态。
getState()
setState(int newState)
compareAndSetState(int expect, int update);
举例如下:
public class Mutex implements Lock {
//内置AQS同步组件
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
@Override
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@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();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
AQS通过同步队列和等待队列实现互斥和等待通知的,队列中的节点为Node,内部结构如下:
属性 | 描述 |
---|---|
waitStatus | CANCELLED -> 超时或中断; SIGNAL -> 释放同步状态时通知后继节点; CONDITION -> 在等待队列中; PROPAGATE -> 共享模式下唤醒后继节点时,无条件向后传播;INITIAL -> 初始状态 |
prev | 前驱节点 |
next | 后继节点 |
nextWait | 等待队列中的后继节点 |
thread | 等待队列中的后继节点 |
同步队列:同步队列是一个FIFO队列,用来存放获取同步状态失败的线程,具体结构如下图所示。
当某个线程获取同步线程失败后,会被构造成一个Node节点然后通过cas加自旋的方式添加到同步队列的末尾;同步队列的头节点是成功获取同步状态的线程,当同步状态被释放后,会通过LockSupport.unpark唤醒它的后继节点代表的线程,从而实现了一个FIFO队列。
独占模式:借助AQS的acquire/relase方法就可以实现独占模式(例如ReentrantLock),下面看一下这两个方法的具体逻辑(总的流程和上述同步队列的处理流程一致)。
public final void acquire(int arg) {
//尝试获取同步状态
if (!tryAcquire(arg) &&
//进入自旋状态
acquireQueued(
//构造Node节点并添加到同步队列末尾
addWaiter(Node.EXCLUSIVE), arg)
)
selfInterrupt();
}
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//快速尝试,如果失败再走cas + 自旋
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
//自旋 + cas 线程安全的将节点加入到同步队列末尾
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//cas
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//进入自旋 + 阻塞状态
for (;;) {
final Node p = node.predecessor();
//如果前驱节点时头节点,并且获取同步状态成功,则成功获取同步状态
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
//否则将当前线程通过LockSupport.park挂起
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//释放同步状态
public final boolean release(int arg) {
//通过模板方法释放同步状态
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒后继节点
unparkSuccessor(h);
return true;
}
return false;
}
独占模式下获取同步状态的具体流程如下:
共享模式:共享模式在获取同步状态和释放同步状态时与独占模式都有所区别
- 获取同步状态时,只要tryAcquireShare返回值>0,就表示成功;
- 在释放同步状态时,由于可能存在多个线程同时释放的情况,所以需要通过cas+自旋的方式来线程安全的释放同步状态;
获取同步状态:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
//添加节点到队列尾部
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
//进入到自旋转状态
for (;;) {
final Node p = node.predecessor();
//判断前驱节点是否为头节点
if (p == head) {
int r = tryAcquireShared(arg);
//尝试获取同步状态
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
释放同步状态:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
//自旋+ CAS
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
响应中断:响应中断的逻辑其实就是在自旋的过程中,判断线程的中断状态,并抛出InterrutedException。有一点需要注意,如果当前线程已经在同步队列中等待,那么即使对线程执行中断也也不会立即抛出异常,而是等到线程被前驱节点唤醒后才能响应中断。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//从自旋等待中恢复后,如果被中断,则抛出异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
超时获取同步状态:超时锁的核心逻辑是要保证超时时间的准确
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
//计算剩余时间
nanosTimeout = deadline - System.nanoTime();
//如果小于0则超时
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
//如果>1000ms,则将当前线程挂起,否则不挂起,进入自旋等待,保证时间的准确性
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ReentrantLock
ReentrantLock是基于AQS的独占模式构造的一个同步锁,具体的实现原理如下:
//在ReentrantLock锁的内部,构造了一个AQS的子类,然后复写相关方法
//操作同步状态
//ReentrantLock支持公平模式和非公平模式,非公平模式在获取锁是能够
//对同步状态进行抢占,而公平模式则是乖乖的进行排队
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//公平模式获取同步状态,立即进行抢占,如果抢占成功则返回true
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//如果锁是由当前线程持有,则直接增加同步状态的值即可
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//持有锁的线程的判断
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
//返回一个等待队列,用来实现等待通知机制
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
/**
* Sync object for non-fair locks
* 非公平模式
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//立即抢占
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//公平模式,直接进行排队
final void lock() {
//前面讲到,AQS内部会调用tryAcquire
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//如果没有等待线程,并且获取同步状态成功了,表示获取锁成功
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
ReentrantReadWriteLock
CountdownLatch
CyclicBarrier
Semaphore
Exchanger
Phaser
并发容器
ConcurrentHashMap
CopyOnWriteArrayList
ConcurrentLinkedQueue
阻塞队列
ArrayBlockingQueue:由数组组成的有界阻塞队列
LinkedBlockingQueue:由链表组成的有界阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
DelayQueue:使用优先级队列实现的无界阻塞队列
SynchronousQueue:不存储元素的阻塞队列
LinkedTransferQueue:由链表组成的无界阻塞队列
LinkedBlockingDeque:由链表组成的双向阻塞队列
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。