有没有IT做兼职的网站,wordpress title插件,自己的网站到期域名如何续费,微信小程序制作视频#x1f446;#x1f3fb;一个专注于 Java 面试的原创公众号。我花了点时间整理了一些多线程,并发相关的面试题#xff0c;虽然不是很多#xff0c;但是偶尔看看还是很有用的哦#xff01;话不多说#xff0c;直接开整#xff01;01 什么是线程#xff1f;线程是操作系… 一个专注于 Java 面试的原创公众号。我花了点时间整理了一些多线程,并发相关的面试题虽然不是很多但是偶尔看看还是很有用的哦话不多说直接开整01 什么是线程线程是操作系统能够进⾏运算调度的最⼩单位它被包含在进程之中是进程中的实际运作单位可以使⽤多线程对进⾏运算提速。02 什么是线程安全和线程不安全线程安全:就是多线程访问时采⽤了加锁机制当⼀个线程访问该类的某个数据时进⾏保护其他线程不能进⾏访问直到该线程读取完其他线程才可使⽤。不会出现数据不⼀致或者数据污染。Vector 是⽤同步⽅法来实现线程安全的, ⽽和它相似的ArrayList不是线程安全的。线程不安全就是不提供数据访问保护有可能出现多个线程先后更改数据造成所得到的数据是脏数据 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作⽽⽆写操作⼀般来说这个全局变量是线程安全的若有多个线程同时执⾏写操作⼀般都需要考虑线程同步否则的话就可能影响线程安全。03 什么是自旋锁自旋锁是SMP架构中的⼀种low-level的同步机制。当线程A想要获取⼀把自旋锁⽽该锁⼜被其它线程锁持有时线程A会在⼀个循环中自旋以检测锁是不是已经可⽤了。自旋锁需要注意由于自旋时不释放CPU因⽽持有自旋锁的线程应该尽快释放自旋锁否则等待该自旋锁的线程会⼀直在那⾥自旋这就会浪费CPU时间。持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。⽬前的JVM实现自旋会消耗CPU如果⻓时间不调⽤doNotify⽅法doWait⽅发会⼀直自旋CPU会消耗太⼤。自旋锁⽐较适⽤于锁使⽤者保持锁时间⽐较短的情况这种情况自旋锁的效率⽐较⾼。自旋锁是⼀种对多处理器相当有效的机制⽽在单处理器⾮抢占式的系统中基本上没有做⽤。04 什么是CASCAScompare and swap的缩写中⽂翻译成⽐较并交换。CAS 不通过JVM,直接利⽤java本地⽅ JNIJava Native Interface为JAVA本地调⽤,直接调⽤CPU 的cmpxchg是汇编指令指令。利⽤CPU的CAS指令同时借助JNI来完成Java的⾮阻塞算法,实现原⼦操作。其它原⼦操作都是利⽤类似的特性完成的。整个java.util.concurrent都是建⽴在CAS之上的因此对于synchronized阻塞算法J.U.C在性能上有了很⼤的提升。CAS是项乐观锁技术当多个线程尝试使⽤CAS同时更新同⼀个变量时只有其中⼀个线程能更新变量的值⽽其它线程都失败失败的线程并不会被挂起⽽是被告知这次竞争中失败并可以再次尝试。使⽤CAS在线程冲突严重时会⼤幅降低程序性能CAS只适合于线程冲突较少的情况使⽤。synchronized在jdk1.6之后已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列基本思路是自旋后阻塞竞争切换后继续竞争锁稍微牺牲了公平性但获得了⾼吞吐量。在线程冲突较少的情况下可以获得和CAS类似的性能⽽线程冲突严重的情况下性能远⾼于CAS。05 什么是乐观锁和悲观锁悲观锁Java在JDK1.5之前都是靠synchronized关键字保证同步的这种通过使⽤⼀致的锁定协议来协调对共享状态的访问可以确保⽆论哪个线程持有共享变量的锁都采⽤独占的⽅式来访问这些变量。独占锁其实就是⼀种悲观锁所以可以说synchronized是悲观锁。乐观锁乐观锁 Optimistic Locking其实是⼀种思想。相对悲观锁⽽⾔乐观锁假设认为数据⼀般情况下不会造成冲突所以在数据进⾏提交更新的时候才会正式对数据的冲突与否进⾏检测如果发现冲突了则让返回⽤户错误的信息让⽤户决定如何去做。memcached使⽤了cas乐观锁技术保证数据⼀致性。06 什么是AQS1、AbstractQueuedSynchronizer简称AQS是⼀个⽤于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建例如ReentrantLockSemaphoreCountDownLatchReentrantReadWriteLockFutureTask等。AQS解决了在实现同步容器时设计的⼤量细节问题。2、AQS使⽤⼀个FIFO的队列表示排队等待锁的线程队列头节点称作“哨兵节点”或者“哑节点”它不与任何线程关联。其他的节点与等待线程关联每个节点维护⼀个等待状态waitStatus07 什么是原⼦操作在Java Concurrency API中有哪些原⼦类(atomic classes)原⼦操作是指⼀个不受其他操作影响的操作任务单元。原⼦操作是在多线程环境下避免数据不⼀致必须的⼿段。int并不是⼀个原⼦操作所以当⼀个线程读取它的值并加1时另外⼀个线程有可能会读到之前的值这就会引发错误。为了解决这个问题必须保证增加操作是原⼦的在JDK1.5之前我们可以使⽤同步技术来做到这⼀点。到JDK1.5java.util.concurrent.atomic包提供了int和long类型的装类它们可以自动的保证对于他们的操作是原⼦的并且不需要使⽤同步。08 什么是Executors框架Java通过Executors提供四种线程池分别为newCachedThreadPool创建⼀个可缓存线程池如果线程池⻓度超过处理需要可灵活回收空闲线程若⽆可回收则新建线程。newFixedThreadPool 创建⼀个定⻓线程池可控制线程最⼤并发数超出的线程会在队列中等待。newScheduledThreadPool 创建⼀个定⻓线程池⽀持定时及周期性任务执⾏。newSingleThreadExecutor 创建⼀个单线程化的线程池它只会⽤唯⼀的⼯作线程来执⾏任务保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执⾏。09 什么是阻塞队列如何使⽤阻塞队列来实现⽣产者-消费者模型1、JDK7提供了7个阻塞队列。也属于并发容器ArrayBlockingQueue ⼀个由数组结构组成的有界阻塞队列。LinkedBlockingQueue ⼀个由链表结构组成的有界阻塞队列。PriorityBlockingQueue ⼀个⽀持优先级排序的⽆界阻塞队列。DelayQueue⼀个使⽤优先级队列实现的⽆界阻塞队列。SynchronousQueue⼀个不存储元素的阻塞队列。LinkedTransferQueue⼀个由链表结构组成的⽆界阻塞队列。LinkedBlockingDeque⼀个由链表结构组成的双向阻塞队列。2、概念阻塞队列是⼀个在队列基础上⼜⽀持了两个附加操作的队列。3、2个附加操作3.1. ⽀持阻塞的插⼊⽅法队列满时队列会阻塞插⼊元素的线程直到队列不满。3.2 ⽀持阻塞的移除⽅法队列空时获取元素的线程会等待队列变为⾮空。10 什么是Callable和Future?1、Callable 和 Future 是⽐较有趣的⼀对组合。当我们需要获取线程的执⾏结果时就需要⽤到它们。Callable⽤于产⽣结果Future⽤于获取结果。2、Callable接⼝使⽤泛型去定义它的返回类型。Executors类提供了⼀些有⽤的⽅法去在线程池中执⾏Callable内的任务。由于Callable任务是并⾏的必须等待它返回的结果。java.util.concurrent.Future对象解决了这个问题。3、在线程池提交Callable任务后返回了⼀个Future对象使⽤它可以知道Callable任务的状态和得到Callable返回的执⾏结果。Future提供了get()⽅法等待Callable结束并获取它的执⾏结果。11 什么是FutureTask?1、FutureTask可⽤于异步获取执⾏结果或取消执⾏任务的场景。通过传⼊Runnable或者Callable的任务给FutureTask直接调⽤其run⽅法或者放⼊线程池执⾏之后可以在外部通过FutureTask的get⽅法异步获取执⾏结果因此FutureTask⾮常适合⽤于耗时的计算主线程可以在完成自⼰的任务后再去获取结果。另外FutureTask还可以确保即使调⽤了多次run⽅法它都只会执⾏⼀次Runnable或者Callable任务或者通过cancel取消FutureTask的执⾏等。2、futuretask可⽤于执⾏多任务、以及避免⾼并发情况下多次创建数据机锁的出现。12 什么是同步容器和并发容器的实现同步容器:1、主要代表有Vector和Hashtable以及Collections.synchronizedXxx等。2、锁的粒度为当前对象整体。3、迭代器是及时失败的即在迭代的过程中发现被修改就会抛出ConcurrentModificationException。并发容器:1、主要代表有ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListMap、ConcurrentSkipListSet。2、锁的粒度是分散的、细粒度的即读和写是使⽤不同的锁。3、迭代器具有弱⼀致性即可以容忍并发修改不会抛出ConcurrentModificationException。ConcurrentHashMap 采⽤分段锁技术同步容器中是⼀个容器⼀个锁但在ConcurrentHashMap中会将hash表的数组部分分成若⼲段每段维护⼀个锁以达到⾼效的并发访问13 什么是多线程的上下⽂切换1、多线程是指从软件或者硬件上实现多个线程的并发技术。2、多线程的好处使⽤多线程可以把程序中占据时间⻓的任务放到后台去处理如图⽚、视屏的下载 发挥多核处理器的优势并发执⾏让系统运⾏的更快、更流畅⽤户体验更好3、多线程的缺点⼤量的线程降低代码的可读性更多的线程需要更多的内存空间, 当多个线程对同⼀个资源出现争夺时候要注意线程安全的问题。4、多线程的上下⽂切换CPU通过时间⽚分配算法来循环执⾏任务当前任务执⾏⼀个时间⽚后会切换到下⼀个任务。但是在切换前会保存上⼀个任务的状态以便下次切换回这个任务时可以再次加载这个任务的状态。14 ThreadLocal的设计理念与作⽤Java中的ThreadLocal类允许我们创建只能被同⼀个线程读写的变量。因此如果⼀段代码含有⼀个ThreadLocal变量的引⽤即使两个线程同时执⾏这段代码它们也⽆法访问到对⽅的ThreadLocal变量。概念线程局部变量。在并发编程的时候成员变量如果不做任何处理其实是线程不安全的各个线程都在操作同⼀个变量显然是不⾏的并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有⼀种情况之下我们需要满⾜这样⼀个条件变量是同⼀个但是每个线程都使⽤同⼀个初始值也就是使⽤同⼀个变量的⼀个新的副本。这种情况之下ThreadLocal就⾮常适⽤⽐如说DAO的数据库连接我们知道DAO是单例的那么他的属性Connection就不是⼀个线程安全的变量。⽽我们每个线程都需要使⽤他并且各自使⽤各自的。这种情况ThreadLocal就⽐较好的解决了这个问题。原理从本质来讲就是每个线程都维护了⼀个map⽽这个map的key就threadLocal⽽值就是我们set的那个值每次线程在get的时候都从自⼰的变量中取值既然从自⼰的变量中取值那肯定就不存在线程安全问题总体来讲ThreadLocal这个变量的状态根本没有发⽣变化他仅仅是充当⼀个key的⻆⾊另外提供给每⼀个线程⼀个初始值。实现机制每个Thread对象内部都维护了⼀个ThreadLocalMap这样⼀个ThreadLocal的Map可以存放若⼲个 ThreadLocal。15 ThreadPool线程池⽤法与优势ThreadPool 优点:减少了创建和销毁线程的次数每个⼯作线程都可以被重复利⽤可执⾏多个任务 可以根据系统的承受能⼒调整线程池中⼯作线线程的数⽬防⽌因为因为消耗过多的内存⽽把服务器累趴下(每个线程需要⼤约1MB内存线程开的越多消耗的内存也就越⼤最后死机)------减少在创建和销毁线程上所花的时间以及系统资源的开销------如不使⽤线程池有可能造成系统创建⼤量线程⽽导致消耗完系统内存Java⾥⾯线程池的顶级接⼜是Executor但是严格意义上讲Executor并不是⼀个线程池⽽只是⼀个执⾏线程的⼯具。真正的线程池接⼜是ExecutorService。当线程数⼩于corePoolSize时创建线程执⾏任务。当线程数⼤于等于corePoolSize并且workQueue没有满时放⼊workQueue中线程数⼤于等于corePoolSize并且当workQueue满时新任务新建线程运⾏线程总数要⼩于maximumPoolSize当线程总数等于maximumPoolSize并且workQueue满了的时候执⾏handler的rejectedExecution。也就是拒绝策略。16 Concurrent包⾥的其他东⻄ArrayBlockingQueue、CountDownLatch等等。1、ArrayBlockingQueue 数组结构组成的有界阻塞队列。2、CountDownLatch 允许⼀个或多个线程等待其他线程完成操作join⽤于让当前执⾏线程等待join线程执⾏结束。其实现原理是不停检查join线程是否存活如果join线程存活则让当前线程永远wait。17 synchronized和ReentrantLock的区别基础知识:可重⼊锁。可重⼊锁是指同⼀个线程可以多次获取同⼀把锁。ReentrantLocksynchronized都是可重⼊锁。可中断锁。可中断锁是指线程尝试获取锁的过程中是否可以响应中断。synchronized是不可中断锁⽽ReentrantLock则提供了中断功能。公平锁与⾮公平锁。公平锁是指多个线程同时尝试获取同⼀把锁时获取锁的顺序按照线程达到的顺序⽽⾮公平锁则允许线程“插队”。synchronized是⾮公平锁⽽ReentrantLock的默认实现是⾮公平锁但是也可以设置为公平锁。CAS操作(CompareAndSwap)。CAS操作简单的说就是⽐较并交换。CAS 操作包含三个操作数 —— 内存位置V、预期原值A和新值(B)。如果内存位置的值与预期原值相匹配那么处理器会自动将该位置值更新为新值。否则处理器不做任何操作。⽆论哪种情况它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了“我认为位置 V 应该包含值 A如果包含该值则将 B 放到这个位置否则不要更改该位置只告诉我这个位置现在的值即可。”Synchronized: isynchronized是java内置的关键字它提供了⼀种独占的加锁⽅式。synchronized的获取和释放锁由JVM实现⽤户不需要显示的释放锁⾮常⽅便。然⽽synchronized也有⼀定的局限性当线程尝试获取锁的时候如果获取不到锁会⼀直阻塞。如果获取锁的线程进⼊休眠或者阻塞除⾮当前线程异常否则其他线程尝试获取锁必须⼀直等待。ReentrantLock:ReentrantLock它是JDK 1.5之后提供的API层⾯的互斥锁需要lock()和unlock()⽅法配合try/finally语句块来完成。等待可中断避免出现死锁的情况如果别的线程正持有锁会等待参数给定的时间在等待的过程中如果获取了锁定就返回true如果等待超时返回false公平锁与⾮公平锁多个线程等待同⼀个锁时必须按照申请锁的时间顺序获得锁Synchronized锁⾮公平锁ReentrantLock默认的构造函数是创建的⾮公平锁可以通过参数true设为公平锁但公平锁表现的性能不是很好。18 Semaphore有什么作⽤Semaphore就是⼀个信号量它的作⽤是限制某段代码块的并发数19 Java Concurrency API中的Lock接⼝(Lock interface)是什么对⽐同步它有什么优势1、Lock接⼝⽐同步⽅法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构可以具有完全不同的性质并且可以⽀持多个相关类的条件对象。2、它的优势有可以使锁更公平 可以使线程在等待锁的时候响应中断 可以让线程尝试获取锁并在⽆法获取锁的时候⽴即返回或者等待⼀段时间 可以在不同的范围以不同的顺序获取和释放锁20 Hashtable的size()⽅法中明明只有⼀条语句”return count”为什么还要做同步1、同⼀时间只能有⼀条线程执⾏固定类的同步⽅法但是对于类的⾮同步⽅法可以多条线程同时访问。所以这样就有问题了可能线程A在执⾏Hashtable的put⽅法添加数据线程B则可以正常调⽤size()⽅法读取Hashtable中当前元素的个数那读取到的值可能不是最新的可能线程A添加了完了数据但是没有对size线程B就已经读取size了那 么对于线程B来说读取到的size⼀定是不准确的。2、⽽给size()⽅法加了同步之后意味着线程B调⽤size()⽅法只有在线程A调⽤put⽅法完毕之后才可以调⽤这样就保证了线程安全性。21 ConcurrentHashMap的并发度是什么1、⼯作机制分⽚思想它引⼊了⼀个“分段锁”的概念具体可以理解为把⼀个⼤的Map拆分成N个⼩的segment根据key.hashCode()来决定把key放到哪个HashTable中。可以提供相同的线程安全但是效率提升N倍默认提升16倍。2、应⽤当读写时使⽤适合做缓存在程序启动时初始化之后可以被多个线程访问3、hash冲突简介HashMap中调⽤hashCode()⽅法来计算hashCode。由于在Java中两个不同的对象可能有⼀样的hashCode,所以不同的键可能有⼀样hashCode从⽽导致冲突的产⽣。hash冲突解决使⽤平衡树来代替链表当同⼀hash中的元素数量超过特定的值便会由链表切换到平衡树4、⽆锁读ConcurrentHashMap之所以有较好的并发性是因为ConcurrentHashMap是⽆锁读和加锁写并且利⽤了分段锁不是在所有的entry上加锁⽽是在⼀部分entry上加锁读之前会先判断count(jdk1.6)其中的count是被volatile修饰的(当变量被volatile修饰后每次更改该变量的时候会将更改结果写到系统主内存中利⽤多处理器的缓存⼀致性其他处理器会发现自⼰的缓存⾏对应的内存地址被修改就会将自⼰处理器的缓存⾏设置为失效并强制从系统主内存获取最新的数据。)故可以实现⽆锁读。5、ConcurrentHashMap的并发度就是segment的⼤⼩默认为16这意味着最多同时可以有16条线程操作ConcurrentHashMap这也是ConcurrentHashMap对Hashtable的最⼤优势。22 ReentrantReadWriteLock读写锁的使⽤1、读写锁分为读锁和写锁多个读锁不互斥读锁与写锁互斥这是由jvm自⼰控制的你只要上好相应的锁即可。2、如果你的代码只读数据可以很多⼈同时读但不能同时写那就上读锁3、如果你的代码修改数据只能有⼀个⼈在写且不能同时读取那就上写锁。总之读的时候上读锁写的时候上写锁23 CyclicBarrier和CountDownLatch的⽤法及区别CyclicBarrier和CountDownLatch 都位于java.util.concurrent 这个包下24 LockSupport⼯具LockSupport是JDK中⽐较底层的类⽤来创建锁和其他同步⼯具类的基本线程阻塞。java锁和同步器框架的核⼼ AQS:AbstractQueuedSynchronizer就是通过调⽤ LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒的。25 Condition接⼝及其实现原理在java.util.concurrent包中有两个很特殊的⼯具类Condition和ReentrantLock使⽤过的⼈都知道ReentrantLock重⼊锁是jdk的concurrent包提供的⼀种独占锁的实现。我们知道在线程的同步时可以使⼀个线程阻塞⽽等待⼀个信号同时放弃锁使其他线程可以能竞争到锁。在synchronized中我们可以使⽤Object的wait()和notify⽅法实现这种等待和唤醒。但是在Lock中怎么实现这种wait和notify呢答案是Condition学习Condition主要是为了⽅便以后学习blockqueue和concurrenthashmap的源码同时也进⼀步理解ReentrantLock。26 Fork/Join框架的理解?1、Fork就是把⼀个⼤任务切分为若⼲⼦任务并⾏的执⾏。2、Join就是合并这些⼦任务的执⾏结果最后得到这个⼤任务的结果。27 wait()和sleep()的区别?1、sleep() ⽅法是线程类Thread的静态⽅法让调⽤线程进⼊睡眠状态让出执⾏机会给其他线程等到休眠时间结束后线程进⼊就绪状态和其他线程⼀起竞争cpu的执⾏时间。因为sleep() 是static静态的⽅法他不能改变对象的机锁当⼀个synchronized块中调⽤了sleep() ⽅法线程虽然进⼊休眠但是对象的机锁没有被释放其他线程依然⽆法访问这个对象。2、wait() wait()是Object类的⽅法当⼀个线程执⾏到wait⽅法时它就进⼊到⼀个和该对象相关的等待池同时释放对象的机锁使得其他线程能够访问可以通过notifynotifyAll⽅法来唤醒等待的线程。28 线程的五个状态五种状态创建、就绪、运⾏、阻塞和死亡?线程通常都有五种状态创建、就绪、运⾏、阻塞和死亡。第⼀是创建状态。在⽣成线程对象并没有调⽤该对象的start⽅法这是线程处于创建状态。第⼆是就绪状态。当调⽤了线程对象的start⽅法之后该线程就进⼊了就绪状态但是此时线程调度程序还没有把该线程设置为当前线程此时处于就绪状态。在线程运⾏之后从等待或者睡眠中回来之后也会处于就绪状态 。第三是运⾏状态。线程调度程序将处于就绪状态的线程设置为当前线程此时线程就进⼊了运⾏状态开始运⾏run函数当中的代码。第四是阻塞状态。线程正在运⾏的时候被暂停通常是为了等待某个时间的发⽣(⽐如说某项资源就绪)之后再继续运⾏。sleep,suspendwait等⽅法都可以导致线程阻塞。第五是死亡状态。如果⼀个线程的run⽅法执⾏结束或者调⽤stop⽅法后该线程就会死亡。对于已经死亡的线程⽆法再使⽤start⽅法令其进⼊就绪。29 start()⽅法和run()⽅法的区别1、start()⽅法来启动⼀个线程真正实现了多线程运⾏。2、如果直接调⽤run(),其实就相当于是调⽤了⼀个普通函数⽽已直接调⽤run()⽅法必须等待run()⽅法执⾏完毕才能执⾏下⾯的代码所以执⾏路径还是只有⼀条根本就没有线程的特征所以在多线程执⾏时要使⽤start()⽅法⽽不是run()⽅法。30 Runnable接⼝和Callable接⼝的区别Runnable接⼝中的run()⽅法的返回值是void它做的事情只是纯粹地去执⾏run()⽅法中的代码⽽已Callable接⼝中的call()⽅法是有返回值的是⼀个泛型和Future、FutureTask配合可以⽤来获取异步执⾏的结果。31 volatile关键字的作⽤多线程主要围绕可⻅性和原⼦性两个特性⽽展开使⽤volatile关键字修饰的变量保证了其在多线程之间的可⻅性即每次读取到volatile变量⼀定是最新的数据。代码底层执⾏不像我们看到的⾼级语⾔—-Java程序这么简单它的执⾏是Java代码–字节码–根据字节码执⾏对应的C/C代码–C/C代码被编译成汇编语⾔–和硬件电路交互现实中为了获取更好的性能JVM可能会对指令进⾏重排序多线程下可能会出现⼀些意想不到的问题。使⽤volatile则会对禁⽌语义重排序当然这也⼀定程度上降低了代码执⾏效率。32 Java中如何获取到线程dump⽂件死循环、死锁、阻塞、⻚⾯打开慢等问题查看线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈获取到线程堆栈有两步获取到线程的pid可以通过使⽤jps命令在Linux环境下还可以使⽤ps -ef | grep java打印线程堆栈可以通过使⽤jstack pid命令在Linux环境下还可以使⽤kill -3 pid另外提⼀点Thread类提供了⼀个getStackTrace()⽅法也可以⽤于获取线程堆栈。这是⼀个实例⽅法因此此⽅法是和具体线程实例绑定的每次获取到的是具体某个线程当前运⾏的堆栈。33 线程和进程有什么区别进程是系统进⾏资源分配的基本单位有独⽴的内存地址空间线程是CPU独⽴运⾏和独⽴调度的基本单位没有单独地址空间有独⽴的栈局部变量寄存器 程序计数器等。创建进程的开销⼤包括创建虚拟地址空间等需要⼤量系统资源创建线程开销⼩基本上只有⼀个内核对象和⼀个堆栈。⼀个进程⽆法直接访问另⼀个进程的资源同⼀进程内的多个线程共享进程的资源。进程切换开销⼤线程切换开销⼩进程间通信开销⼤线程间通信开销⼩。线程属于进程不能独⽴执⾏。每个进程⾄少要有⼀个线程成为主线程34 线程实现的⽅式有⼏种四种继承Thread类重写run⽅法实现Runnable接⼝重写run⽅法实现Runnable接⼝的实现类的实例对象作为Thread构造函数的target实现Callable接⼝通过FutureTask包装器来创建Thread线程通过线程池创建线程35 高并发、任务执⾏时间短的业务怎样使⽤线程池并发不⾼、任务执⾏时间⻓的业务怎样使⽤线程池并发⾼业务执⾏时间⻓的业务怎样使⽤线程池高并发、任务执⾏时间短的业务线程池线程数可以设置为CPU核数1减少线程上下⽂的切换。并发不⾼、任务执⾏时间⻓的业务要区分开看假如是业务时间⻓集中在IO操作上也就是IO密集型的任务因为IO操作并不占⽤CPU所以不要让所有的CPU闲下来可以加⼤线程池中的线程数⽬让CPU处理更多的业务假如是业务时间⻓集中在计算操作上也就是计算密集型任务这个就没办法了和1⼀样吧线程池中的线程数设置得少⼀些减少线程上下⽂的切换并发⾼、业务执⾏时间⻓解决这种类型任务的关键不在于线程池⽽在于整体架构的设计看看这些业务⾥⾯某些数据是否能做缓存是第⼀步增加服务器是第⼆步⾄于线程池的设置设置参考2。最后业务执⾏时间⻓的问题 也可能需要分析⼀下看看能不能使⽤中间件对任务进⾏拆分和解耦。36 如果你提交任务时线程池队列已满这时会发⽣什么1、如果你使⽤的LinkedBlockingQueue也就是⽆界队列的话没关系继续添加任务到阻塞队列中等待执⾏因为LinkedBlockingQueue可以近乎认为是⼀个⽆穷⼤的队列可以⽆限存放任务2、如果你使⽤的是有界队列⽐⽅说ArrayBlockingQueue的话任务⾸先会被添加到ArrayBlockingQueue中ArrayBlockingQueue满了则会使⽤拒绝策略RejectedExecutionHandler处理满了的任务默认是AbortPolicy。37 锁的等级⽅法锁、对象锁、类锁?⽅法锁synchronized修饰⽅法时:通过在⽅法声明中加⼊ synchronized关键字来声明 synchronized ⽅法。synchronized ⽅法控制对类成员变量的访问;每个类实例对应⼀把锁每个 synchronized ⽅法都必须获得调⽤该⽅法的类实例的锁⽅能执⾏否则所属线程阻塞⽅法⼀旦执⾏就独占该锁直到从该⽅法返回时才将锁释放此后被阻塞的线程⽅能获得该锁重新进⼊可执⾏状态。这种机制确保了同⼀时刻对于每⼀个类实例其所有声明为 synchronized 的成员函数中⾄多只有⼀个处于可执⾏状态从⽽有效避免了类成员变量的访问冲突。对象锁synchronized修饰⽅法或代码块:当⼀个对象中有synchronized method或synchronized block的时候调⽤此对象的同步⽅法或进⼊其同步区域时就必须先获得对象锁。如果此对象的对象锁已被其他调⽤者占⽤则需要等待此锁被释放。⽅法锁也是对象锁java的所有对象都含有1个互斥锁这个锁由JVM自动获取和释放。线程进⼊synchronized⽅法的时候获取该对象的锁当然如果已经有线程获取了这个对象的锁那么当前线程会等待synchronized⽅法正常返回或者抛异常⽽终⽌JVM会自动释放对象锁。这⾥也体现了⽤synchronized来加锁的1个好处⽅法抛异常的时候锁仍然可以由JVM来自动释放。类锁(synchronized 修饰静态的⽅法或代码块):由于⼀个class不论被实例化多少次其中的静态⽅法和静态变量在内存中都只有⼀份。所以⼀旦⼀个静态的⽅法被申明为synchronized。此类所有的实例化对象在调⽤此⽅法共⽤同⼀把锁我们称之为类锁。对象锁是⽤来控制实例⽅法之间的同步类锁是⽤来控制静态⽅法或静态变量互斥体之间的同步38 如果同步块内的线程抛出异常会发⽣什么synchronized⽅法正常返回或者抛异常⽽终⽌JVM会自动释放对象锁39 并发编程concurrency并⾏编程parallellism有什么区别解释⼀并⾏是指两个或者多个事件在同⼀时刻发⽣⽽并发是指两个或多个事件在同⼀时间间隔发⽣。解释⼆并⾏是在不同实体上的多个事件并发是在同⼀实体上的多个事件。解释三在⼀台处理器上“同时”处理多个任务在多台处理器上同时处理多个任务。如hadoop分布式集群所以并发编程的⽬标是充分的利⽤处理器的每⼀个核以达到最⾼的处理性能。40 如何保证多线程下 i 结果正确volatile只能保证你数据的可⻅性获取到的是最新的数据不能保证原⼦性⽤AtomicInteger保证原⼦性。synchronized既能保证共享变量可⻅性也可以保证锁内操作的原⼦性。41 ⼀个线程如果出现了运⾏时异常会怎么样?如果这个异常没有被捕获的话这个线程就停⽌执⾏了。另外重要的⼀点是如果这个线程持有某个对象的监视器那么这个对象监视器会被⽴即释放.42 如何在两个线程之间共享数据?通过在线程之间共享对象就可以了然后通过wait/notify/notifyAll、await/signal/signalAll进⾏唤起和等待⽐⽅说阻塞队列BlockingQueue就是为线程之间共享数据⽽设计的。43 ⽣产者消费者模型的作⽤是什么?通过平衡⽣产者的⽣产能⼒和消费者的消费能⼒来提升整个系统的运⾏效率这是⽣产者消费者模型最重要的作⽤。解耦这是⽣产者消费者模型附带的作⽤解耦意味着⽣产者和消费者之间的联系少联系越少越可以独自发展⽽不需要受到相互的制约。44 怎么唤醒⼀个阻塞的线程?如果线程是因为调⽤了wait()、sleep()或者join()⽅法⽽导致的阻塞suspend与resume:Java废弃 suspend() 去挂起线程的原因是因为 suspend() 在导致线程暂停的同时并不会去释放任何锁资源。其他线程都⽆法访问被它占⽤的锁。直到对应的线程执⾏ resume() ⽅法后被挂起的线程才能继续从⽽其它被阻塞在这个锁的线程才可以继续执⾏。但是如果 resume() 操作出现在 suspend() 之前执⾏那么线程将⼀直处于挂起状态同时⼀直占⽤锁这就产⽣了死锁。⽽且对于被挂起的线程它的线程状态居然还是 Runnable。wait与notifywait与notify必须配合synchronized使⽤因为调⽤之前必须持有锁wait会⽴即释放锁notify则是同步块执⾏完了才释放await与singalCondition类提供⽽Condition对象由new ReentLock().newCondition()获得与wait和notify相同因为使⽤Lock锁后⽆法使⽤wait⽅法park与unparkLockSupport是⼀个⾮常⽅便实⽤的线程阻塞⼯具它可以在线程任意位置让线程阻塞。和Thread.suspenf()相⽐它弥补了由于resume()在前发⽣导致线程⽆法继续执⾏的情况。和Object.wait()相⽐它不需要先获得某个对象的锁也不会抛出IException异常。可以唤醒指定线程。如果线程遇到了IO阻塞⽆能为⼒因为IO是操作系统实现的Java代码并没有办法直接接触到操作系统。45 Java中⽤到的线程调度算法是什么抢占式。⼀个线程⽤完CPU之后操作系统会根据线程优先级、线程饥饿情况等数据算出⼀个总的优先级并分配下⼀个时间⽚给某个线程执⾏。46 单例模式的线程安全性?⽼⽣常谈的问题了⾸先要说的是单例模式的线程安全意味着某个类的实例在多线程环境下只会被创建⼀次出来。单例模式有很多种的写法我总结⼀下1饿汉式单例模式的写法线程安全2懒汉式单例模式的写法⾮线程安全3双检锁单例模式的写法线程安全47 线程类的构造⽅法、静态块是被哪个线程调⽤的?线程类的构造⽅法、静态块是被new这个线程类所在的线程所调⽤的⽽run⽅法⾥⾯的代码才是被线程自身所调⽤的。48 同步⽅法和同步块哪个是更好的选择?同步块是更好的选择因为它不会锁住整个对象当然也可以让它锁住整个对象。同步⽅法会锁住整个对象哪怕这个类中有多个不相关联的同步块这通常会导致他们停⽌执⾏并需要等待获得这个对象上的锁。synchronized(this)以及⾮static的synchronized⽅法⾄于static synchronized⽅法请往下看只能防⽌多个线程同时执⾏同⼀个对象的同步代码段。如果要锁住多个对象⽅法可以锁住⼀个固定的对象或者锁住这个类的Class对象。synchronized锁住的是括号⾥的对象⽽不是代码。对于⾮static的synchronized⽅法锁的就是对象本身也就是this。49 如何检测死锁怎么预防死锁概念是指两个或两个以上的进程在执⾏过程中因争夺资源⽽造成的⼀种互相等待的现象若⽆外⼒作⽤它们都将⽆法推进下去。此时称系统处于死锁死锁的四个必要条件互斥条件进程对所分配到的资源不允许其他进程进⾏访问若其他进程访问该资源只能等待直⾄占有该资源的进程使⽤完成后释放该资源请求和保持条件进程获得⼀定的资源之后⼜对其他资源发出请求但是该资源可能被其他进程占有此时请求阻塞但⼜对自⼰获得的资源保持不放不可剥夺条件是指进程已获得的资源在未完成使⽤之前不可被剥夺只能在使⽤完后自⼰释放环路等待条件是指进程发⽣死锁后若⼲进程之间形成⼀种头尾相接的循环等待资源关系死锁产⽣的原因因竞争资源发⽣死锁 现象系统中供多个进程共享的资源的数⽬不⾜以满⾜全部进程的需要时就会引起对诸资源的竞争⽽发⽣死锁现象进程推进顺序不当发⽣死锁检查死锁:有两个容器⼀个⽤于保存线程正在请求的锁⼀个⽤于保存线程已经持有的锁。每次加锁之前都会做如下检测检测当前正在请求的锁是否已经被其它线程持有,如果有则把那些线程找出来遍历第⼀步中返回的线程检查自⼰持有的锁是否正被其中任何⼀个线程请求如果第⼆步返回真,表示出现了死锁死锁的解除与预防控制不要让四个必要条件成⽴。50 HashMap在多线程环境下使⽤需要注意什么要注意死循环的问题HashMap的put操作引发扩容这个动作在多线程并发下会发⽣线程死循环的问题。1、HashMap不是线程安全的Hashtable线程安全但效率低因为是Hashtable是使⽤synchronized的所有线程竞争同⼀把锁⽽ConcurrentHashMap不仅线程安全⽽且效率⾼因为它包含⼀个segment数组将数据分段存储给每⼀段数据配⼀把锁也就是所谓的锁分段技术。2、HashMap为何线程不安全put时key相同导致其中⼀个线程的value被覆盖多个线程同时扩容造成数据丢失多线程扩容时导致Node链表形成环形结构造成.next()死循环导致CPU利⽤率接近100%3、ConcurrentHashMap最⾼效51 什么是守护线程有什么⽤守护线程即daemon thread是个服务线程准确地来说就是服务其他的线程这是它的作⽤——⽽其他的线程只有⼀种那就是⽤户线程。所以java⾥线程分2种1、守护线程⽐如垃圾回收线程就是最典型的守护线程。2、⽤户线程就是应⽤程序⾥的自定义线程。52 如何实现线程串⾏执⾏a. 为了控制线程执⾏的顺序如ThreadA-ThreadB-ThreadC-ThreadA循环执⾏三个线程我们需要确定唤醒、等待的顺序。这时我们可以同时使⽤ Obj.wait()、Obj.notify()与synchronized(Obj)来实现这个⽬标。线程中持有上⼀个线程类的对象锁以及自⼰的锁由于这种依赖关系该线程执⾏需要等待上个对象释放锁从⽽ 保证类线程执⾏的顺序。b. 通常情况下wait是线程在获取对象锁后主动释放对象锁同时本线程休眠直到有其它线程调⽤对象的notify()唤醒该线程才能继续获取对象锁并继续执⾏。⽽notify()则是对等待对象锁的线程的唤醒操作。但值得注意的是notify()调⽤后并不是⻢上就释放对象锁⽽是在相应的synchronized(){}语句块执⾏结束。释放对象锁后JVM会在执⾏wait()等待对象锁的线程中随机选取⼀线程赋予其对象锁唤醒线程继续执⾏。53 可以运⾏时kill掉⼀个线程吗a. 不可以线程有5种状态新建new、可运⾏runnable、运⾏中running、阻塞block、死亡dead。b. 只有当线程run⽅法或者主线程main⽅法结束⼜或者抛出异常时线程才会结束⽣命周期。54 关于synchronized在某个对象的所有synchronized⽅法中,在某个时刻只能有⼀个唯⼀的⼀个线程去访问这些synchronized⽅法如果⼀个⽅法是synchronized⽅法,那么该synchronized关键字表示给当前对象上锁(即this)相当于synchronized(this){}如果⼀个synchronized⽅法是static的,那么该synchronized表示给当前对象所对应的class对象上锁(每个类不管⽣成多少对象,其对应的class对象只有⼀个)55 分步式锁,程序数据库中死锁机制及解决⽅案基本原理⽤⼀个状态值表示锁对锁的占⽤和释放通过状态值来标识。三种分布式锁第一种Zookeeper基于zookeeper瞬时有序节点实现的分布式锁其主要逻辑如下。⼤致思想即为每个客户端对某个功能加锁时在zookeeper上的与该功能对应的指定节点的⽬录下⽣成⼀个唯⼀的瞬时有序节点。判断是否获取锁的⽅式很简单只需要判断有序节点中序号最⼩的⼀个。当释放锁的时候只需将这个瞬时节点删除即可。同时其可以避免服务宕机导致的锁⽆法释放⽽产⽣的死锁问题【优点】锁安全性⾼zk可持久化且能实时监听获取锁的客户端状态。⼀旦客户端宕机则瞬时节点随之消失zk因⽽能第⼀时间释放锁。这也省去了⽤分布式缓存实现锁的过程中需要加⼊超时时间判断的这⼀逻辑。【缺点】性能开销⽐较⾼。因为其需要动态产⽣、销毁瞬时节点来实现锁功能。所以不太适合直接提供给⾼并发的场景使⽤。【实现】可以直接采⽤zookeeper第三⽅库curator即可⽅便地实现分布式锁。【适⽤场景】对可靠性要求⾮常⾼且并发程度不⾼的场景下使⽤。如核⼼数据的定时全量/增量同步等。)第二种memcachedmemcached带有add函数利⽤add函数的特性即可实现分布式锁。add和set的区别在于如果多线程并发set则每个set都会成功但最后存储的值以最后的set的线程为准。⽽add的话则相反add会添加第⼀个到达的值并返回true后续的添加则都会返回false。利⽤该点即可很轻松地实现分布式锁。【优点】并发⾼效【缺点】 memcached采⽤列⼊LRU置换策略所以如果内存不够可能导致缓存中的锁信息丢失。memcached⽆法持久化⼀旦重启将导致信息丢失。【使⽤场景】⾼并发场景。需要 1)加上超时时间避免死锁; 2)提供⾜够⽀撑锁服务的内存空间; 3)稳定的集群化管理。第三种Redisredis分布式锁即可以结合zk分布式锁锁⾼度安全和memcached并发场景下效率很好的优点其实现⽅式和memcached类似采⽤setnx即可实现。需要注意的是这⾥的redis也需要设置超时时间以避免死锁。可以利⽤jedis客户端实现。数据库死锁机制和解决⽅案死锁死锁是指两个或者两个以上的事务在执⾏过程中因争夺锁资源⽽造成的⼀种互相等待的现象。处理机制解决死锁最有⽤最简单的⽅法是不要有等待将任何等待都转化为回滚并且事务重新开始。但是有可能影响并发性能。------超时回滚innodb_lock_wait_time设置超时时间------wait-for-graph⽅法跟超时回滚⽐起来这是⼀种更加主动的死锁检测⽅式。InnoDB引擎也采⽤这种⽅式。56 spring单例为什么没有安全问题(ThreadLocal)1、ThreadLocalspring使⽤ThreadLocal解决线程安全问题ThreadLocal会为每⼀个线程提供⼀个独⽴的变量副本从⽽隔离了多个线程对数据的访问冲突。因为每⼀个线程都拥有自⼰的变量副本从⽽也就没有必要对该变量进⾏同步了。ThreadLocal提供了线程安全的共享对象在编写多线程代码时可以把不安全的变量封装进ThreadLocal。概括起来说对于多线程资源共享的问题同步机制采⽤了“以时间换空间”的⽅式⽽ThreadLocal采⽤了“以空间换时间”的⽅式。前者仅提供⼀份变量让不同的线程排队访问⽽后者为每⼀个线程都提供了⼀份变量因此可以同时访问⽽互不影响。在很多情况下ThreadLocal⽐直接使⽤synchronized同步机制解决线程安全问题更简单更⽅便且结果程序拥有更⾼的并发性。2、单例⽆状态的Bean(⽆状态就是⼀次操作不能保存数据。⽆状态对象(Stateless Bean)就是没有实例变量的对象不能保存数据是不变类是线程安全的。)适合⽤不变模式技术就是单例模式这样可以共享实例提⾼性能。57 线程池原理使⽤场景假设⼀个服务器完成⼀项任务所需时间为T1-创建线程时间T2-在线程中执⾏任务的时间T3-销毁线程时间。如果T1T3远⼤于T2则可以使⽤线程池以提⾼服务器性能组成线程池管理器ThreadPool⽤于创建并管理线程池包括 创建线程池销毁线程池添加新任务⼯作线程PoolWorker线程池中线程在没有任务时处于等待状态可以循环的执⾏任务任务接⼝Task每个任务必须实现的接⼝以供⼯作线程调度任务的执⾏它主要规定了任务的⼊⼝任务执⾏完后的收尾⼯作任务的执⾏状态等任务队列taskQueue⽤于存放没有处理的任务。提供⼀种缓冲机制。原理线程池技术正是关注如何缩短或调整T1,T3时间的技术从⽽提⾼服务器程序性能的。它把T1T3分别安排在服务器程序的启动和结束的时间段或者⼀些空闲的时间段这样在服务器程序处理客户请求时不会有T1T3的开销了。⼯作流程1、线程池刚创建时⾥⾯没有⼀个线程(也可以设置参数prestartAllCoreThreads启动预期数量主线程)。任务队列是作为参数传进来的。不过就算队列⾥⾯有任务线程池也不会⻢上执⾏它们。2、当调⽤ execute() ⽅法添加⼀个任务时线程池会做如下判断如果正在运⾏的线程数量⼩于 corePoolSize那么⻢上创建线程运⾏这个任务如果正在运⾏的线程数量⼤于或等于 corePoolSize那么将这个任务放⼊队列如果这时候队列满了⽽且正在运⾏的线程数量⼩于 maximumPoolSize那么还是要创建⾮核⼼线程⽴刻运⾏这个任务如果队列满了⽽且正在运⾏的线程数量⼤于或等于 maximumPoolSize那么线程池会抛出异常RejectExecutionException。3、当⼀个线程完成任务时它会从队列中取下⼀个任务来执⾏。4、当⼀个线程⽆事可做超过⼀定的时间keepAliveTime时线程池会判断如果当前运⾏的线程数⼤于corePoolSize那么这个线程就被停掉。所以线程池的所有任务完成后它最终会收缩到 corePoolSize 的⼤⼩。58 java锁多个对象例如在银⾏系统转账时需要锁定两个账户这个时候顺序使⽤两个synchronized可能存在死锁的情况59 java线程如何启动1、继承Thread类2、实现Runnable接⼝3、直接在函数体内⽐较1、实现Runnable接⼝优势1适合多个相同的程序代码的线程去处理同⼀个资源2可以避免java中的单继承的限制3增加程序的健壮性代码可以被多个线程共享代码和数据独⽴。2、继承Thread类优势1可以将线程类抽象出来当需要使⽤抽象⼯⼚模式设计时。2多线程同步3、在函数体使⽤优势1⽆需继承thread或者实现Runnable缩⼩作⽤域。60 java中加锁的⽅式有哪些,如何实现怎么个写法?1、java中有两种锁⼀种是⽅法锁或者对象锁(在⾮静态⽅法或者代码块上加锁)第⼆种是类锁(在静态⽅法或者class上加锁)2、注意其他线程可以访问未加锁的⽅法和代码synchronized同时修饰静态⽅法和实例⽅法但是运⾏结果是交替进⾏的这证明了类锁和对象锁是两个不⼀样的锁控制着不同的区域它们是互不⼲扰的。61 如何保证数据不丢失1、使⽤消息队列消息持久化2、添加标志位未处理 0处理中 1已处理 2。定时处理。62、ThreadLocal为什么会发⽣内存泄漏1、OOM实现ThreadLocal的实现是这样的每个Thread 维护⼀个 ThreadLocalMap 映射表这个映射表的 key 是 ThreadLocal实例本身value 是真正需要存储的 Object。2、也就是说 ThreadLocal 本身并不存储值它只是作为⼀个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线表示 ThreadLocalMap 是使⽤ ThreadLocal 的弱引⽤作为 Key 的弱引⽤的对象在 GC 时会被回收。ThreadLocalMap使⽤ThreadLocal的弱引⽤作为key如果⼀个ThreadLocal没有外部强引⽤来引⽤它那么系统 GC的时候这个ThreadLocal势必会被回收这样⼀来ThreadLocalMap中就会出现key为null的Entry就没有办法访问这些key为null的Entry的value如果当前线程再迟迟不结束的话这些key为null的Entry的value就会⼀直存在⼀条强引⽤链Thread Ref - Thread - ThreaLocalMap - Entry - value永远⽆法回收造成内存泄漏。3、预防办法在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap⾥所有key为null的value。但是这些被动的预防措施并不能保证不会内存泄漏1使⽤static的ThreadLocal延⻓了ThreadLocal的⽣命周期可能导致内存泄漏。2分配使⽤了ThreadLocal⼜不再调⽤get(),set(),remove()⽅法那么就会导致内存泄漏因为这块内存⼀直存在。63 jdk8中对ConcurrentHashmap的改进Java 7为实现并⾏访问引⼊了Segment这⼀结构实现了分段锁理论上最⼤并发度与Segment个数相等。Java 8为进⼀步提⾼并发性摒弃了分段锁的⽅案⽽是直接使⽤⼀个⼤的数组。同时为了提⾼哈希碰撞下的寻址性能Java 8在链表⻓度超过⼀定阈值8时将链表寻址时间复杂度为O(N)转换为红⿊树寻址时间复杂度为O(long(N))。64 concurrent包下有哪些类ConcurrentHashMap、Future、FutureTask、AtomicInteger…65 线程a,b,c,d运⾏任务怎么保证当a,b,c线程执⾏完再执⾏d线程?1、CountDownLatch类⼀个同步辅助类常⽤于某个条件发⽣后才能执⾏后续进程。给定计数初始化CountDownLatch调⽤countDown(⽅法在计数到达零之前await⽅法⼀直受阻塞。重要⽅法为countdown()与await()2、join⽅法将线程B加⼊到线程A的尾部当A执⾏完后B才执⾏。3、notify、wait⽅法Java中的唤醒与等待⽅法关键为synchronized代码块参数线程间应相同也常⽤Object作为参数。66 ⾼并发系统如何做性能优化如何防⽌库存超卖1、⾼并发系统性能优化优化程序优化服务配置优化系统配置尽量使⽤缓存包括⽤户缓存信息缓存等多花点内存来做缓存可以⼤量减少与数据库的交互提⾼性能。⽤jprofiler等⼯具找出性能瓶颈减少额外的开销。优化数据库查询语句减少直接使⽤hibernate等⼯具的直接⽣成语句仅耗时较⻓的查询做优化。优化数据库结构多做索引提⾼查询效率。统计的功能尽量做缓存或按每天⼀统计或定时统计相关报表避免需要时进⾏统计的功能。能使⽤静态⻚⾯的地⽅尽量使⽤减少容器的解析尽量将动态内容⽣成静态html来显示。解决以上问题后使⽤服务器集群来解决单台的瓶颈问题。2.防⽌库存超卖悲观锁在更新库存期间加锁不允许其它线程修改数据库锁select xxx for update分布式锁乐观锁使⽤带版本号的更新。每个线程都可以并发修改但在并发时只有⼀个线程会修改成功其它会返回失败。redis watch监视键值对作⽤时如果事务提交exec时发现监视的监视对发⽣变化事务将被取消。消息队列通过 FIFO 队列使修改库存的操作串⾏化。总结总的来说不能把压⼒放在数据库上所以使⽤ “select xxx for update” 的⽅式在⾼并发的场景下是不可⾏的。FIFO 同步队列的⽅式可以结合库存限制队列⻓但是在库存较多的场景下⼜不太适⽤。所以相对来说我会倾向于选择乐观锁 / 缓存锁 / 分布式锁的⽅式。