70.原子变量和CAS
1.原子变量基于CAS(比较并交换、可看做一条指令)内部使用volatile:
//如果当前值等于expect,则更新为update,否则不更新,如果更新成功,返回true,否则返回false
public final boolean compareAndSet(int expect, int update)
public final int incrementAndGet() {
for (;;) {//无线循环
int current = get();
int next = current + 1;
if (compareAndSet(current, next))//如果current没有变就会return next,否则重试
return next;
}
}
2.synchronized(悲观锁-认为会冲突)代表一种阻塞式算法,得不到锁的时候,进入锁等待队列,等待其他线程唤醒,有上下文切换开销。
原子变量(乐观-认为冲突较少)的更新逻辑是非阻塞式的,更新冲突的时候,它就重试,不会阻塞,不会有上下文切换开销
3.使用场景:并发环境中的计数(AtomicInteger)、产生序列号(AtomicLong)
- 显示锁
1.显式锁接口Lock:
public interface Lock {
void lock();//就是普通的获取锁和释放锁方法,lock()会阻塞直到成功
void lockInterruptibly() throws InterruptedException;//与lock()的不同是,可以响应中断
boolean tryLock();//只是尝试获取锁,立即返回,不阻塞,如果获取成功,返回true,否则返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//尝试获取锁,如果成功则立即返回true,
否则阻塞等待time时长,等待期间可以响应中断。如果在等待的时间内获得了锁,返回true,否则返回false
void unlock();//就是普通的获取锁和释放锁方法,lock()会阻塞直到成功
Condition newCondition();//新建一个条件,一个Lock可以关联多个条件
}
2.相比synchronized,显式锁支持以非阻塞方式获取锁、可以响应中断、可以限时,可以指定公平性、可以解决死锁问题
3.Lock接口的主要实现类是ReentrantLock,它的基本用法lock/unlock实现了与synchronized一样的语义,包括:
a.可重入,一个线程在持有一个锁的前提下,可以继续获得该锁
b.可以解决竞态条件问题
c.可以保证内存可见性
一般使用方法:
public class Counter {
private final Lock lock = new ReentrantLock();
private volatile int count;
public void incr() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
4.使用tryLock避免死锁。
A获得了a锁,需要申请b锁,与此同时,B获得了b锁,需要申请a锁,这样就会死锁(使用lock的方式),
如果使用tryLock则可以避免,因为A如果申请不到b锁会释放a锁返回失败,B也一样。应该这么使用:如果失败则重试
boolean success = false;
do {
success = tryTransfer(from, to, money);//内部使用tryLock获取锁
if (!success) {
Thread.yield();
}
} while (!success);
5.获取锁信息:
public boolean isLocked()//锁是否被持有,只要有线程持有就返回true,不一定是当前线程持有
public boolean isHeldByCurrentThread()//锁是否被当前线程持有
public int getHoldCount()//锁被当前线程持有的数量,0表示不被当前线程持有
public final boolean isFair()//锁等待策略是否公平
public final boolean hasQueuedThreads()//是否有线程在等待该锁
public final boolean hasQueuedThread(Thread thread)//指定的线程thread是否在等待该锁
public final int getQueueLength()//在等待该锁的线程个数
6. LockSupport
LockSupport.park()使当前线程放弃CPU,进入等待状态(WAITING),其他线程对它调用了LockSupport.unpark(Thread t),则该线程才会恢复运行状态
park不同于Thread.yield(),yield只是告诉操作系统可以先让其他线程运行,但自己依然是可运行状态,而park会放弃调度资格,使线程进入WAITING状态
park是响应中断的
72.显示条件(Condition)
1.锁用于解决竞态条件问题,条件是线程间的协作机制。显式锁与synchronzied相对应,而显式条件与wait/notify相对应。
wait/notify与synchronized配合使用,显式条件与显式锁配合使用。
2.Condition表示条件变量,是一个接口:
public interface Condition {
void await() throws InterruptedException;//类似wait()
void awaitUninterruptibly();//不响应中断,如果等待过程中发生了中断,中断标志位会被设置
long awaitNanos(long nanosTimeout) throws InterruptedException;//响应中断,如果发生了中断,中断标志位会被清空
boolean await(long time, TimeUnit unit) throws InterruptedException;//响应中断,如果发生了中断,中断标志位会被清空
boolean awaitUntil(Date deadline) throws InterruptedException;//响应中断,这是绝对时间,如果发生了中断,中断标志位会被清空
void signal();//类似notify()
void signalAll();//类似notifyAll()
}
3.一般使用:
public class WaitThread extends Thread {
private volatile boolean fire = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
try {
while (!fire) {
condition.await();
}
} finally {
lock.unlock();
}
System.out.println("fired");
} catch (InterruptedException e) {
Thread.interrupted();
}
}
public void fire() {
lock.lock();
try {
this.fire = true;
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
WaitThread waitThread = new WaitThread();
waitThread.start();
Thread.sleep(1000);
System.out.println("fire");
waitThread.fire();
}
}
73.并发容器 - 写时拷贝的List和Set
1. CopyOnWriteArrayList,1.8前,CopyOnWriteArrayList的迭代器不支持修改操作,也不支持一些依赖迭代器修改方法的操作,比如Collections的sort方法
2.CopyOnWriteArrayList直接支持两个原子方法:
public boolean addIfAbsent(E e) //不存在才添加,如果添加了,返回true,否则返回false
public int addAllAbsent(Collection<? extends E> c) //批量添加c中的非重复元素,不存在才添加,返回实际添加的个数
3. 内部实现是volitile数组+ReentrantLock。每次修改都创建一个新数组,然后复制所有内容。读不需要锁,可以并行,读和写也可以并行,但多个线程不能同时写
4.CopyOnWriteArrayList不适用于数组很大,且修改频繁的场景。它是以优化读操作为目标的,读不需要同步,性能很高,但在优化读的同时就牺牲了写的性能(读远多于写、集合不太大的场合)
5.保证线程安全的思路:
a.加锁
b.CAS
c.写时拷贝
77.异步任务执行服务
1.Runnable:无返回值、不会抛异常
Callable:有返回值、会抛异常
2. Executor:执行一个Runnable,接口没有限定任务如何执行,可能是创建一个新线程,可能是复用线程池中的某个线程,也可能是在调用者线程中执行
public interface Executor {
void execute(Runnable command);
}
3. ExecutorService:
public interface ExecutorService extends Executor {
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
void shutdown();//不再接受新任务,但已提交的任务会继续执行(未开始的任务也会)
List<Runnable> shutdownNow();//不接受新任务,已提交但未执行的任务会被终止,对于正在执行的任务,调用线程的interrupt方法,返回已提交但尚未执行的任务列表
boolean isShutdown();
boolean isTerminated();
//shutdown和shutdownNow不会阻塞等待,返回后不代表所有任务已结束,但isShutdown返回true
//awaitTermination等待所有任务结束,可限定等待时间,如果超时前所有任务都结束,即isTerminated方法返回true,则返回true,否则返回false。
boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;
//等待所有任务完成,返回的Future列表(每个Future的isDone是true)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)throws InterruptedException;
//指定等待时间,如果超时后有的任务没完成,就会被取消
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException;
//只要有一个任务在限时内成功返回了,它就会返回该任务的结果,其他任务会被取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException;
//如果没有任务能在限时内成功返回,抛出TimeoutException,如果限时内所有任务都结束了,但都发生了异常,抛出ExecutionException
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
4.Future:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);//取消异步任务。
1.已完成、或已取消、或不能取消返回false,否则返回true
2.如果任务还未开始,则不再运行
3.如果任务已经在运行,则不一定能取消(mayInterruptIfRunning表示,如果任务正在执行,是否调用interrupt方法(不一定会中断线程))
boolean isCancelled();//是否取消。只要cancel方法返回了true,随后的isCancelled方法都会返回true,即使执行任务的线程还未真正结束
boolean isDone();//是否完成。任务正常结束、可能抛出异常、也可能任务被取消
V get() throws InterruptedException, ExecutionException;//阻塞等待直到有结果
V get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException;//等待指定时间,超时会抛出TimeoutException
get的结果:
1.正常完成,get方法会返回其执行结果,如果任务是Runnable且没有提供结果,返回null
2.任务执行抛出了异常,get方法会将异常包装为ExecutionException重新抛出,通过异常的getCause方法可以获取原异常
3.任务被取消了,get方法会抛出异常CancellationException
4.如果调用get方法的线程被中断了,get方法会抛出InterruptedException
}
5.内部使用FutureTask
78.线程池
1. ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
a.有新任务到来的时候,如果当前线程个数小于corePoolSiz,就会创建一个新线程来执行该任务(即使其他核心线程是空闲的也会创建)
b.如果线程个数大于等于corePoolSiz,不会立即创建新线程,先进队列workQueue,如果队列满了或其他原因不能立即入队,
它就不会排队,而是检查线程个数是否达到了maximumPoolSize,如果没有,就会继续创建线程,直到线程数达到maximumPoolSize
空闲的核心线程会从队列里面取任务执行
c. keepAliveTime是非核心线程能空闲等待的时间,超时就被回收
d.ThreadPoolExecutor要求的队列类型是阻塞队列BlockingQueue
-LinkedBlockingQueue:基于链表的阻塞队列,可以指定最大长度,但默认是无界的
-ArrayBlockingQueue:基于数组的有界阻塞队列
-PriorityBlockingQueue:基于堆的无界阻塞优先级队列
-SynchronousQueue:没有实际存储空间的同步阻塞队列(当尝试排队时,只有正好有空闲线程在等待接受任务时,才会入队成功,否则,总是会创建新线程,直到达到maximumPoolSize)
e.任务拒绝策略
ThreadPoolExecutor.AbortPolicy:这就是默认的方式,抛出异常
ThreadPoolExecutor.DiscardPolicy:静默处理,忽略新任务,不抛异常,也不执行
ThreadPoolExecutor.DiscardOldestPolicy:将等待时间最长的任务扔掉,然后自己排队
ThreadPoolExecutor.CallerRunsPolicy:在任务提交者线程中执行任务,而不是交给线程池中的线程执行
f.如果需要自定义一些线程的属性,比如名称,可以实现自定义的ThreadFactory
g.核心线程不会预先创建(prestartAllCoreThreads()会预先创建),只有当有任务时才会创建
核心线程不会因为空闲而被终止,keepAliveTime参数不适用于它(allowCoreThreadTimeOut,如果参数为true,则keepAliveTime参数也适用于核心线程)
h.内置创建线程池:
public static ExecutorService newSingleThreadExecutor() {
//所有任务被顺序执行,无界队列LinkedBlockingQueue,线程创建后不会超时终止,如果排队任务过多,可能会消耗非常大的内存
return new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
//固定数目的n个线程
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
//新任务到来,如果有空闲线程在等待任务,则线程接受任务,否则创建新线程,创建的总线程个数不受限制,对空闲线程,60秒内没有新任务,就终止
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
i.线程池死锁:任务A,在它的执行过程中,线程池提交了一个任务B,但需要等待任务B结束,此时如果B在等待队列,就会死锁
解决方法:1.使用newCachedThreadPool(让创建线程不再受限)
2.使用SynchronousQueue。
对于普通队列,入队只是把任务放到了队列中;
对于SynchronousQueue来说,入队成功就意味着已有线程接受处理,如果入队失败,可以创建更多线程直到maximumPoolSize,如果达到了maximumPoolSize,会触发拒绝机制
79.方便的CompletionService
1. 实现类ExecutorCompletionService,主线程提交多个异步任务,然后希望有任务完成就处理结果,并且按任务完成顺序逐个处理
80.定时任务的那些坑
1.固定延时:基于上次任务的"实际"执行时间来算的,如果由于某种原因,上次任务延时了,则本次任务也会延时
2.固定频率会尽量补够运行次数(任务被延迟后,可能会立即执行多次,将次数补够)
3.一个Timer对象只有一个Timer线程,上一个任务的执行完毕后才能够执行下一个任务
4.在执行任何一个任务的run方法时,一旦run抛出异常,Timer线程就会退出,从而所有定时任务都会被取消
5. ScheduledExecutorService:任务队列是一个无界的优先级队列,所以最大线程数对它没有作用,即使corePoolSize设为0,它会至少运行一个线程(总是会保留至少一个线程以监控定时任务啊),对于以上四点ScheduledExecutorService会没有这些坑
81.并发同步协作工具
1、读写锁ReentrantReadWriteLock:只有"读-读"操作是可以并行的,"读-写"和"写-写"都不可以(在读多写少的场景中使用)
public class MyCache {
private Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock readLock = readWriteLock.readLock();
private Lock writeLock = readWriteLock.writeLock();
public Object get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public Object put(String key, Object value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
public void clear() {
writeLock.lock();
try {
map.clear();
} finally {
writeLock.unlock();
}
}
}
2、信号量Semaphore:限制对资源的并发访问数eg:限制并发访问的用户数不超过100
public class AccessControlService {
public static class ConcurrentLimitException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
private static final int MAX_PERMITS = 100;
private Semaphore permits = new Semaphore(MAX_PERMITS, true);
public boolean login(String name, String password) {
if (!permits.tryAcquire()) {//尝试获取许可
// 同时登录用户数超过限制
throw new ConcurrentLimitException();
}
// ..其他验证
return true;
}
public void logout(String name) {
permits.release();//释放许可
}
}
如果我们将permits的值设为1,它与一般的锁是不同的:一般锁只能由持有锁的线程释放,而Semaphore表示的只是一个许可数,任意线程都可以调用其release方法
3、倒计时门栓CountDownLatch
a.同时开始:
public class RacerWithCountDownLatch {
static class Racer extends Thread {
CountDownLatch latch;
public Racer(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
this.latch.await();
System.out.println(getName()
+ " start run "+System.currentTimeMillis());
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) throws InterruptedException {
int num = 10;
CountDownLatch latch = new CountDownLatch(1);
Thread[] racers = new Thread[num];
for (int i = 0; i < num; i++) {
racers[i] = new Racer(latch);
racers[i].start();
}
Thread.sleep(1000);
latch.countDown();
}
}
b.主从协作(主线程依赖工作线程完成)
public class MasterWorkerDemo {
static class Worker extends Thread {
CountDownLatch latch;
public Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
// simulate working on task
Thread.sleep((int) (Math.random() * 1000));
// simulate exception
if (Math.random() < 0.02) {
throw new RuntimeException("bad luck");
}
} catch (InterruptedException e) {
} finally {
this.latch.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
int workerNum = 100;
CountDownLatch latch = new CountDownLatch(workerNum);
Worker[] workers = new Worker[workerNum];
for (int i = 0; i < workerNum; i++) {
workers[i] = new Worker(latch);
workers[i].start();
}
latch.await();
System.out.println("collect worker results");
}
}
4、循环栅栏CyclicBarrier :所有线程在到达该栅栏后都需要等待其他线程,等所有线程都到达后再一起通过
public class CyclicBarrierDemo {
static class Tourist extends Thread {
CyclicBarrier barrier;
public Tourist(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
// 模拟先各自独立运行
Thread.sleep((int) (Math.random() * 1000));
// 集合点A
barrier.await();
System.out.println(this.getName() + " arrived A "
+ System.currentTimeMillis());
// 集合后模拟再各自独立运行
Thread.sleep((int) (Math.random() * 1000));
// 集合点B
barrier.await();
System.out.println(this.getName() + " arrived B "
+ System.currentTimeMillis());
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
}
}
}
public static void main(String[] args) {
int num = 3;
Tourist[] threads = new Tourist[num];
CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() {
@Override
public void run() {
System.out.println("all arrived " + System.currentTimeMillis()
+ " executed by " + Thread.currentThread().getName());
}
});
for (int i = 0; i < num; i++) {
threads[i] = new Tourist(barrier);
threads[i].start();
}
}
}
82.理解ThreadLocal
1.每个线程对与ThreadLocal变量都有一个拷贝
public class ThreadLocalBasic {
static ThreadLocal<Integer> local = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Thread child = new Thread() {
@Override
public void run() {
System.out.println("child thread initial: " + local.get());
local.set(200);
System.out.println("child thread final: " + local.get());
}
};
local.set(100);
child.start();
child.join();
System.out.println("main thread final: " + local.get());
}
}
输出:
child thread initial: null
child thread final: 200
main thread final: 100
这说明,main线程对local变量的设置对child线程不起作用,child线程对local变量的改变也不会影响main线程,
它们访问的虽然是同一个变量local,但每个线程都有自己的独立的值,这就是线程本地变量的含义
2.initialValue()、remove()
public class ThreadLocalInit {
static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 100;
}
};
public static void main(String[] args) {
System.out.println(local.get());//输出100
local.set(200);
local.remove();//remove之后再调用get会重新调用initialValue
System.out.println(local.get());//输出100
}
}
3.使用场景:
a.DateFormat/SimpleDateFormat:(ThreadLocal对象一般都定义为static,以便于引用)
public class ThreadLocalDateFormat {
static ThreadLocal<DateFormat> sdf = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String date2String(Date date) {
return sdf.get().format(date);
}
public static Date string2Date(String str) throws ParseException {
return sdf.get().parse(str);
}
}
b. ThreadLocalRandom:Random是线程安全的,但如果并发访问竞争激烈的话,性能会下降,所以Java并发包提供了类ThreadLocalRandom
c.上下文信息:
public class RequestContext {
public static class Request { //...
};
private static ThreadLocal<String> localUserId = new ThreadLocal<>();
private static ThreadLocal<Request> localRequest = new ThreadLocal<>();
public static String getCurrentUserId() {
return localUserId.get();
}
public static void setCurrentUserId(String userId) {
localUserId.set(userId);
}
public static Request getCurrentRequest() {
return localRequest.get();
}
public static void setCurrentRequest(Request request) {
localRequest.set(request);
}
}
4.原理:每个线程都有一个Map,类型为ThreadLocalMap,对于每个ThreadLocal对象,调用其get/set实际上就是以ThreadLocal对象为键读写当前线程的Map
5.线程池与ThreadLocal:由于线程池会复用线程,在没有处理的情况下,一个线程执行完一个任务如果修改了ThreadLocal对象的值,会被带到下一个任务去:
public class ThreadPoolProblem {
static ThreadLocal<AtomicInteger> sequencer = new ThreadLocal<AtomicInteger>() {
@Override
protected AtomicInteger initialValue() {
return new AtomicInteger(0);
}
};
static class Task implements Runnable {
@Override
public void run() {
AtomicInteger s = sequencer.get();
int initial = s.getAndIncrement();
// 期望初始为0
System.out.println(initial);
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);//两个线程工作
executor.execute(new Task());
executor.execute(new Task());
executor.execute(new Task());
executor.shutdown();
}
}
输出:
0
0
1//线程池中的线程在执行完一个任务,执行下一个任务时,其中的ThreadLocal对象并不会被清空,所以输出1
解决方法:
1.第一次使用ThreadLocal对象时,总是先调用set设置初始值,或者如果ThreaLocal重写了initialValue方法,先调用remove
static class Task implements Runnable {
@Override
public void run() {
sequencer.set(new AtomicInteger(0));
//或者 sequencer.remove();
AtomicInteger s = sequencer.get();
//...
}
}
2.使用完ThreadLocal对象后,总是调用其remove方法
static class Task implements Runnable {
@Override
public void run() {
try{
AtomicInteger s = sequencer.get();
int initial = s.getAndIncrement();
// 期望初始为0
System.out.println(initial);
}finally{
sequencer.remove();
}
}
}
3.使用自定义的线程池
static class MyThreadPool extends ThreadPoolExecutor {
public MyThreadPool(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override//每个线程在执行前会被调用
protected void beforeExecute(Thread t, Runnable r) {
try {
//使用反射清空所有ThreadLocal
Field f = t.getClass().getDeclaredField("threadLocals");
f.setAccessible(true);
f.set(t, null);
} catch (Exception e) {
e.printStackTrace();
}
super.beforeExecute(t, r);
}
}
83.并发总结
1.线程安全的机制
(1) synchronized(隐式锁、悲观锁):
既可以解决竞态条件问题,也可以解决内存可见性问题
需要注意,它不能尝试获取锁,也不响应中断,还可能会死锁。不过,相比显式锁,synchronized简单易用,JVM也可以不断优化它的实现,应该被优先使用
(2)使用显式锁(乐观锁)
可以实现synchronzied同样的功能,但需要程序员自己创建锁,调用锁相关的接口,主要接口是Lock,主要实现类是ReentrantLock
支持以非阻塞方式获取锁、可以响应中断、可以限时、可以指定公平性、可以解决死锁问题
在读多写少、读操作可以完全并行的场景中,可以使用读写锁以提高并发度,读写锁的接口是ReadWriteLock,实现类是ReentrantReadWriteLock
(3)使用volatile
synchronized和显式锁都是锁,使用锁可以实现安全,但使用锁是有成本的,获取不到锁的线程还需要等待,会有线程的上下文切换开销等
保证安全不一定需要锁。如果共享的对象只有一个,操作也只是get/set操作,set不依赖于之前的值,那就不存在竞态条件问题,只有内存可见性问题,这时使用volatile就可以了
(4)使用原子变量和CAS
使用volatile,set的新值不能依赖于旧值,但很多时候,set的新值与原来的值有关,这时,也不一定需要锁,如果需要同步的代码比较简单,可以考虑原子变量
原子变量的基础是CAS,比较并设置,非阻塞式的
(5)写时复制(CopyOnWriteArrayList)
之所以会有线程安全的问题,是因为多个线程并发读写同一个对象,如果每个线程读写的对象都是不同的,或者,如果共享访问的对象是只读的,不能修改,那也就不存在线程安全问题了
原理:写时复制就是将共享访问的对象变为只读的,写的时候,再使用锁,保证只有一个线程写,写的线程不是直接修改原对象,而是新创建一个对象,对该对象修改完毕后,再原子性地修改共享访问的变量,让它指向新的对象
(6)使用ThreadLocal
每个线程,对同一个变量,都有自己的独有拷贝,每个线程实际访问的对象都是自己的
2.线程的协作机制
(1)wait/notify
wait/notify与synchronized配合一起使用
每个对象都有一把锁和两个等待队列:
a.锁等待队列:存放等待获取锁的线程
b.条件等待队列:存放等待条件的线程
wait()将自己加入条件等待队列,notify()从条件等待队列上移除一个线程并唤醒,notifyAll移除所有线程并唤醒
注意:
wait/notify方法只能在synchronized代码块内被调用
调用wait时,线程会释放对象锁,被notify/notifyAll唤醒后,要重新竞争对象锁,获取到锁后才会从wait调用中返回,返回后,不代表其等待的条件就一定成立了,需要重新检查其等待的条件
synchronized (obj) {
while (条件不成立)
obj.wait();
... // 执行条件满足后的操作
}
wait/notify与一个共享的条件变量有关,这个条件变量是程序自己维护的,当条件不成立时,线程调用wait进入条件等待队列,另一个线程修改了条件变量后调用notify,调用wait的线程唤醒后需要重新检查条件变量
(2)显式条件
显式条件与显式锁配合使用
与wait/notify相比,可以支持多个条件队列,代码更为易读,效率更高,使用时注意不要将signal/signalAll误写为notify/notifyAll
(3)线程的中断
Java中取消/关闭一个线程的方式是中断,中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出,线程在不同状态和IO操作时对中断有不同的反应,
作为线程的实现者,应该提供明确的取消/关闭方法,并用文档清楚描述其行为,作为线程的调用者,应该使用其取消/关闭方法,而不是贸然调用interrupt
(4)协作工具类
信号量类Semaphore用于限制对资源的并发访问数
倒计时门栓CountDownLatch主要用于不同角色线程间的同步(让多个线程同时开始、主线程等待多个线程结束)
循环栅栏CyclicBarrier用于同一角色线程间的协调一致,所有线程在到达栅栏后都需要等待其他线程,等所有线程都到达后再一起通过,它是循环的,可以用作重复的同步
(5)阻塞队列
阻塞队列封装了锁和条件,不需要考虑同步和协作问题
(6)Future/FutureTask
异步执行任务(ExecutorService)提交任务后马上得到一个结果,但这个结果不是最终结果,而是一个Future,Future是一个接口,主要实现类是FutureTask
Future封装了主线程和执行线程关于执行状态和结果的同步,对于主线程而言,它只需要通过Future就可以查询异步任务的状态、获取最终结果、取消任务等,不需要再考虑同步和协作问题
3.容器类
(1)同步容器:
Collections类中有一些静态方法,可以基于普通容器返回线程安全的同步容器
给所有容器方法都加上synchronized来实现安全,性能比较低
(2)并发容器:
线程安全、并发度更高、性能更高、迭代不会抛出ConcurrentModificationException、很多容器以原子方式支持一些复合操作
a.写时拷贝的List和Set:采用了写时拷贝,适用于读远多于写,集合不太大的场合
b.ConcurrentHashMap:分段锁和其他技术实现了高并发,读操作完全并行,写操作支持一定程度的并行,以原子方式支持一些复合操作,迭代不用加锁,不会抛出ConcurrentModificationException
c.基于SkipList的Map和Set:TreeMap/TreeSet对应的并发版本是ConcurrentSkipListMap和ConcurrentSkipListSet,没有使用锁,所有操作都是无阻塞的,所有操作都可以并行,包括写
d.各种队列:各种阻塞队列主要用于协作,非阻塞队列适用于多个线程并发使用一个队列的场合(ConcurrentLinkedQueue和ConcurrentLinkedDeque、无界,这两个类最基础的实现原理是循环CAS,没有使用锁)
4.任务执行服务
Runnable和Callable:表示要执行的异步任务
Executor和ExecutorService:表示执行服务
Future:表示异步任务的结果
线程池ThreadPoolExecutor实现了生产者/消费者模式,工作者线程就是消费者,任务提交者就是生产者,线程池自己维护任务队列。当我们碰到类似生产者/消费者问题时,应该优先考虑直接使用线程池
84.反射
1.每个已加载的类在内存都有一份类信息(java.lang.Class),每个对象都有指向它所属类信息的引用
2.获取Class对象:
(1)实例对象.getClass()
(2)类名.class、基本数据类型.class 、void.class:
Class<Date> cls = Date.class;
Class<Integer> intCls = int.class;
Class<Byte> byteCls = byte.class;
Class<Void> voidCls = void.class;
对于数组,每种类型都有对应数组类型的Class对象,每个维度都有一个,即一维数组有一个,二维数组有一个不同的:
String[] strArr = new String[10];
int[][] twoDimArr = new int[3][2];
int[] oneDimArr = new int[10];
Class<? extends String[]> strArrCls = strArr.getClass();
Class<? extends int[][]> twoDimArrCls = twoDimArr.getClass();
Class<? extends int[]> oneDimArrCls = oneDimArr.getClass();
(3)Class.forName
3.Class信息
getName():返回Java内部使用的真正的名字 ,前缀[表示数组,有几个[表示是几维数组,数组的类型用一个字符表示,I表示int,L表示类或接口,
其他: boolean(Z), byte(B), char(C), double(D), float(F), long(J), short(S),对于引用类型的数组,注意最后有一个分号";"
getSimpleName():返回不带包信息
getCanonicalName():返回的名字更为友好
image.png
public Field[] getFields() :返回所有的public字段,包括其父类的,如果没有字段,返回空数组
public Field[] getDeclaredFields():返回本类声明的所有字段,包括非public的,但不包括父类的
Field:
isAccessible():当前程序是否有该字段的访问权限
setAccessible(boolean flag):flag设为true表示忽略Java的访问检查机制,以允许读写非public的字段
public int getModifiers():
Field f = Student.class.getField("MAX_NAME_LEN");
int mod = f.getModifiers();
System.out.println(Modifier.toString(mod));
System.out.println("isPublic: " + Modifier.isPublic(mod));
System.out.println("isStatic: " + Modifier.isStatic(mod));
System.out.println("isFinal: " + Modifier.isFinal(mod));
System.out.println("isVolatile: " + Modifier.isVolatile(mod));
Method:
public Object invoke(Object obj, Object... args):如果是静态方法,obj可以传null
4.类型检查和转换:
Class.isInstance(Obejct) //类似instanceof 关键字
Class.cast(Obejct) //强转
public static <T> T toType(Object obj, Class<T> cls){
return cls.cast(obj);
}
isInstance/cast描述的都是对象和类之间的关系,Class还有一个方法,可以判断Class之间的关系:
public native boolean isAssignableFrom(Class<?> cls)//检查参数类型cls能否赋给当前Class类型的变量
Object.class.isAssignableFrom(String.class)//true
String.class.isAssignableFrom(String.class)//true
List.class.isAssignableFrom(ArrayList.class)//true
5.类的加载:
public static Class<?> forName(String className)//相当于Class.forName(className, true, currentLoader)
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)//initialize表示加载后,是否执行类的初始化代码(如static语句块)
这里className与Class.getName的返回值是一致的,比如,对于String数组:
String name = "[Ljava.lang.String;";
Class cls = Class.forName(name);
System.out.println(cls == String[].class);/true
需要注意的是,基本类型不支持forName方法
getComponentType()返回数组元素的类型
85.注解
1.@Target:表示注解的目标,取值如下:(不声明默认为适用于所有类型)
TYPE:表示类、接口(包括注解),或者枚举声明
FIELD:字段,包括枚举常量
METHOD:方法
PARAMETER:方法中的参数
CONSTRUCTOR:构造方法
LOCAL_VARIABLE:本地变量(局部变量)
ANNOTATION_TYPE:注解类型
PACKAGE:包
2.@Retention:表示注解信息保留到什么时候,取值如下:(默认为CLASS)
SOURCE:只在源代码中保留,编译器将代码编译为字节码文件后就会丢掉
CLASS:保留到字节码文件中,但Java虚拟机将class文件加载到内存时不一定会在内存中保留
RUNTIME:一直保留到运行时
3.定义参数
对于public @interface SuppressWarnings {
String[] value();
}
使用:@SuppressWarnings(value={"deprecation","unused"})
当只有一个参数,且名称为value时,提供参数值时可以省略"value=",即:
@SuppressWarnings({"deprecation","unused"})
参数定义时可以使用default指定一个默认值(注意:String的默认值不能为null)
4.@Inherited
注解不能继承,但可以使用@Inherited来实现:
public class InheritDemo {
@Inherited
@Retention(RetentionPolicy.RUNTIME)
static @interface Test {
}
@Test
static class Base {
}
static class Child extends Base {
}
public static void main(String[] args) {
System.out.println(Child.class.isAnnotationPresent(Test.class));//输出true
}
}
5.注解与反射
public Annotation[] getAnnotations()//获取所有的注解
public Annotation[] getDeclaredAnnotations() //获取所有本元素上直接声明的注解,忽略inherited来的
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)//获取指定类型的注解,没有返回null
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)//判断是否有指定类型的注解
86.类加载机制
1.类加载的基本机制和过程
a.类加载器:(输入是完全限定的类名,输出是Class对象)
(1)启动类加载器(Bootstrap ClassLoader):负责加载Java的基础类,主要是<JAVA_HOME>/lib/rt.jar
(2)扩展类加载器(Extension ClassLoader):负责加载Java的一些扩展类,一般是<JAVA_HOME>/lib/ext目录中的jar包
(3)应用程序类加载器(Application ClassLoader):实现类AppClassLoader,它负责加载应用程序的类(也被称为系统类加载器)
b.双亲委派:Application ClassLoader的父亲是Extension ClassLoader,Extension的父亲是Bootstrap ClassLoader,注意不是父子继承关系,而是父子委派关系,
子ClassLoader有一个变量parent指向父ClassLoader,在子ClassLoader加载类时,一般会首先通过父ClassLoader加载
1.判断是否已经加载过了,加载过了,直接返回Class对象,一个类只会被一个ClassLoader加载一次。
2.如果没有被加载,先让父ClassLoader去加载,如果加载成功,返回得到的Class对象。
3.在父ClassLoader没有加载成功的前提下,自己尝试加载类
2.ClassLoader.getSystemClassLoader():获取默认的系统类加载器
3.ClassLoader vs Class.forName:
public static Class<?> forName(String className)//使用系统类加载器加载
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)//指定ClassLoader,参数initialize表示,加载后,是否执行类的初始化代码(如static语句块),没有指定默认为true
ClassLoader的loadClass方法与forName方法都可以加载类,但是ClassLoader的loadClass不会执行类的初始化代码
4.自定义ClassLoader
image.png
网友评论