1. 创建线程的四种方式
实现Runnable 重写run方法
继承Thread 重写run方法
线程池创建 Executors.newCachedThreadPool()
实现Callable接口
2. Thread线程操作方法
当前线程睡眠指定mills毫秒
Thread.sleep([mills])
当前线程优雅让出执行权
Thread.yield()
例如Thread t1, t2,在t2的run方法中调用t1.join(),线程t2将等待t1完成后执行
join
3. Thread状态
状态 | 使用场景 |
---|---|
NEW | Thread被创建之后,未start之前 |
RUNNABLE | 在调用start()方法之后,这也是线程进入运行状态的唯一一种方式。 具体分为ready跟running,当线程被挂起或者调用Thread.yield()的时候为ready |
WAITING | 当一个线程执行了Object.wait()的时候,它一定在等待另一个线程执行Object.notify()或者Object.notifyAll()。 或者一个线程thread,其在主线程中被执行了thread.join()的时候,主线程即会等待该线程执行完成。当一个线程执行了LockSupport.park()的时候,其在等待执行LockSupport.unpark(thread)。当该线程处于这种等待的时候,其状态即为WAITING。需要关注的是,这边的等待是没有时间限制的,当发现有这种状态的线程的时候,若其长时间处于这种状态,也需要关注下程序内部有无逻辑异常。 |
TIMED_WAITING |
这个状态和WAITING状态的区别就是,这个状态的等待是有一定时效的 Thread.sleep(long) Object.wait(long) Thread.join(long) LockSupport.parkNanos() LockSupport.parkUntil() |
BLOCKED | 在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态 |
TERMINATED | 线程执行结束之后的状态。 线程一旦终止了,就不能复生。 在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常 |
4. synchronized
锁住的是对象而不是代码
this 等价于 当前类.class
锁定方法,非锁定方法同时进行
锁在执行过程中发生异常会自动释放锁
synchronized获得的锁是可重入的
锁升级 偏向锁-自旋锁-重量级锁
synchronized(object)不能用String常量/Integer,Long等基本数据类型
锁定对象的时候要保证对象不能被重写,最好加final定义
4. volatile
保证线程可见性
禁止指令重排序
volatile并不能保证多个线程修改的一致性,要保持一致性还是需要synchronized关键字
volatile 引用类型(包括数组)只能保证引用本身的可见性,不能保证内部字段的可见性 volatile关 键字只能用于变量而不可以修饰方法以及代码块
5. synchronized与AtomicLong以及LongAdder的效率对比
Synchronized 是需要加锁的,效率偏低;AtomicLong 不需要申请锁,使用CAS机制;LongAdder 使用分段锁,所以效率好,在并发数量特别高的时候,LongAdder最合适
6. ConcurrentHashMap的分段锁原理
分段锁就是将数据分段上锁,把锁进一步细粒度化,有助于提升并发效率。HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
7. ReentrantLock
ReentrantLock可以替代synchronized 但是ReentrantLock必