ArrayList和LinkedList的区别
- ArrayList: 基于动态数组,连续的内存存储,适合下标访问(随机访问)。扩容机制:长度固定,超出长度内存数组时新建数组,将原数组中的数据拷贝到新数组,如果不是尾部插入数据还会设计到元素的移动。
- LinkedList: 基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询,因为查询需要逐一遍历
遍历LinkedList必须使用iterator,不能使用for循环,因为每次for循环内部通过get(i)获取元素时需要对list重新进行遍历,消耗大 不要试图使用indexOf返回元素索引,并利用其进行遍历,使用indexOf对list进行遍历,当结果为空时会遍历整个列表
ThreadLocal
- ThreadLocal是Java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据
- Threadlocal底层通过ThreadLocalMap实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,value为需要缓存的值
- 如果在线程池中使用ThreadLocal对象使用完之后,应该把要设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法: 使用完之后 调用ThreadLocal下的remove方法,手动清除Entry对象
- ThreadLocal经典的应用场景就是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享同一个连接)
线程安全的理解
当多个线程访问同一个对象时,如果不用进行额外的同步控制或其他协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程进行初始化的时候分配,运行过程中也可以向系统要额外的对,用完之后需要还给操作系统,否则就会内存泄漏
在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区唯一的目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立
Java死锁
形成死锁的原因:
- 一个资源每次只能被一个线程使用
- 一个线程在阻塞等待某个资源时,不释放已占有资源
- 一个线程已经获得的资源,在未使用完之前,不能被强行剥夺
- 若干线程形成头尾相接的循环等待资源关系
如何避免死锁:
- 注意加锁顺序,保证每个线程按同样的顺序进行加锁
- 注意加锁时限,可以针对锁设置一个超时时间
- 注意死锁检查,这是一种预防机制,确保在第一时间发现死锁并进行解决
提交任务时,线程池队列满,会发生什么
- 如果使用的是无界队列LinkedBlockingQueue,那么可以继续提交任务
- 如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue 中ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略,RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
Synchronized和ReentrantLock区别
Synchronized | ReentrantLock |
---|
关键字 | 类 | 自动加锁和释放锁 | 手动加锁和释放锁 | JVM层面的锁 | API层面的锁 | 非公平锁 | 可以选择公平或者非公平 | 锁的是对象,锁信息保存在对象头中 | 锁的是线程,通过代码中int类型的state标识来标识锁的状态 | 底层有一个锁升级的过程 | |
线程池的作用以及参数
作用:
- 降低资源消耗;提高线程的利用率,降低创建和销毁线程的消耗
- 提高响应速度,任务来了,有线程可用可执行,而不是先创建线程,再执行
- 提高线程的可管理性;使用线程池可以统一分配调优监控
参数:
- corePoolSize: 核心线程数,正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
- maxinumPoolSize: 代表最大线程数,与核心线程相对应,标识最大允许被创建的线程数,比如当前任务较多,核心线程数用完了,无法满足需求时,就会创建新的线程,但是线程池内的总数不会超过最大线程数
- keepAliveTime、unit: 表示超出核心线程数之外的线程空闲存活时间,核心线程不会消除,但超出核心线程数部分的线程如果空闲一定时间则会被消除,可以通过setKeepAliveTime来设置空间时间
- workQueue: 用来存放待执行的任务,假设核心线程都已被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还再持续进入则开始创建新的线程
① ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。 ②LinkedBlockingQuene:基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。 ③SynchronousQuene:一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。 ④PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。 - ThreadFactory: 实际上是一个线程工厂,用来生产线程执行任务。
- handler :任务拒绝策略,两种情况,一:当调用shutdown等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在进行,由于线程池关闭,再继续提交任务会遭到拒绝;二:另一种情况是当达到最大线程数,线程池没有能力继续处理新提交的任务时。
①CallerRunsPolicy:在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。 ②AbortPolicy: 直接丢弃任务,并抛出RejectedExecutionException异常。 ③DiscardPolicy: 直接丢弃任务,什么都不做。 ④DiscardOldestPolicy: 抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
CountDownLatch和Semaphore的区别和底层原理
- CountDownLatch:表示计数器,可以给CountDownLatch设置一个数字,一个线程调用CountDownLatch的await()将会被阻塞,其他线程可以调用CountDownLatch的countDown()方法来对CountDownLatch的数字减一,当数字被减为0后,所有await的线程都将被唤醒。对应的底层原理: 调用await()方法的线程会利用AQS排队,一旦数字被减为0,则会将AQS中排队的线程依次唤醒。
- Semaphore: 表示信号量,可以设置许可的个数,表示同时允许最多多少个线程同时使用该信号量,通过acquire()来获取许可,如果没有许可可以则阻塞线程,并通过AQS来排队,可以通过release()方法来释放许可,当某个线程释放某个许可后,会从AQS中正在排队的第一个线程开始依次唤醒,直到没有空间许可。
AQS如何实现可重入锁
AQS:AbstractQueuedSynchronizer是并发容器J.U.C(java.util.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表。
- 在AQS中,维护了一个信号量state和一个线程组成的双向链表队列,其中,这个线程队列,就是用来给线程排队的,而state就是用来控制线程排队或放行的
- 在可重入锁场景下,state用来标识加锁的次数,0表示无锁,加一次锁,state就加1,释放锁就减1
|