亚马逊站外推广网站,163企业邮箱费用,关键词排名快照优化,电销外包公司有哪些1、线程池的理解
线程池就是要达到线程复用的目的#xff0c;对线程的创建和消耗会涉及到cpu上下文切换、内存分配等#xff0c;会带来性能的消耗#xff1b;提高响应速度#xff0c;任务到达时#xff0c;任务可以不需要等到线程创建就能立即执行#xff1b;提高线程的…1、线程池的理解
线程池就是要达到线程复用的目的对线程的创建和消耗会涉及到cpu上下文切换、内存分配等会带来性能的消耗提高响应速度任务到达时任务可以不需要等到线程创建就能立即执行提高线程的可管理性统一的分配、调优和监控。
public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR new ThreadPoolExecutor(APS * 2, 核心线程数APS * 4, 最大线程数KEEP_ALIVE_TIME, 非核心线程空闲TimeUnit.SECONDS, 单位new LinkedBlockingDeque(256), 阻塞队列new ThreadFactoryBuilder().build(), 线程工厂new ThreadPoolExecutor.CallerRunsPolicy() 阻塞策略-线程上限并且满队列由主线程执行线程一定会执行但高并发下会影响效率
);
CPU密集型计算密集型系统的I/O读写效率高于CPU效率大部分的情况CPU有许多运算需要处理使用率很高。但I/O执行很快。
I/O密集型系统的CPU性能比磁盘读写效能要高很多大多数情况是CPU在等I/O的读写操作此时CPU的使用率并不高
计算公式分为:
CPU密集型 线程数 CPU核数1 (现代CPU支持超线程)
IO密集型 线程数 线程等待时间线程CPU时间/线程CPU时间 * CPU数目 2、线程回收
线程池的队列满了的情况下线程池会增加非核心线程。当任务处理完成后非核心线程会被回收一般不设置核心线程的回收因为当没有任务时是处于阻塞状态不会占用cpu资源当有任务时减少创建线程带来的cpu上下文切换的损耗 3、如何不进入队列直接启用最大线程数
SynchronousQueue队列 4、实现多线程的方法 继承Thread重写run 实现Runnable接口并重写run如果已经存在继承关系的类实现线程只能实现Runnable 实现Callable重写call通过FutureTask包装器来创建Thread线程get方法获取带有返回值的线程 5、Thread和Runnable区别
Thread是一个类类只能单一继承Runnable是接口接口支持多继承
Runnable是线程的顶级接口Thread也是实现了Runnable的接口Runnable相当于一个任务而Thread才是真正处理的线程达到一个松耦合的设计目的。 6、常用的线程池 newSingleThreadExecutor创建一个单线程的线程池保证任务的执行顺序按照提交顺序适用于需要顺序执行的场景 newFixedThreadExecutor创建固定大小的线程池能控制最大并发数防止过度竞争导致资源耗尽 newCachedThreadExecutor创建缓存的线程池当有新任务提交时如果当前有空闲线程可用就会被复用否则会创建新的线程。当线程在60秒内未被使用时将被终止并从缓存中移除。适合执行大量的短时间任务。如果线程池的当前规模超过了处理需求则会进行回收 newScheduledThreadExecuto支持定时以及周期性执行任务 WorkStealingPooljava8新加入的基于 ForkJoinPool 实现的线程池利用工作窃取算法实现任务的高效执行。适用于需要处理大量并行任务的场景通常在异步编程中使用 7、如何知道线程已经执行完
在线程池内部线程池调度线程来执行时是用run方法ran方法结束并且等待run方法返回后再去统计任务的完成数量。
在线程池外部 submit方法线程执行完返回一个Future的返回值get获取结果没执行完会一直阻塞 countDownLatch计数器为0时结束 isTermianted()方法循环判断返回结果判断运行状态Termianted时所有任务执行完 8、线程调用两次start会出现什么
IllegalThreadStateException 异常 是 java.lang 包中的一个异常类它表示线程处于不正确的状态下尝试执行某个操作 9、如何停止一个正在运行的线程
kill命令强制终止 退出标志run完成后线程终止 stop强行终止是不安全的可能线程任务还没有结束导致结果不正确的问题 使用标志位 这是一种常见的策略通过设置一个布尔型的标志位来指示线程何时停止。运行的线程应该经常检查这个标志位 interrupt方法中断线程配合interrupted方法并不是强制中断是告诉正在运行的线程可以停止了是否要中断取决于正在运行的线程能够保证线程运行结果的安全性。m1.interrupt(); // 阻塞时退出阻塞状态阻塞方法(比如sleep(),wait(),join())会抛出 InterruptedException 如果您的线程是由 ExecutorService 创建的调用 future.cancel(true) 方法时如果线程正在运行它会被中断 10、notify和notifyAll的区别 notify随机唤醒一个等待的线程进入锁等待池 notifyAll唤醒所有等待的线程进入等待池竞争获取锁 使用notifyAll可以确保所有等待的线程被唤醒降低死锁或静态条件的风险如果确定有一个线程需要被唤醒可以使用notify提高效率 11、sleep和wait区别 sleep属于Thread类程序暂停执行指定的时间让出cpu但不释放锁当时间到自动回复运行状态 wait属于Object类线程会放弃对象锁进入等待状态使用notify后唤醒。 sleep就是让线程休眠一段时间而wait是线程中通信让一个线程等待其他的线程的通知 12、Thread类中的yield方法
暂停当前正在执行的线程对象让其他相同优先级的线程执行。是一个静态方法且只保证当前线程放弃cpu占用而不能保证其他线程一定占用cpu执行yield的线程可能在进入到暂停状态后马上又执行。 13、线程状态Blocked和Waiiting的区别
Blocked是阻塞状态比如竞争同步锁时没有获取锁的执行会被阻塞等待是被动的
Waiiting是线程的等待需要等待线程的特点操作才会被唤醒可以使用wait()、join()方法notify()唤醒是主动的 14、start和run的区别 start() 方法实际上是启动一个新线程并使新线程执行 run() 方法。新的线程会为其分配资源和调度下独立并行地执行其任务。 直接调用 run() 方法不会启动新线程而只会在当前线程中执行 run() 方法 15、interrupted和isInterrupted方法区别
所有者 interrupted() 是 Thread 类的静态方法它检查当前线程是否被中断。而 isInterrupted() 是 Thread 类的实例方法用于检查实例化的具体线程是否被中断。中断状态 interrupted() 方法调用后会对线程的中断状态进行复位即将中断标志设置为 false无论它之前是什么状态。而 isInterrupted() 方法则不会影响线程的中断状态。 16、为什么wait、notify、notifyAll不在Thread类里 JAVA提供的锁是对象级的而不是线程级的每个对象都有锁通过线程获得。如果线程需要等待某些锁调用对象中的wait方法如果wait在Thread中线程正在等待的是哪个锁就不确定了。 Java 的每一个对象都可以作为一个锁线程通过 synchronized 关键字来获取对象的锁进行同步操作。wait(), notify(), 和 notifyAll() 方法就是用于在已经获取锁的线程中进行交互和通讯的。 wait、notify、notifyAll定义在Object中因为锁属于对象。 17、为什么wait和notify要在同步块中调用 wait让线程进入到阻塞状态notify唤醒线程必然是成对出现的实现多线程之间的通信 由于这些方法是控制锁的行为因此它们必须在 synchronized 代码块中使用。 调用wait和notify之前当前线程已经获得了该对象的锁拥有对象锁通常需要在同步块内部执行后续的代码这样避免竞态条件或者其他的并发问题抛出IllegalMonitorStateException 。 wait通常是希望线程再某个特殊的状态被设置后再继续执行调用notfy希望线程告诉其他等待的线程特殊的状态已经被设置这个状态作为线程间通信的通道 18、解决死锁
多个线程执行过程中争夺同一个共享资源造成的相互等待的过程
死锁的四个条件
互斥条件共享资源只能被一个线程占用
请求和保持条件线程1获取到共享资源X等待共享Y的时候不释放X
不可抢占条件其他线程不能强行占用线程1的资源
循环等待条件线程1等待线程2占用的线程2等待线程1占用的
破坏其中一个即可互斥是不能破坏的。 19、线程安全
多线程访问通同一代码不会产生不确定的结果。具体表现三个方面 原子性一段程序只能由一个线程完整的执行完成不能存在多个线程干扰。CPU的上下文切换导致原子性的核心使用synchronized解决 可见性多线程下读和写发生在不同的线程可能存在一个线程对共享变量的更改对其他线程不是实时可见的 有序性指令重排序可能导致可见性的问题。使用volatile解决 20、有界阻塞队列和无界阻塞队列 阻塞队列如果队列为空消费者阻塞唤醒生产者。如果队列满生产者阻塞唤醒消费者 有界阻塞队列能够容纳的元素个数通常是有界的。ArrayBlockingQueue基于数组结构的阻塞队列数组的有长度限制为了达到循环生生产和消费目的用到了循环数组。 无界队列是没有固定大小的队列不是没有限制而是存储量很大比如LinkedBlockingQueue如果并发大情况下可以无限制添加导致内存溢出 21、ArrayBlockingQueue的理解
阻塞队列是在队列的基础上增加了两个附加条件队列为空的时候获取元素的线程会等待队列变为空队列满时存储元素的线程会等待队列可用。就比如生产者消费者模型。ArrayBlockingQueue是基于数组的阻塞队列队列元素是存储在一个数组结构里并且由于数组有长度限制用到了循环数组达到循环生产和循环消费的目的。内部就是两个指针都是从队首向队尾移动保证队列的先进先出原则利用指针和数组行成环状。底层使用的是ReentrantLock保证互斥性存取都是同一把锁操作的是同一个数组对象存取互相排斥 22、阻塞队列被异步消费是怎么保持顺序的
阻塞队列是先进先出的在阻塞队列中使用了condition条件维护两个等待队列一个是队列为空的时候存储被阻塞的消费者一个是队列满了的时候存储被阻塞的生产者。
对于消费过程有两种情况第一种是阻塞队列里已经包含很多任务启动多个消费者去消费时候每个消费者线程去阻塞队列获取任务的时候要先获取排他锁。第二种是多个消费者因为阻塞队列没有任务而阻塞线程是按照先进先出的顺序存储到condition等待队列中然后严格按照先进先出顺序唤醒。 23、保证多线程的顺序执行
使用join()方法 24、submit和execute区别 submit方法是 ExecutorService 接口中定义的方法它用于提交一个实现了 Callable 或 Runnable 接口的任务并返回一个 Future 对象用于跟踪任务的执行状态。 Callable是一个可以返回结果的任务它的 call 方法可以返回一个结果可以通过 Future 对象获取。 Runnable是一个无返回结果的任务submit 返回的 Future 对象将总是包含一个 null 结果。 execute方法是 Executor 接口中定义的方法它用于执行一个 Runnable 任务但它不返回任何结果也无法获取任务的执行状态 25、ThreadLocal理解
线程隔离机制多线程下对于共享变量访问的安全性。
一般多线程对共享变量的处理是对共享变量加锁保证只有一个线程对共享变量进行操作。
加锁会带来性能响ThreadLocal就是空间换时间每个线程里都有一个容器存储共享变量的副本然后每个线程只对自己的变量副本进行操作。 26、ThreadLocal会存在内存泄漏吗
每个线程都有一个成员变量ThreadLocalMap是在这个map中保存了一份数据副本key是引用(弱引用)value是副本。
弱引用只要JVM发现就会回收一旦回收key就为null导致这个内存永远无法被访问造成内存泄漏。
优化
ThreadLocal为了避免造成内存泄漏会找到并清理Entry里面的key为null的数据。
调用 set()方法时ThreadLocal 会进行采样清理、全量清理扩容时还会继续检查。
调用 get()方法时如果没有直接命中或者向后环形查找时也会进行清理。
调用 remove()时除了清理当前 Entry还会向后继续清理。
避免
每次使用ThreadLocal后主动调用remove移除
将 ThreadLocal 变量尽可能地定义成 static final避免频繁创建 ThreadLocal 实例。 27、volatile是什么 保证不同线程对变量进行操作时的可见性一个线程修改某个变量的值对其他来说都是立即可见的会强制将修改的值写入主存 禁止进行指令重排序可以保证部分有序性 造成重排序问题
指令的编写顺序和执行顺序不一致
如果加入了volatile就不会触发编译器优化同时在jvm里插入内存屏障指令避免重排序 造成可见性的问题
cpu高速缓存使用了三级缓存解决cpu运算效率和IO效率问题在使用volatile修饰共享变量jvm会自动增加一个lock指令添加总线锁或者缓存锁
总线锁锁定cpu前端总线同一时刻只能有一个线程去和内存通信
缓存锁对总线锁的优化总线锁导致cpu使用效率下降缓存锁只对cpu三级缓存中的目标数据加锁 28、volatile和synchronized区别 valatile本质告诉jvm当前变量的值是不确定的需要从主存中读取 synchronized是锁定当前变量只有当前线程可以访问其他线程阻塞 volatile仅能使用在变量实现变量的修改可见性 synchronized可以使用在变量、方法、类保证变量的修改可见性和原子性 volatile不会造成线程的阻塞 synchronized可能会阻塞 29、lock和synchronized的区别
synchronized是关键字可以通过两种方式控制锁的粒度一是修饰在方法上二是修饰在代码块上如果锁对象是静态对象或者类对象这个锁对象就是全局锁如果锁对象是普通实例对象锁范围取决于这个实例的生命周期。lock是接口提供lock和unlock方法在这两个方法之间的代码保证线程安全性提供了tryLock方法通过放回true/false告诉当前线程是否有其他线程正在使用锁。lock比synchronized的灵活性更高。lock提供了公平锁和非公平锁公平锁是指竞争锁资源时如果已经有其他线程症状排队等待锁释放那么当前竞争锁资源的线程无法插队非公平锁就是不管是否有线程等待锁都会尝试去竞争锁synchronized就是一种非公平锁。 30、可重入锁
可重入锁Reentrant Lock是一种能够被单个线程多次获取的锁也就是说如果当前线程已经持有了这个锁再次请求持有时请求就会成功。每一个锁关联一个线程持有者和计数器当计数器为0代表没有线程占用那么线程都可以去获取锁。当某一个线程请求成功JVM记下锁的持有线程其他线程再去获取锁时必须等待而持有锁的线程再次请求这个锁就可以再次拿到锁计数器同步增加线程退出同步代码块时递减。关于为何需要可重入锁这与我们在程序设计中经常使用的函数或方法的递归调用有关。一个很实际的例子是如果一个线程已经在一个synchronized方法内部那么它能再次调用这个对象的其他synchronized方法或者同个synchronized方法自己调用自己即递归。如果没有可重入锁的设计那时线程再次获取锁时因为这把锁已被自己持有就会造成该线程死锁。而可重入锁的设计则保证了这个线程可以再次获取到那把已经被自己持有的锁进而避免了死锁情况的发生。 31、对ReentrantLock理解
ReentrantLock是一种可重入排他锁
提供了阻塞竞争锁和非阻塞竞争锁分别是lock和trylock 锁的竞争通过互斥变量使用CAS机制实现 没有竞争到锁的线程使用了AbstractQueueSynchronizer队列同步器存储底层是先进先出的双向链表锁释放之后会从AQS队列中头部唤醒下一个等待锁的线程 设置公平和非公平主要是体现在竞争锁的时候是否需要判断AQS队列存在等待中的线程公平锁就是竞争资源的时候判断AQS同步队列中是否有等待的线程如果有就加入到队列的尾部非公平锁就是不管有没有等待都会尝试抢占锁抢不到再加入到锁队列。 可重入的概念在AQS有一个成员变量来保存当前获得锁的线程当同一个线程下次再来竞争锁的时候就不会走锁竞争的逻辑直接增加重入次数 32、ReentrantLock如何实现公平和非公平的的
公平竞争锁资源的线程按照顺序分配锁
非公平竞争锁资源的线程允许插队来抢占资源锁
内部使用AQS实现锁资源的竞争没有竞争到锁的线程加入到AQS同步队列这个队列是一个先进先出的双向链表实现的公平锁就是竞争资源的时候判断AQS同步队列中是否有等待的线程如果有就加入到队列的尾部非公平锁就是不管有没有等待都会尝试抢占锁抢不到再加入到锁队列。
ReentrantLock和synchronized默认都是非公平锁主要是考虑到性能方面的原因因为一个竞争锁的线程如果按照公平的策略去阻塞等待同时AQS再把等待队列里面的线程唤醒涉及到内核态的切换对性能应较大非公平锁提升了锁竞争的性能。 33、synchronized和ReentrantLock区别 都是阻塞式的同步进行线程阻塞和唤醒的代价是较高的 synchronized是java关键字原生语法层面的互斥需要jvm实现进行编译在同步块前后monitorenter和monitorexit两个字节码指令执行指令时获取锁1释放锁-1为0时释放。修饰方法时是ACC_SYNCHRONIZED标识是否是一个同步方法。 ReentrantLock是java.util.concurrent下的互斥锁需要lock()和unlock()以及try/finally完成。长期持有锁不释放时等待线程可以选择放弃等待。可以设置公平锁。 34、对synchronized的了解
jdk1.6之前是重量级锁解决多个线程间访问资源的同步性保证被修饰的方法或代码块在任意时刻只有一个线程执行。
原生语法层面的互斥需要jvm实现而操作系统实现线程切换需要在用户态和内核态直接的转化导致效率低JDK1\6以后引入了偏向锁和轻量级锁的机制。 35、偏向锁和轻量级锁 偏向锁把当前锁偏向于某个线程通过CAS修改偏向锁标记 轻量级锁自旋锁多次自旋重试竞争锁 synchronized尝试使用偏向锁去竞争锁如果能竞争到就是成功直接返回否则当前锁已经偏向了其他线程。需要将锁升级到轻量级锁竞争锁的线程根据自适应自旋次数去尝试抢占资源如果还没有竞争到锁升级到重量级锁没有竞争到就会被阻塞需要等待获得锁的线程来触发唤醒。 总的来说synchronized的升级思想就是 36、对synchronized的使用 修饰实例方法对当前对象实例加锁进入代码前要获取锁 修饰静态方法当前类加锁作用于类的所有对象实例。当一个类的静态方法使用 synchronized 关键字修饰时这个方法会获取该类的类级别锁也就是类对象的锁因此会影响所有使用该类的实例。同时如果一个线程在调用一个实例对象的非静态同步方法时另一个线程需要调用该实例对象所属类的静态同步方法两者之间不会产生互斥因为它们分别使用的是实例对象锁和类级别锁。 修饰代码块指定加锁对象对给定对象加锁 在Java中字符串常量池是一种特殊的存储机制它会缓存字符串字面量以便在后续使用相同字符串字面量时能够重用同一个对象。因此如果使用一个字符串常量作为锁对象可能会导致多个线程使用相同的锁对象从而破坏了锁的独立性。 37、synchronizedMap和concurrentHashMap的区别
synchronizedMap在调用map所有方法时对整个map进行同步而concurrentHashMap更加细致对map所有桶加锁只要有一个线程访问map其他线程都无法进入map而如果一个线程再访问concurrentHashMap某个桶时其他线程仍然可以对map操作。 38、对concurrentHashMap的理解 在JDK1.8中由数组、单向链表、红黑树组成。默认长度16的数组核心是哈希表。当数组长度大于64链表长度大于8单链表转为红黑树。 JDK1.8中是对指定的节点加锁保证数据更新的安全性。1.7中锁定的是segment锁的范围更大性能更低 数组长度不够时进行扩容引入了多线程并发扩容机制多个线程对原始数组进行分片每个线程负责 一个分片的数据迁移提升了扩容过程中数据迁移的效率 concurrentHashMap中 有一个size方法获取总的元素个数而多线程并发中在保证原子性的前提下来实现元素个数的累加性能低。优化在线程竞争不激烈时采用CAS实现元素个数的原子递增。如果激烈使用一个数组维护元素个数如果要增加总的元素个数直接从数组中随机选择一个通过CAS实现原子递增。 它是通过 CAS 或者 synchronized 来保证线程安全的并且缩小了锁的粒度查询性能也更高。
39、concurrentHashMap的size和put方法
size() 方法被用来获取映射中键值对的数量。这是一个估计的数字因为并发更新可能会导致计数器的变化。
put() 操作是线程安全的可以在并发环境下安全使用因为底层会使用一个称为段锁Segment Lock的锁机制在并发环境下提供高效的数据更新读写操作
为什么不直接加锁呢
concurrentHashMap中对于size数量一致性的需求不大更多的是保证数据存储的安全性多线程下获取到size后可能其他线程又进行更改也不是最新的值。
如果要获取一个精确的怎么办呢
如果需要一个线程安全的且可以返回精确size的容器Java提供了类似 java.util.concurrent.CopyOnWriteArrayList 的并发容器这些容器在添加和删除元素时才会使用锁但是在获取size()时不会使用。因为容器实际上维护了一个内部数组的拷贝在进行修改操作时它们将当前的数组进行拷贝然后在拷贝上进行修改最后再将拷贝设置为当前的数组。这使得在获取size()时无需进行锁定而可以直接返回当前数组的长度。但这类容器的缺点是修改操作需要额外的内存和CPU时间。
为什么key不允许是null
底层put源码中为空会抛出空指针异常是避免在多线程下出现歧义如果k或v为null通过get(k)获取v的时候如果返回的结果null不能确定是k本身不存在还是v就是null 40、ConcurrentHashMap 如何实现安全的
在 JDK 1.7 中使用的数组链表的结构其中数组分为两类大树组 Segment 和 小数组 HashEntry而加锁是通过给 Segment 添加ReentrantLock 重入锁来保证线程安全的。
在 JDK1.8 中使用的是数组链表红黑树的方式实现它是通过 CAS 或者 synchronized 来保证线程安全的并且缩小了锁的粒度查询性能也更高。
添加元素时首先会判断容器是否为空如果为空则使用 volatile 加 CAS 来初始化如果容器不为空则根据存储的元素计算该位置是否为空。如果根据存储的元素计算结果为空则利用 CAS 设置该节点如果根据存储的元素计算为空不为空则使用 synchronized 然后遍历桶中的数据并替换或新增节点到桶中最后再判断是否需要转为红黑树。 41、守护线程
守护线程就是一种后台服务线程是为用户线程服务的当一个程序中的所有用户线程都执行完成之后程序就会结束运行程序结束运行时不会管守护线程是否正在运行。
JVM垃圾回收机制就是典型的守护线程处理用户线程产生的内存垃圾用户线程结束守护线程也就没有意义了。
守护线程不能用在线程池或者一些IO任务的场景里因为一旦JVM退出后守护线程也会退出可能导致任务没有执行完成或者资源没有正确释放的问题。 42、CompletableFuture理解如何让一个线程等待另一个线程执行完成后触发
如果是括号的问题其实还有join()方法使用CountDownLatch。
主要用于当一个任务完成后触发一个后续的任务。 thenCombine两个任务组合一起并发执行当两个任务都执行结束后触发时间回调 thenCompose两个任务组合一起串行执行第一个任务执行完自动触发第二份 thenAccept第一个任务执行后触发第二个任务并且第一个任务的执行结果作为第二个任务的参数看不返回结果 thenApply和accept一样有返回值 thenRun第一个任务执行后执行一个实现了Runnable的任务 43、CAS
compare and swap多线程下对于共享变量的修改的原子性
只有当预期值和内存位置的值相同时才将新值写入内存。 修改前记录数据的内存地址V 读取数据的当前的值记录为A 修改数据的值变为B 查看地址V下的值是否仍然为A若为A则用B替换它若地址V下的值不为A表示在自己修改的过程中其他的线程对数据进行了修改则不更新变量的值
造成ABA的问题
变量i100线程A 100-80线程B 80-100线程C 100-80执行顺序ACB其实数据已经被更改了但是A没有发现解决方法加版本号。
CAS是一个本地方法就是先从内存读取值再去比较最后修改都会存在原子性的问题那么在多核cpu下增加一个lock指令对缓存或者总线加锁保证原子性。 44、AQS
AQS是JUC包下Lock锁的底层实现也就是多线程同步器主要两个部分组成
一是volatail修饰的 state变量作为一个竞态条件
二是双向链表维护的先进先出的等待队列
工作原理是多个线程通过对state共享变量修改实现竞态条件失败的线程加入等待队列并阻塞抢占到竞态资源的线程释放后按照先进先出实现唤醒
有两种资源共享方式
一是独占资源一个时刻只有一个线程获取竞态资源比如ReentrantLock实现排他锁
二是共享资源一个时刻多个线程可以获得竞态资源比如CountDownLatch唤醒多个线程 45、AQS为什么使用双向链表
双向链表的特点是一个指针指向前置节点一个指针指向后继节点在插入和删除的时候更高效。
那么AQS在使用双向链表的时候
一是没有竞争的锁的线程在阻塞之前要判断前置节点的状态避免存在异常线程导致无法唤醒后续线程如果没有双向链表的前置节点就需要从head遍历性能低
二是lock接口中有一个方法处于阻塞的线程允许被中断也就是没有竞争到锁的线程加入到队列等待后允许外部线程通过interrupt方法唤醒并中断中断的线程状态变成cancelled是不需要竞争锁的但是仍在队列中后续竞争锁时这个节点需要移除否则导出其他线程无法被正常唤醒
三是避免线程阻塞和唤醒的开销刚加入到链表的线程会通过自旋的方式尝试竞争锁但是按照公平锁的设计只有头结点的下一个节点才去竞争锁否则会出现大量的线程在阻塞之前尝试竞争锁带来大的性能开销所以在加入到链表尝试竞争锁之前判断前置节点是不是头结点如果不是就没必要触发竞争。 46、伪共享
cpu里设计三级缓存去平衡cpu和内存直接的速度差异一次性读取64个字节作为一个缓存行缓存到cpu高速缓存里。java中一个long是8个字节一个缓存行可以存储8个long的变量也就是如果一个存储器的位置被引用附加的位置也会被引用有效的减少和内存的交互次数避免了cpu的io等待提升cpu的利用率。
如果多个线程修改同一个缓存行里的多个独立变量基于缓存一致性协议影响了彼此的性能比如线程A更新变量A线程B更新变量BAB都在一个缓存行每个线程都要去竞争缓存行的所有权基于缓存一致性一旦运行在某个cpu上的线程获得了所有权并执行修改导致其他cpu的缓存行失效就是伪共享。
解决java8里提供了Contented通过缓存行填充来解决伪共享的问题被注解的数据会被加载到独立的缓存行上。 47、对 Happens-Before 的理解
在多线程环境下因为指令重排序的存在会导致数据的可见性问题也就是 A 线程修改某个共享变量对 B 线程不可见。
Happens-Before 只要不对结果产生影响仍然允许指令的重排序。
程序顺序规则在一个线程中按照程序代码的顺序前面的操作 Happens-Before 于后续的操作。
监视器锁规则对一个锁的解锁 Happens-Before 于随后对这个锁的加锁。
volatile 变量规则对一个 volatile 变量的写操作 Happens-Before 于随后对这个变量的读操作。
传递性如果 A Happens-Before B且 B Happens-Before C则可以得出 A Happens-Before C。
线程 start 规则线程的启动start操作 Happens-Before 于该线程的任何操作。
线程 join 规则在一个线程中对其他线程的 join 操作 Happens-Before 于被 join 线程的任何操作。
线程中断规则对线程的 interrupt() 方法的调用 Happens-Before 于被中断线程的代码检测到中断事件的发生。
对象终结规则对象的构造函数执行 Happens-Before 于 finalize() 方法的开始执行。 48、SimpleDateFormat是线程安全的吗
不是内部有一个Calendar对象引用存储相关的日期信息。如果作为共享资源使用共享Calendar引用导致脏读。
定义成局部变量每个线程调用的时候创建一个新的实例
加同步锁只允许一个线程使用
1.8加入了安全的APIjava.time比如LocalDateTimer、SimpleDateFormatte