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必须手动开启锁/关闭锁,synchronized遇到异常会自动释放锁,ReentrantLock需要手动关闭,一般都是放在finally中关闭 定义锁 Lock lock = new ReentrantLock(); 开启 lock.lock(); 关闭 lock.unlock(); 使用Reentrantlock可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行 可以根据tryLock的返回值来判定是否锁定 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中,如果tryLock未锁定,则不需要unlock 使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断 new ReentrantLock(true) 表示公平锁,不带参数默认为false,非公平锁
8. CountDownLatch
countDownLatch这个类可以使一个线程等待其他线程各自执行完毕后再执行。是通过一个计数器来实现的,计数器的初始值是线程的数量。当调用countDown()方法后,每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
线程中调用countDown()方法开始计数;在调用await()方法的线程中,当计数器为0后续才会继续执行,否则一直等待;也可以使用latch.await(timeout, unit)在等待timeout时间后如果计数器不为0,线程仍将继续。countDown()之后的代码不受计数器控制 与join区别,使用join的线程将被阻塞,使用countDown的线程不受影响,只有调用await的时候才会阻塞
8. CyclicBarrier
作用就是会让指定数量的(数量由构造函数指定)所有线程都等待完成后才会继续下一步行动。构造函数:public CyclicBarrier(int parties)
- public CyclicBarrier(int parties)
- public CyclicBarrier(int parties, Runnable barrierAction)
parties 是线程的个数;barrierAction为最后一个到达线程要做的任务
所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。
实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。
9. Phaser
可重复使用的同步屏障,功能类似于CyclicBarrier和CountDownLatch,但支持更灵活的使用。
Phaser使我们能够建立在逻辑线程需要才去执行下一步的障碍等。
我们可以协调多个执行阶段,为每个程序阶段重用Phaser实例。每个阶段可以有不同数量的线程等待前进到另一个阶段。我们稍后会看一个使用阶段的示例。
要参与协调,线程需要使用Phaser实例 register() 本身。请注意:这只会增加注册方的数量,我们无法检查当前线程是否已注册 – 我们必须将实现子类化以支持此操作。
线程通过调用 arriAndAwaitAdvance() 来阻止它到达屏障,这是一种阻塞方法。当数量到达等于注册的数量时,程序的执行将继续,并且数量将增加。我们可以通过调用getPhase()方法获取当前数量。
10. ReadWriteLock
ReadWriteLock的具体实现是ReentrantReadWriteLock
ReadWriteLock允许分别创建读锁跟写锁
- ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- Lock readLock = readWriteLock.readLock();
- Lock writeLock = readWriteLock.writeLock();
使用ReadWriteLock时,适用条件是同一个数据,有大量线程读取,但仅有少数线程修改。ReadWriteLock可以保证:
只允许一个线程写入(其他线程既不能写入也不能读取);
没有写入时,多个线程允许同时读(提高性能)
读写分离锁可以有效地帮助减少锁竞争,以提高系统性能,读写锁读读之间不互斥,读写,写写都是互斥的