作者: neo已经被使用 | 来源:发表于2020-11-19 21:32 被阅读0次

    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. 显示锁
    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

    相关文章

      网友评论

          本文标题:

          本文链接:https://www.haomeiwen.com/subject/kdtpjxtx.html