当前位置: 首页 > news >正文

江苏建设类高级工程师在那个网站公示信誉好的龙岗网站制作

江苏建设类高级工程师在那个网站公示,信誉好的龙岗网站制作,白度指数,seo关键词优化经验技巧1、#x1f4aa; 目录1、#x1f4aa;1、说说List, Set, Queue, Map 四者的区别1.1、List1.2、Set1.3、Map2、如何选用集合4、线程安全的集合有哪些#xff1f;线程不安全的呢#xff1f;3、为什么需要使用集合4、comparable和Comparator的区别5、无序性和不可重复性的含义…1、 目录1、1、说说List, Set, Queue, Map 四者的区别1.1、List1.2、Set1.3、Map2、如何选用集合4、线程安全的集合有哪些线程不安全的呢3、为什么需要使用集合4、comparable和Comparator的区别5、无序性和不可重复性的含义6、比较HashSet、LinkedHashSet和TreeSet的异同7、HashMap和Hashtable的区别8、HashSet和HashMap的区别9、HashMap和TreeMap的区别10、HashSet如何检查重复11、线程和进程有什么区别12、有几种方式创建多线程13、线程的生命周期/线程有哪些状态14、为什么要使用多线程呢15、使用多线程可能带来什么问题16、说说线程的生命周期和状态17、什么是线程死锁如何避免线程死锁18、为什么我们调用start()方法时会执行run()方法为什么我们不能直接调用Thread类的run方法19、谈一下你对 volatile 关键字的理解20、sleep()方法和 wait()方法21、yield() 和 join() 方法的区别22、线程阻塞的三种情况23、线程死亡的三种方式24、如何使用synchronized25、Synchronized的作用有哪些26、synchronized和volatile的区别是什么27、synchronized和Lock有什么区别28、构造方法可以用 synchronized 修饰吗29、ThreadLocal有什么用30、知道ThreadLocal内存泄漏问题吗31、为什么要用线程池32、如何创建线程池33、线程池执行任务的流程34、核心线程数和最大线程数有什么区别35、线程池的饱和策略有哪些36、如何设置线程池的大小37、Executor和Executors的区别38、谈谈乐观锁和悲观锁39、AQS是什么40、AQS的原理是什么41、AQS组件了解吗42、Semaphore有什么用43、CountDownLatch有什么用44、CountDownLatch的原理是什么45、用过CountDownLatch么什么场景下用的46、CyclicBarrier有什么用47、CyclicBarrier的原理是什么48、Iterator怎么使用有什么特点49、Collection和Collections有什么区别50、在Java程序中怎么保证多线程的运行安全51、Java线程同步的几种方式52、简单说下对 Java 中的原子类的理解53、谈谈对CopyOnWriteArrayList的理解54、说下你对 Java 内存模型的理解 1、说说List, Set, Queue, Map 四者的区别 Collection是一个接口Collections是一个工具类Map不是Collection的子接口。 List(对付顺序的好帮手): 存储的元素是有序的、可重复的。可直接根据元素的索引来访问Set(注重独一无二的性质): 存储的元素是无序的、不可重复的只能根据元素本身来访问Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序存储的元素是有序的、可重复的。Map(用 key 来搜索的专家): 使用键值对key-value存储key 是无序的、不可重复的value 是无序的、可重复的每个键最多映射到一个值。 Java 集合 也叫作容器主要是由两大接口派生而来一个是 Collection接口主要用于存放单一元素。另一个是 Map 接口主要用于存放键值对。对于Collection 接口下面又有三个主要的子接口List、Set 和 Queue 1.1、List ArrayList 基于动态数组实现支持随机访问。Vector和 ArrayList 类似但它是线程安全的LinkedList 基于双向链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素。不仅如此LinkedList 还可以用作栈、队列和双向队列。 1.2、Set HashSet(无序唯一): 基于哈希表实现支持快速查找但不支持有序性操作。并且失去了元素的插入顺序信息也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的 LinkedHashSet: LinkedHashSet 是 HashSet 的子类并且其内部是通过 LinkedHashMap 来实现的使用双向链表维护元素的插入顺序 TreeSet(有序唯一): 基于红黑树实现支持有序性操作例如根据一个范围查找元素的操作。但是查找效率不如 HashSet 1.3、Map HashMap JDK1.8 之前 HashMap 由数组链表组成的数组是 HashMap 的主体链表则是主要为了解决哈希冲突而存在的“拉链法”解决冲突。JDK1.8 以后在解决哈希冲突时有了较大的变化当链表长度大于阈值默认为 8时将链表转化为红黑树以减少搜索时间LinkedHashMap LinkedHashMap 继承自 HashMap所以它的底层仍然是基于拉链式散列结构。即由数组和链表或红黑树组成Hashtable和 HashMap 类似但它是线程安全的这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全并且 ConcurrentHashMap 的效率会更高因为 ConcurrentHashMap 引入了分段锁。TreeMap 红黑树 2、如何选用集合 需要根据键值获取到元素值时就选用 Map 接口下的集合 需要排序时选择 TreeMap不需要排序时就选择 HashMap需要保证线程安全就选用 ConcurrentHashMap 当我们只需要存放元素值时就选择实现Collection 接口的集合 需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSet 或 HashSet不需要保证元素唯一就选择实现 List 接口的比如 ArrayList 或 LinkedList 4、线程安全的集合有哪些线程不安全的呢 线程安全的 Hashtable比HashMap多了个线程安全。ConcurrentHashMap:是一种高效但是线程安全的集合。Vector比Arraylist多了个同步化机制。Stack栈也是线程安全的继承于Vector。 线性不安全的 HashMapArraylistLinkedListHashSetTreeSetTreeMap 3、为什么需要使用集合 当我们需要保存一组类型相同的数据的时候我们应该是用一个容器来保存这个容器就是数组但是使用数组存储对象具有一定的弊端 因为我们在实际开发中存储的数据的类型是多种多样的于是就出现了“集合”集合同样也是用来存储多个数据的。 数组的缺点是一旦声明之后长度就不可变了同时声明数组时的数据类型也决定了该数组存储的数据的类型而且数组存储的数据是有序的、可重复的特点单一。 但是集合提高了数据存储的灵活性Java 集合不仅可以用来存储不同类型不同数量的对象还可以保存具有映射关系的数据 4、comparable和Comparator的区别 comparable 接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序 一般我们需要对一个集合使用自定义排序时我们就要重写compareTo()方法或compare()方法当我们需要对某一个集合实现两种排序方式比如一个 song 对象中的歌名和歌手名分别采用一种排序方法的话我们可以重写compareTo()方法和使用自制的Comparator方法或者以两个 Comparator 来实现歌名排序和歌星名排序。 一个类实现了 Comparable 接口意味着该类的对象可以直接进行比较排序但比较排序的方式只有一种很单一一个类如果想要保持原样又需要进行不同方式的比较排序就可以定制比较器实现 Comparator 接口Comparable 更多的像一个内部比较器而 Comparator 更多的像一个外部比较器体现了一种策略模式耦合性较低如果对象的排序需要基于自然顺序请选择 Comparable如果需要按照对象的不同属性进行排序请选择 Comparator。 5、无序性和不可重复性的含义 无序性不等于随机性 无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 而是根据数据的哈希值决定的。不可重复性是指添加的元素按照 equals() 判断时 返回 false需要同时重写 equals() 方法和 hashCode() 方法。 6、比较HashSet、LinkedHashSet和TreeSet的异同 HashSet、LinkedHashSet 和 TreeSet 都是 Set 接口的实现类都能保证元素唯一并且都不是线程安全的。HashSet、LinkedHashSet 和 TreeSet 的主要区别在于底层数据结构不同。 HashSet 的底层数据结构是哈希表基于 HashMap 实现LinkedHashSet 的底层数据结构是链表和哈希表元素的插入和取出顺序满足 FIFO。TreeSet 底层数据结构是红黑树元素是有序的排序的方式有自然排序和定制排序 底层数据结构不同又导致这三者的应用场景不同 HashSet 用于不需要保证元素插入和取出顺序的场景LinkedHashSet 用于保证元素的插入和取出顺序满足 FIFO 的场景TreeSet 用于支持对元素自定义排序规则的场景 7、HashMap和Hashtable的区别 HashTable 继承 Dictionary 类而 HashMap 是继承 AbstractMap。 线程是否安全HashMap 是非线程安全的Hashtable 是线程安全的,因为 Hashtable 内部的方法基本都经过synchronized 修饰。如果你要保证线程安全的话就使用 ConcurrentHashMap 吧效率 因为线程安全的问题HashMap 要比 Hashtable 效率高一点。另外Hashtable 基本被淘汰不要在代码中使用它对 Null key 和 Null value 的支持HashMap 可以存储 null 的 key 和 value但 null 作为键只能有一个null 作为值可以有多个Hashtable 不允许有 null 键和 null 值 HashMap的 size 为什么必须是 2 的整数次方 这样做总是能够保证 HashMap 的底层数组长度为 2 的 n 次方这样在计算机中转化二进制和进行与操作可以增加速度这是 HashMap 在速度上的一个优化 HashMap 的 get 方法能否判断某个元素是否在 map 中 HashMap 的 get 函数的返回值不能判断一个 key 是否包含在 map 中因为 get 返回 null 有可能是不包含该 key也有可能该 key 对应的 value 为 null。因为 HashMap 中允许 key 为 null也允许 value 为 null。 LinkedHashMap的实现原理 LinkedHashMap 是基于 HashMap 实现的但是多了header、before、after三个属性有了这三个属性就能组成一个双向链表来实现按插入顺序或访问顺序排序其迭代顺序默认为插入顺序。 8、HashSet和HashMap的区别 HashMapHashSet实现了Map接口实现Set接口存储键值对仅存储对象调用 put() 向 map 中添加元素调用 add() 方法向 set 中添加元素HashMap使用键key来计算 HashcodeHashSet使用成员对象来计算 hashcode再用 equals() 方法来判断对象的相等性HashMap相对于HashSet较快因为它是使用唯一的键获取对象 HashSet的底层其实就是HashMap只不过我们HashSet是实现了Set接口并且把数据作为K值而V值一直使用一个相同的虚值来保存。 9、HashMap和TreeMap的区别 TreeMap 和HashMap 都继承自AbstractMap 但是需要注意的是TreeMap它还实现了NavigableMap接口和SortedMap 接口。 TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力 为什么HashMap中String、Integer这样的包装类适合作为key String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性能够有效的减少Hash碰撞的几率 都是final类型即不可变性保证key的不可更改性不会存在同一对象获取hash值不同的情况内部已重写了equals()、hashCode()等方法遵守了HashMap内部的规范不容易出现Hash值计算错误的情况 10、HashSet如何检查重复 当你把对象加入HashSet时HashSet 会先计算对象的hashcode值来判断对象加入的位置同时也会与其他加入的对象的 hashcode 值作比较如果没有相符的 hashcodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同HashSet 就不会让加入操作成功。 HashSet的实现原理 HashSet 的实现是依赖于 HashMap 的HashSet 的值都是存储在 HashMap 中的。在 HashSet 的构造方法中会初始化一个 HashMap 对象HashSet 不允许值重复。因此HashSet 的值是作为 HashMap 的 key 存储在 HashMap 中的当存储的值已经存在时返回 false。 11、线程和进程有什么区别 通常一个进程都有若干个线程至少包含一个线程。 根本区别进程是操作系统资源分配的基本单位而线程是处理器任务调度和执行的基本单位资源开销每个进程都有独立的代码和数据空间程序上下文程序之间的切换会有较大的开销线程可以看做轻量级的进程同一类线程共享代码和数据空间每个线程都有自己独立的运行栈和程序计数器PC线程之间切换的开销小包含关系如果一个进程内有多个线程则执行过程不是一条线的而是多条线线程共同完成的线程是进程的一部分所以线程也被称为轻权进程或者轻量级进程内存分配同一进程的线程共享本进程的地址空间和资源而进程之间的地址空间和资源是相互独立的影响关系一个进程崩溃后在保护模式下不会对其他进程产生影响但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮执行过程每个独立的进程有程序运行的入口. 顺序执行序列和程序出口。但是线程不能独立执行必须依存在应用程序中由应用程序提供多个线程执行控制两者均可并发执行 12、有几种方式创建多线程 第一种方式继承 Thread 类并重写该类的 run() 方法然后创建线程对象调用线程对象的 start() 方法启动线程。 第二种方式实现 Runnable 接口重写该接口的 run() 方法然后创建实现类的实例并将此实例作为 Thread 的 target 来创建线程对象调用线程对象的 start() 方法启动线程。 第三种方式通过 Callable 和 Future 创建线程。实现Callable接口重写 call() 方法创建一个Callable的线程任务对象把Callable的线程任务对象包装成一个未来任务对象把未来任务对象包装成线程对象调用线程的start()方法启动线程 第四种方式通过线程池创建线程 Thread和Runnable的区别 总结 实现Runnable接口比继承Thread类所具有的优势 线程类只是实现了Runnable接口或Callable接口还可以继承其他类。在这种方式下多个线程可以共享同一个target对象所以非常适合多个相同线程来处理同一份资源的情况从而可以将CPU. 代码和数据分开形成清晰的模型较好地体现了面向对象的思想。劣势编程稍微复杂如果要访问当前线程则必须使用Thread.currentThread()方法 Runnable 和 Callable 有什么区别 Callable规定重写的方法是call()Runnable规定重写的方法是run()Callable的任务执行后可返回值而Runnable的任务是不能返回值的Call方法可以抛出异常run方法不可以运行Callable任务可以拿到一个Future对象表示异步计算的结果。它提供了检查计算是否完成的方法以等待计算的完成并检索计算的结果。通过Future对象可以了解任务执行情况可取消任务的执行还可获取执行结果。 13、线程的生命周期/线程有哪些状态 线程通常有五种状态创建、就绪、运行、阻塞和死亡状态 阻塞情况又分为三种 等待阻塞运行的线程执行 wait 方法该线程会释放占用的所有资源JVM会把该线程放入 等待池中进入这个状态后是不能自动唤醒的必须依靠其他线程调用 notify 或者 notifyAll 方法才能被唤醒wait 是object 类的方法。同步阻塞运行的线程在获取对象的同步锁时若该同步锁被别的线程占用则 JVM 会把该线程放入 锁池中。其他阻塞运行的线程执行 sleep 或者 join 方法或者发出了 I/O请求时JVM 会把该线程设置为阻塞状态。当 sleep 状态超时、join 等待线程终止或者超时、或者 I/O处理完毕时线程重新转入就绪状态。 sleep 是 Thread 类的方法。 新建状态(New) 新创建了一个线程对象就绪状态(runnable) 线程对象创建后其他线程调用了该对象的 start 方法。该状态的线程位于可运行线程池中变得可运行等待获取CPU的使用权运行状态(Running) 就绪状态的线程获取了 CPU执行程序代码阻塞状态(Blocked)阻塞状态是线程因为某种原因放弃CPU使用权暂时停止运行。直到线程进入就绪状态才有机会转到运行状态死亡状态(Dead) 线程执行完了或者因异常退出了 run 方法该线程结束生命周期。 14、为什么要使用多线程呢 先从总体上来说 从计算机底层来说 线程可以比作是轻量级的进程是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外多核 CPU 时代意味着多个线程可以同时运行这减少了线程上下文切换的开销。从当代互联网发展趋势来说 现在的系统动不动就要求百万级甚至千万级的并发量而多线程并发编程正是开发高并发系统的基础利用好多线程机制可以大大提高系统整体的并发能力以及性能。 15、使用多线程可能带来什么问题 并发编程的目的就是为了能提高程序的执行效率提高程序运行速度但是并发编程并不总是能提高程序运行速度的而且并发编程可能会遇到很多问题比如内存泄漏、死锁、线程不安全等等。 16、说说线程的生命周期和状态 线程有6种状态 NEW: 初始状态线程被创建出来但没有被调用 start()RUNNABLE: 运行状态线程被调用了 start()等待运行的状态BLOCKED 阻塞状态需要等待锁释放WAITING等待状态表示该线程需要等待其他线程做出一些特定动作通知或中断TIME_WAITING超时等待状态可以在指定的时间后自行返回而不是像 WAITING 那样一直等待TERMINATED终止状态表示该线程已经运行完毕。 线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。线程创建之后它将处于 NEW新建 状态调用 start() 方法后开始运行线程这时候处于 READY可运行 状态。可运行状态的线程获得了 CPU 时间片timeslice后就处于 RUNNING运行 状态。当线程执行 wait()方法之后线程进入 WAITING等待 状态。通过 sleeplong millis方法或 waitlong millis方法可以将线程置于 TIMED_WAITING 状态。当线程进入 synchronized 方法/块或者调用 wait 后被 notify重新进入 synchronized 方法/块但是锁被其它线程占有这个时候线程就会进入 BLOCKED阻塞 状态。线程在执行完了 run()方法之后将会进入到 TERMINATED终止 状态 17、什么是线程死锁如何避免线程死锁 线程死锁描述的是这样一种情况多个线程同时被阻塞它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞因此程序不可能正常终止。 死锁产生的四个必要条件 互斥条件该资源任意一个时刻只由一个线程占用请求与保持条件一个线程因请求资源而阻塞时对已获得的资源保持不放不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺只有自己使用完毕后才释放资源循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系 如何预防死锁 破坏死锁的产生的必要条件即可 破坏请求与保持条件 一次性申请所有的资源破坏不剥夺条件 占用部分资源的线程进一步申请其他资源时如果申请不到可以主动释放它占有的资源破坏循环等待条件 靠按序申请资源来预防 **如何避免死锁**避免死锁就是在资源分配时借助于算法比如银行家算法对资源分配进行计算评估使其进入安全状态。 锁排序法必须回答出来的点 指定获取锁的顺序比如某个线程只有获得A锁和B锁才能对某资源进行操作在多线程条件下如何避免死锁 通过指定锁的获取顺序比如规定只有获得A锁的线程才有资格获取B锁按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方法。 18、为什么我们调用start()方法时会执行run()方法为什么我们不能直接调用Thread类的run方法 new 一个 Thread线程进入了新建状态。调用 start()方法会启动一个线程并使线程进入了就绪状态当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作然后自动执行 run() 方法的内容这是真正的多线程工作。 但是直接执行 run() 方法会把 run() 方法当成一个 main 线程下的普通方法去执行并不会在某个线程中执行它所以这并不是多线程工作。 总结 调用 start() 方法方可启动线程并使线程进入就绪状态直接执行 run() 方法的话不会以多线程的方式执行run 方法只是thread 的一个普通方法调用还是在主线程里执行。 线程的 run() 和 start() 有什么区别 每个线程都是通过某个特定 Thread 对象所对应的方法 run() 来完成其操作的方法 run() 称为线程体。通过调用 Thread 类的 start() 方法来启动一个线程start() 方法来启动一个线程真正实现了多线程运行。 19、谈一下你对 volatile 关键字的理解 volatile 关键字是用来保证有序性和可见性的。这跟 Java 内存模型有关。我们所写的代码不一定是按照我们自己书写的顺序来执行的编译器会做重排序CPU 也会做重排序的这样做是为了减少流水线阻塞提高 CPU 的执行效率。这就需要有一定的顺序和规则来保证不然程序员自己写的代码都不知道对不对了 被 volatile 修饰的共享变量具有以下两点特性 保证了不同线程对该变量操作的内存可见性禁止指令重排序 在 Java 中volatile 关键字可以保证变量的可见性如果我们将变量声明为 volatile 这就指示 JVM这个变量是共享且不稳定的每次使用它都到主存中进行读取 volatile 关键字能保证数据的可见性但不能保证数据的原子性。synchronized 关键字两者都能保证 20、sleep()方法和 wait()方法 首先明白两个概念锁池和等待池 锁池所有需要竞争同步锁的线程都会放在锁池当中比如当前对象的锁已经被其中一个线程得到则其他线程需要在这个锁池进行等待当前面的线程释放同步锁后锁池中的线程去竞争同步锁当某个线程得到后会进入就绪队列进行等待cpu资源分配。等待池 当我们调用 wait() 方法后线程会放到等待池中等待池的线程是不会去竞争同步锁。只有调用了 notify() 或 notifyAll() 方法后等待池的线程才会开始去竞争锁 notify() 是随机从等待池选出一个线程放到锁池而 notifyAll() 方法是将等待池的所有线程放到锁池当中。 区别 sleep方法是Thread类的静态方法当前线程将睡眠n毫秒线程进入阻塞状态。当睡眠时间到了会解除阻塞进入可运行状态等待CPU的到来。睡眠不释放锁如果有的话。wait方法是Object的方法必须与synchronized关键字一起使用线程进入阻塞状态当notify或者notifyall被调用后会解除阻塞。但是只有重新占用互斥锁之后才会进入可运行状态。睡眠时会释放互斥锁。sleep 方法没有释放锁而 wait 方法释放了锁 。sleep 通常被用于暂停执行Wait 通常被用于线程间交互/通信sleep() 方法执行完成后线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。wait() 方法被调用后线程不会自动苏醒需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法 相同 两者都可以暂停线程的执行。 21、yield() 和 join() 方法的区别 yield() 执行后线程直接进入就绪状态马上释放 cpu但是依然保留了cpu的执行资格所以有可能cpu下次进行线程调入还会让这个线程获取到执行权继续执行。就是让别的线程去抢cpu如果其他线程抢不到cpu那么仍然是这个线程执行join() 执行后线程进入阻塞状态例如在线程B调用线程A的 join() 那么线程 B会进入到阻塞队列直到线程A结束或中断线程 public class Hello {public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(new Runnable() {Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(22222);}});t1.start();// 调用 t1的join()方法,会将 main 线程进行阻塞t1.join();//所以会先打印t1的内容 22222,再打印main的内容 1111System.out.println(1111);} }22、线程阻塞的三种情况 当线程因为某种原因放弃 CPU 使用权后即让出了 CPU 时间片暂时就会停止运行直到线程进入可运行状态Runnable才有机会再次获得 CPU 时间片转入 RUNNING 状态。一般来讲阻塞的情况可以分为如下三种 等待阻塞RUNNING 状态的线程执行 Object.wait() 方法后JVM 会将线程放入等待序列同步阻塞RUNNING 状态的线程在获取对象的同步锁时若该 同步锁被其他线程占用则 JVM 将该线程放入锁池其他阻塞RUNNING 状态的线程执行 Thread.sleep(long ms) 或 Thread.join() 方法或发出 I/O 请求时JVM 会将该线程置为阻塞状态。当 sleep() 状态超时join() 等待线程终止或超时. 或者 I/O 处理完毕时线程重新转入可运行状态RUNNABLE 23、线程死亡的三种方式 正常结束run() 或者 call() 方法执行完成后线程正常结束异常结束线程抛出一个未捕获的 Exception 或 Error导致线程异常结束调用 stop()直接调用线程的 stop() 方法来结束该线程但是一般不推荐使用该种方式因为该方法通常容易导致死锁 24、如何使用synchronized synchronized 关键字的使用方式主要有下面 3 种 修饰实例方法修饰静态方法修饰代码块 修饰实例方法(锁当前对象实例) 给当前对象实例加锁进入同步代码前要获得 当前对象实例的锁 synchronized void method() {//业务代码 }修饰静态方法(锁当前类) 给当前类加锁会作用于类的所有对象实例 进入同步代码前要获得 当前 class 的锁。这是因为静态成员不属于任何一个实例对象归整个类所有不依赖于类的特定实例被类的所有实例共享。 synchronized static void method() {//业务代码 }静态 synchronized 方法和非静态 synchronized 方法之间的调用互斥么不互斥如果一个线程 A 调用一个实例对象的非静态 synchronized 方法而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法是允许的不会发生互斥现象因为访问静态 synchronized 方法占用的锁是当前类的锁而访问非静态 synchronized 方法占用的锁是当前实例对象锁。 修饰代码块(锁当前对象/类)对括号里指定的对象/类加锁 synchronized(object) 表示进入同步代码库前要获得 给定对象的锁。synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁 synchronized(this) {//业务代码 }总结 synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁synchronized 关键字加到实例方法上是给对象实例上锁尽量不要使用 synchronized(String a) 因为 JVM 中字符串常量池具有缓存功能 25、Synchronized的作用有哪些 原子性确保线程互斥的访问同步代码可见性保证共享变量的修改能够及时可见有序性有效解决重排序问题 26、synchronized和volatile的区别是什么 volatile 解决的是内存可见性问题会使得所有对 volatile 变量的读写都直接写入主存即 保证了变量的可见性。synchronized 既保证了原子性也保证了可见性。 区别 volatile 本质是在告诉 JVM 当前变量在寄存器工作内存中的值是不确定的需要从主存中读取 synchronized 则是锁定当前变量只有当前线程可以访问该变量其他线程被阻塞住volatile 仅能使用在变量级别synchronized 则可以使用在 变量. 方法. 和类级别的volatile 仅能实现变量的修改可见性不能保证原子性而synchronized 则可以 保证变量的修改可见性和原子性volatile 不会造成线程的阻塞synchronized 可能会造成线程的阻塞volatile 标记的变量不会被编译器优化synchronized 标记的变量可以被编译器优化 27、synchronized和Lock有什么区别 synchronized 可以给类. 方法. 代码块加锁而 lock 只能给代码块加锁synchronized 不需要手动获取锁和释放锁使用简单发生异常会自动释放锁不会造成死锁而 lock 需要自己加锁和释放锁如果使用不当没有 unLock()去释放锁就会造成死锁。通过 Lock 可以知道有没有成功获取锁而 synchronized 却无法办到 28、构造方法可以用 synchronized 修饰吗 **构造方法不能使用 synchronized 关键字修饰。**构造方法本身就属于线程安全的不存在同步的构造方法一说 29、ThreadLocal有什么用 通常情况下我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢 ThreadLocal即线程本地变量。如果你创建了一个ThreadLocal变量那么访问这个变量的每个线程都会有这个变量的一个本地拷贝多个线程操作这个变量的时候实际是操作自己本地内存里面的变量从而起到线程隔离的作用避免了线程安全问题 Java 的 Web 项目大部分都是基于 Tomcat。每次访问都是一个新的线程每一个线程都独享一个 ThreadLocal我们可以在接收请求的时候 set 特定内容在需要的时候 get 这个值ThreadLocal 提供 get 和 set 方法为每一个使用这个变量的线程都保存有一份独立的副本 get() 方法是用来获取 ThreadLocal 在当前线程中保存的变量副本set() 用来设置当前线程中变量的副本remove() 用来移除当前线程中变量的副本initialValue() 是一个 protected 方法一般是用来在使用时进行重写的如果在没有 set 的时候就调用 get会调用 initialValue 方法初始化内容 在那些场景下会使用 ThreadLocal 数据库连接、处理数据库事务 每个线程都有一个连接对象副本不用担心造成线程安全问题进行回滚或者其他操作都不会受到干扰。 数据跨层传递 通过静态方法传递参数避免参数传递的麻烦。在调用 API 接口的时候传递了一些公共参数这些公共参数携带了一些设备信息是安卓还是 ios服务端接口根据不同的信息组装不同的格式数据返回给客户端。假定服务器端需要通过设备类型device来下发下载地址我们只要在返回数据的时候判断好是什么类型的客户端就好了。上面这种场景就可以将传进来的参数 device 设置到 ThreadLocal 中。用的时候取出来就行。避免了参数的层层传递 30、知道ThreadLocal内存泄漏问题吗 ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用弱引用只要垃圾回收机制一运行不管JVM的内存空间是否充足都会回收该对象占用的内存。弱引用比较容易被回收。因此如果ThreadLocalThreadLocalMap的Key被垃圾回收器回收了但是因为ThreadLocalMap生命周期和Thread是一样的它这时候如果不被回收就会出现这种情况ThreadLocalMap的key没了value还在这就会**「造成了内存泄漏问题」**。 如何**「解决内存泄漏问题」**使用完ThreadLocal后及时调用remove()方法释放内存空间。 31、为什么要用线程池 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控 JDK中提供了哪些并发容器 JDK 提供的这些容器大部分在 java.util.concurrent 包中 ConcurrentHashMap线程安全的 HashMapConcurrentLinkedQueue高效的并发队列使用链表实现。可以看做一个线程安全的 LinkedList这是一个非阻塞队列CopyOnWriteArrayList线程安全的 List在读多写少的场合性能非常好远远好于 VectorBlockingQueue这是一个接口JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列非常适合用于作为数据共享的通道ConcurrentSkipListMap跳表的实现。这是一个 Map使用跳表的数据结构进行快速查找 32、如何创建线程池 《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建而是通过 ThreadPoolExecutor 的方式这样的处理方式让写的同学更加明确线程池的运行规则规避资源耗尽的风险。 Executors创建线程池对象的弊端如下FixedThreadPool 和 SingleThreadExecutor 允许请求的队列长度为 Integer.MAX_VALUE可能堆积大量的请求从而导致 OOM内存用完。CachedThreadPool 允许创建的线程数量为Integer.MAX_VALUE 可能会创建大量线程从而导致 OOM。 方式一通过 Executor 框架的工具类 Executors 来实现我们可以创建三种类型的 ThreadPoolExecutor FixedThreadPool 创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程如果工作线程数量达到线程池初始的最大数则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时即线程池中没有可运行任务时它不会释放工作线程还会占用一定的系统资源。 SingleThreadExecutor 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池任务会被保存在一个任务队列中待线程空闲按先入先出的顺序执行队列中的任务。 CachedThreadPool 创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲线程若无可回收则新建线程。这种类型的线程池特点是如果长时间没有往线程池中提交任务即如果工作线程空闲了指定的时间(默认为1分钟)则该工作线程将自动终止。终止后如果你又提交了新的任务则线程池重新创建一个工作线程。在使用CachedThreadPool时一定要注意控制任务的数量否则由于大量线程同时运行很有会造成系统OOM 方式二通过 ThreadPoolExecutor 的构造方法实现 创建线程池的参数有哪些 corePoolSize 核心线程大小。线程池一直运行核心线程就不会停止。maximumPoolSize线程池最大数量线程池允许创建的最大线程数。如果队列满了并且已创建的线程数小于最大线程数则线程池会再创建新的线程执行任务。keepAliveTime线程活动保持时间线程池的工作线程空闲后保持存活的时间。所以如果任务很多并且每个任务执行的时间比较短可以调大时间提高线程的利用率。TimeUnit线程活动保持时间的单位workQueue任务队列用于保存等待执行的任务的阻塞队列。 线程池中的线程数一般怎么设置需要考虑哪些问题 主要考虑下面几个方面 线程池中线程执行任务的性质 计算密集型的任务比较占 cpu所以一般线程数设置的大小 等于或者略微大于 cpu 的核数但 IO 型任务主要时间消耗在 IO 等待上cpu 压力并不大所以线程数一般设置较大。 cpu 使用率 当线程数设置较大时会有如下几个问题第一线程的初始化切换销毁等操作会消耗不小的 cpu 资源使得 cpu 利用率一直维持在较高水平。第二线程数较大时任务会短时间迅速执行任务的集中执行也会给 cpu 造成较大的压力。第三 任务的集中支持会让 cpu 的使用率呈现锯齿状即短时间内 cpu 飙高然后迅速下降至闲置状态cpu 使用的不合理应该减小线程数让任务在队列等待使得 cpu 的使用率应该持续稳定在一个合理平均的数值范围。 内存使用率 线程数过多和队列的大小都会影响此项数据队列的大小应该通过前期计算线程池任务的条数来合理的设置队列的大小不宜过小让其不会溢出因为溢出会走拒绝策略多少会影响性能也会增加复杂度。 下游系统抗并发能力 多线程给下游系统造成的并发等于你设置的线程数如果访问的是下游系统的接口你就得考虑下游系统是否能抗的住这么多并发量不能把下游系统打挂了。 执行 execute() 方法和 submit() 方法的区别是什么呢 execute() 方法用于提交不需要返回值的任务所以无法判断任务是否被线程池执行成功与否submit() 方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象通过这个 Future 对象可以判断任务是否执行成功并且可以通过 Future 的 get() 方法来获取返回值get() 方法会阻塞当前线程直到任务完成而使用 get(long timeoutTimeUnit unit) 方法则会阻塞当前线程一段时间后立即返回这时候有可能任务没有执行完。 33、线程池执行任务的流程 线程池执行execute/submit方法向线程池添加任务当任务小于核心线程数corePoolSize线程池中可以创建新的线程。当任务大于核心线程数corePoolSize就向阻塞队列添加任务如果阻塞队列已满需要通过比较参数maximumPoolSize在线程池创建新的线程当线程数量大于maximumPoolSize说明当前设置线程池中线程已经处理不了了就会执行饱和策略。 34、核心线程数和最大线程数有什么区别 ThreadPoolExecutor 3 个最重要的参数 corePoolSize : 核心线程数定义了最小可以同时运行的线程数量。maximumPoolSize : 当队列中存放的任务达到队列容量的时候当前可以同时运行的线程数量变为最大线程数。workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数如果达到的话新任务就会被存放在队列中。 ThreadPoolExecutor其他常见参数: keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候如果这时没有新的任务提交核心线程外的线程不会立即销毁而是会等待直到等待的时间超过了 keepAliveTime才会被回收销毁 unit : keepAliveTime 参数的时间单位。 threadFactory :executor 创建新线程的时候会用到。 handler :饱和策略 35、线程池的饱和策略有哪些 如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时ThreadPoolTaskExecutor 定义一些策略: ThreadPoolExecutor.AbortPolicy 抛出 RejectedExecutionException来拒绝新任务的处理。 ThreadPoolExecutor.CallerRunsPolicy 调用执行自己的线程运行任务也就是直接在调用execute方法的线程中运行(run)被拒绝的任务如果执行程序已关闭则会丢弃该任务。因此这种策略会降低对于新任务提交速度影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话你可以选择这个策略。 ThreadPoolExecutor.DiscardPolicy 不处理新任务直接丢弃掉。 ThreadPoolExecutor.DiscardOldestPolicy 此策略将丢弃最早的未处理的任务请求 举个例子Spring 通过 ThreadPoolTaskExecutor 创建线程池的时候当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的时候默认使用的是 ThreadPoolExecutor.AbortPolicy。在默认情况下ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 这代表你将丢失对这个任务的处理。对于可伸缩的应用程序建议使用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时此策略为我们提供可伸缩队列。 36、如何设置线程池的大小 如果我们设置的线程池数量太小的话如果同一时间有大量任务/请求需要处理可能会导致大量的请求/任务在任务队列中排队等待执行甚至会出现任务队列满了之后任务/请求无法处理的情况如果我们设置线程数量太大大量线程可能会同时在争取 CPU 资源这样会导致大量的上下文切换从而增加线程的执行时间影响了整体执行效率。 有一个简单并且适用面比较广的公式 CPU 密集型任务(N1) 这种任务消耗的主要是 CPU 资源可以将线程数设置为 NCPU 核心数1比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断或者其它原因导致的任务暂停而带来的影响。一旦任务暂停CPU 就会处于空闲状态而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。I/O 密集型任务(2N) 这种任务应用起来系统会用大部分的时间来处理 I/O 交互而线程在处理 I/O 的时间段内不会占用 CPU 来处理这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中我们可以多配置一些线程具体的计算方法是 2N。 CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取文件读取这类都是 IO 密集型这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少大部分时间都花在了等待 IO 操作完成上。 37、Executor和Executors的区别 Executors 工具类的不同方法按照我们的需求创建了不同的线程池来满足业务的需求 Executor 接口对象能执行我们的线程任务。ExecutorService接口继承了Executor接口并进行了扩展提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。 使用ThreadPoolExecutor 可以创建自定义线程池。 38、谈谈乐观锁和悲观锁 乐观锁是CAS [Compare And Swap(比较再交换)]悲观锁是 synchronized Synchronized是从悲观的角度出发悲观锁总是假设最坏的情况每次去拿数据的时候都认为别人会修改所以每次在拿数据的时候都会上锁这样别人想拿这个数据就会阻塞直到它拿到锁。jdk中的ReentrantLock也是一种悲观锁。性能较差CAS是从乐观的角度出发总是假设最好的情况每次去拿数据的时候都认为别人不会修改所以不会上锁但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS这种机制我们也可以将其称之为乐观锁。综合性能较好 两种锁的使用场景 乐观锁适用于写比较少的情况下多读场景即冲突真的很少发生的时候这样可以省去了锁的开销加大了系统的整个吞吐量。但如果是多写的情况一般会经常产生冲突这就会导致上层应用会不断的进行 retry这样反倒是降低了性能所以一般多写的场景下用悲观锁就比较合适。 乐观锁的常见的两种实现方式是什么 乐观锁一般会使用版本号机制或者 CAS 算法实现。 版本号机制 一般是在数据表中加上一个数据版本号 version 字段表示数据被修改的次数当数据被修改时version 值会加 1。 CAS 算法 即 compare and swap比较与交换是一种有名的无锁算法。无锁编程即不使用锁的情况下实现多线程之间的变量同步也就是在没有线程被阻塞的情况下实现变量的同步所以也叫非阻塞同步 乐观锁的缺点有哪些 ABA 问题 如果一个变量 V 初次读取的时候是 A 值并且在准备赋值的时候检查到它仍然是 A 值那我们就能说明它的值没有被其他线程修改过了吗很明显是不能的因为在这段时间它的值可能被改为其他值然后又改回 A那 CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 “ABA” 问题。 循环时间长开销大 自旋 CAS也就是不成功就一直循环执行直到成功如果长时间不成功会给 CPU 带来非常大的执行开销。 只能保证一个共享变量的原子操作 CAS 只对单个共享变量有效当操作涉及跨多个共享变量时 CAS 无效。 但是从 JDK 1.5 开始提供了 AtomicReference 类来保证引用对象之间的原子性你可以把多个变量放在一个对象里来进行 CAS 操作。所以我们可以使用锁或者利用 AtomicReference 类把多个共享变量合并成一个共享变量来操作 CAS和 synchronized 的使用场景 简单的来说 CAS 适用于写比较少的情况下多读场景冲突一般较少synchronized 适用于写比较多的情况下多写场景冲突一般较多。 对于资源竞争较少线程冲突较轻的情况使用 synchronized 同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗 cpu 资源而 CAS 基于硬件实现不需要进入内核不需要切换线程操作自旋几率较少因此可以获得更高的性能。对于资源竞争严重线程冲突严重的情况CAS 自旋的概率会比较大从而浪费更多的 CPU 资源效率低于 synchronized。 39、AQS是什么 AQS 的全称为 AbstractQueuedSynchronizer 翻译过来的意思就是抽象队列同步器使用 AQS 能简单且高效地构造出应用广泛的大量的同步器比如我们提到的 ReentrantLockSemaphore是基于 AQS 的。 AQS 是一个锁框架它定义了锁的实现机制并开放出扩展的地方让子类去实现比如我们在 lock 的时候AQS 开放出 state 字段让子类可以根据 state 字段来决定是否能够获得锁对于获取不到锁的线程 AQS 会自动进行管理无需子类锁关心这就是 lock 时锁的内部机制封装的很好又暴露出子类锁需要扩展的地方AQS 底层是由同步队列 条件队列联手组成同步队列管理着获取不到锁的线程的排队和释放条件队列是在一定场景下对同步队列的补充比如获得锁的线程从空队列中拿数据肯定是拿不到数据的这时候条件队列就会管理该线程使该线程阻塞AQS 围绕两个队列提供了四大场景分别是获得锁、释放锁、条件队列的阻塞条件队列的唤醒 40、AQS的原理是什么 AQS 核心思想是如果被请求的共享资源空闲则将当前请求资源的线程设置为有效的工作线程并且将共享资源设置为锁定状态。如果被请求的共享资源被占用那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制这个机制 AQS 是用 CLH 队列锁 实现的即将暂时获取不到锁的线程加入到队列中。 AQS 使用 int 成员变量 state 表示同步状态通过内置的 线程等待队列 来完成获取资源线程的排队工作。 以 ReentrantLock 为例state 初始值为 0表示未锁定状态。A 线程 lock() 时会调用 tryAcquire() 独占该锁并将 state1 。此后其他线程再 tryAcquire() 时就会失败直到 A 线程 unlock() 到 state0即释放锁为止其它线程才有机会获取该锁。当然释放锁之前A 线程自己是可以重复获取此锁的state 会累加这就是可重入的概念。但要注意获取多少次就要释放多少次这样才能保证 state 是能回到零态的。 再以 CountDownLatch 以例任务分为 N 个子线程去执行state 也初始化为 N注意 N 要与线程个数一致。这 N 个子线程是并行执行的每个子线程执行完后countDown() 一次state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后(即 state0 )会 unpark() 主调用线程然后主调用线程就会从 await() 函数返回继续后余动作。 AQS对资源的共享模式有哪些 Exclusive独占只有一个线程能执行如ReentrantLock又可分为公平锁和非公平锁Share共享多个线程可同时执行如CountDownLatch、Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock。 AQS 底层使用了模板方式模式你能说出几个需要重写的方法吗 使用者继承 AbstractQueuedSynchronizer 并重写指定的方法。将 AQS 组合在自定义同步组件的实现中并调用其模板方法而这些模板方法会调用使用者重写的方法。 isHeldExclusively() 该线程是否正在独占资源。只有用到 condition 才需要去实现它tryAcquire(int) 独占方式。尝试获取资源成功则返回 true失败则返回 false。tryRelease(int) 独占方式。尝试释放资源成功则返回 true失败则返回 false。tryAcquireShared(int) 共享方式。尝试获取资源。负数表示失败0 表示成功但没有剩余可用资源正数表示成功且有剩余资源。tryReleaseShared(int) 共享方式。尝试释放资源成功则返回 true失败则返回 false。 41、AQS组件了解吗 Semaphore(信号量)-允许多个线程同时访问 synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源Semaphore(信号量)可以指定多个线程同时访问某个资源 CountDownLatch 倒计时器 CountDownLatch是一个同步工具类用来协调多个线程之间的同步。这个工具通常用来控制线程等待它可以让某一个线程等待直到倒计时结束再开始执行 CyclicBarrier(循环栅栏) CyclicBarrier 和 CountDownLatch 非常类似它也可以实现线程间的技术等待但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用Cyclic的屏障Barrier。它要做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)其参数表示屏障拦截的线程数量每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障然后当前线程被阻塞。 42、Semaphore有什么用 synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源而Semaphore(信号量)可以用来控制同时访问特定资源的线程数量。 Semaphore 的使用简单我们这里假设有 N(N5) 个线程来获取 Semaphore 中的共享资源下面的代码表示同一时刻 N 个线程中只有 5 个线程能获取到共享资源其他线程都会阻塞只有获取到共享资源的线程才能执行。等到有线程释放了共享资源其他阻塞的线程才能获取到。 // 初始共享资源数量 final Semaphore semaphore new Semaphore(5); // 获取1个许可 semaphore.acquire(); // 释放1个许可 semaphore.release();Semaphore 有两种模式 公平模式 调用 acquire() 方法的顺序就是获取许可证的顺序遵循 FIFO非公平模式 抢占式的。(默认) Semaphore 通常用于那些资源有明确访问数量限制的场景比如限流仅限于单机模式实际项目中推荐使用 Redis Lua 来做限流 43、CountDownLatch有什么用 CountDownLatch 允许 count 个线程阻塞在一个地方直至所有线程的任务都执行完毕。CountDownLatch允许一个或多个线程等待其他线程完成操作再执行自己。CountDownLatch是通过一个计数器来实现的每当一个线程完成了自己的任务后可以调用countDown()方法让计数器-1当计数器到达0时调用CountDownLatch。 44、CountDownLatch的原理是什么 CountDownLatch 是共享锁的一种实现,它默认构造 AQS 的 state 值为 count。当线程使用 countDown() 方法时,其实使用了tryReleaseShared方法以 CAS 的操作来减少 state,直至 state 为 0 。当调用 await() 方法的时候如果 state 不为 0那就证明任务还没有执行完毕await() 方法就会一直阻塞也就是说 await() 方法之后的语句不会被执行。然后CountDownLatch 会自旋 CAS 判断 state 0如果 state 0 的话就会释放所有等待的线程await() 方法之后的语句得到执行。 45、用过CountDownLatch么什么场景下用的 使用多线程读取多个文件处理的场景我用到了 CountDownLatch 。要读取处理 6 个文件这 6 个任务都是没有执行顺序依赖的任务但是我们需要返回给用户的时候将这几个文件的处理的结果进行统计整理。为此我们定义了一个线程池和 count 为 6 的CountDownLatch对象 。使用线程池处理读取任务每一个线程处理完之后就将 count-1调用CountDownLatch对象的 await()方法直到所有文件读取完之后才会接着执行后面的逻辑。 46、CyclicBarrier有什么用 CyclicBarrier 和 CountDownLatch 非常类似它也可以实现线程间的技术等待但是它的功能比 CountDownLatch 更加复杂和强大。 CyclicBarrier 的字面意思是可循环使用Cyclic的屏障Barrier。它要做的事情是让一组线程到达一个屏障也可以叫同步点时被阻塞直到最后一个线程到达屏障时屏障才会开门所有被屏障拦截的线程才会继续干活 47、CyclicBarrier的原理是什么 CyclicBarrier 内部通过一个 count 变量作为计数器每当一个线程到了栅栏这里了那么就将计数器减 1。如果 count 值为 0 了表示这是这一代最后一个线程到达栅栏就尝试执行我们构造方法中输入的任务。 CountDownLatch 和 CyclicBarrier 有什么区别 CountDownLatch 是计数器只能使用一次而 CyclicBarrier 的计数器提供 reset 功能可以多次使用。 对于 CountDownLatch 来说重点是“一个线程多个线程等待”而其他的 N 个线程在完成“某件事情”之后可以终止也可以等待。而对于 CyclicBarrier重点是多个线程在任意一个线程没有完成所有的线程都必须等待。 CountDownLatch 是计数器线程完成一个记录一个只不过计数不是递增而是递减而 CyclicBarrier 更像是一个阀门需要所有线程都到达阀门才能打开然后继续执行。 CountDownLatch 应用场景 某一线程在开始运行前等待 n 个线程执行完毕启动一个服务时主线程需要等待多个组件加载完毕之后再继续执行。实现多个线程开始执行任务的最大并行性。注意是并行性不是并发强调的是多个线程在某一时刻同时开始执行。类似于赛跑将多个线程放到起点等待发令枪响然后同时开跑。死锁检测一个非常方便的使用场景是你可以使用 n 个线程访问共享资源在每次测试阶段的线程数目是不同的并尝试产生死锁。 CyclicBarrier 应用场景 CyclicBarrier 可以用于多线程计算数据最后合并计算结果的应用场景。我们用一个 Excel 保存了用户所有银行流水每个 Sheet 保存一个帐户近一年的每笔银行流水现在需要统计用户的日均银行流水先用多线程处理每个 sheet 里的银行流水都执行完之后得到每个 sheet 的日均银行流水最后再用 barrierAction 用这些线程的计算结果计算出整个 Excel 的日均银行流水。 48、Iterator怎么使用有什么特点 迭代器是一种设计模式它是一个对象它可以遍历并选择序列中的对象而开发人员不需要了解该序列的底层结构。 使用 next() 获得序列中的下一个元素。使用 hasNext() 检查序列中是否还有元素。使用 remove() 将迭代器新返回的元素删除。 Iterator 和 ListIterator 有什么区别 Iterator 可用来遍历 Set 和 List 集合但是 ListIterator 只能用来遍历 List。Iterator 对集合只能是前向遍历ListIterator 既可以前向也可以后向。ListIterator 实现了 Iterator 接口并包含其他的功能比如增加元素替换元素获取前一个和后一个元素的索引等等。 49、Collection和Collections有什么区别 Collection是最基本的集合接口一个 Collection 代表一组 Object即 Collection 的元素。它的直接继承接口有 ListSet 和 Queue。Collections是不属于 Java 的集合框架的它是集合类的一个工具类/帮助类。此类不能被实例化 服务于 Java 的 Collection 框架。它包含有关集合操作的静态多态方法实现对各种集合的搜索、排序、线程安全等操作。 50、在Java程序中怎么保证多线程的运行安全 线程安全在三个方面体现 原子性提供互斥访问同一时刻只能有一个线程对数据进行操作atomicsynchronized可见性一个线程对主内存的修改可以及时地被其他线程看到synchronized、volatile有序性一个线程观察其他线程中的指令执行顺序由于指令重排序该观察结果一般杂乱无序 51、Java线程同步的几种方式 使用 Synchronized 关键字wait 和 notify使用特殊域变量 volatile 实现线程同步使用可重入锁实现线程同步使用阻塞队列实现线程同步使用信号量 Semaphore 52、简单说下对 Java 中的原子类的理解 这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候一个操作一旦开始就不会被其他线程干扰。所以所谓原子类说简单点就是具有原子操作特征的类。 基本类型使用原子的方式更新基本类型 AtomicInteger 整型原子类AtomicLong 长整型原子类AtomicBoolean 布尔型原子类 数组类型使用原子的方式更新数组里的某个元素 AtomicIntegerArray 整型数组原子类AtomicLongArray 长整型数组原子类AtomicReferenceArray 引用类型数组原子类 引用类型 使用原子的方式更新引用类型 AtomicReference 引用类型原子类 53、谈谈对CopyOnWriteArrayList的理解 在很多应用场景中读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问 List 的内部数据毕竟读取操作是安全的。 CopyOnWriteArrayList 类的所有可变操作addset 等等都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候我们并不需要修改原有内容而是对原有数据进行一次复制将修改的内容写入副本。写完之后再将修改完的副本替换原来的数据这样就可以保证写操作不会影响读操作了。 先copy再write读的时候无需控制无需加锁写的时候要加锁不是为了不让读而是避免了多线程写的时候会 copy 出多个副本出来。 54、说下你对 Java 内存模型的理解 Java 内存模型的主要目标是定义程序中各个变量的访问规则即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。 Java 内存模型规定了所有的变量都存储在主内存Main Memory中每个线程有自己的工作线程Working Memory保存主内存副本拷贝和自己私有变量不同线程不能访问工作内存中的变量。线程间变量值的传递需要通过主内存来完成。 个操作是不可中断的。即使是在多个线程一起执行的时候一个操作一旦开始就不会被其他线程干扰。所以所谓原子类说简单点就是具有原子操作特征的类。 基本类型使用原子的方式更新基本类型 AtomicInteger 整型原子类AtomicLong 长整型原子类AtomicBoolean 布尔型原子类 数组类型使用原子的方式更新数组里的某个元素 AtomicIntegerArray 整型数组原子类AtomicLongArray 长整型数组原子类AtomicReferenceArray 引用类型数组原子类 引用类型 使用原子的方式更新引用类型 AtomicReference 引用类型原子类
http://www.dnsts.com.cn/news/78916.html

相关文章:

  • 晋城中英文网站建设黄山最佳旅游攻略
  • dw怎么做网站教程营销策划方案怎么做
  • 技术网站有哪些做同城网站有哪些
  • 宝宝投票网站怎么做软件下载网站地址
  • 南宁市网站建设价格龙华网站建设设计
  • 地方社区网站 备案宁波网站制作企业
  • 网站一般采用的设计方案当前网站建设的主要方法
  • 做电商网站必需知道qc快手自媒体平台注册入口
  • 用户体验网站旅游网站建设的好处
  • 专业网站开发哪家专业竞价恶意点击报案
  • 网站优化推广公司推荐建站系统开发
  • 公司做免费网站建设国外网站建设素材
  • h5网站建设方案seo排名赚app最新版本
  • 服务器放多个网站网页设计与制作期末作业源代码
  • 网站开发充值功能可信的手机网站建设
  • 免费的行情软件网站不用下载陕西城乡住房建设部网站
  • 做百度网站好吗建设电影网站需要什么
  • 晋中市科技馆网站建设wordpress钩子大全
  • wordpress收费么企业网站seo排名优化
  • 山东网站制作公司排名怎么在百度搜索到我的网站
  • 温州企业建站程序辛集seo网站优化
  • 晋江论坛网站网站建设工作推进会上的讲话
  • 网页作业班级网站怎么做局域网网站建设软件
  • 门户网站设计技巧网站开发的学习方法
  • 自己做盗号网站徐州网站建设开发
  • 应用网站如何做做便宜的宝贝的网站
  • 免费招聘网站推荐深圳网站优化最好的方法
  • 网站建设招标技术要求公司网页设计思路
  • 惠州 网站建设做网站的要到处跑吗
  • 新网站没有死链接怎么做网站推广文章 优帮云