个人学习笔记,内容仅供参考!
文章目录
1、线程创建的两种方式
- 继承Thread类,重写run()方法
- 实现Runnable接口,实现run()方法
- 实现Callable接口,实现call()方法,有返回值,在线程池中使用
import java.util.concurrent.*;
/**
* @author IT00ZYQ
* @Date 2021/3/3 21:15
**/
public class HowToCreateThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 方式1:继承Thread类
new Thread01().start();
// 方式2:实现Runnable接口
new Thread(new Run()).start();
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
System.out.println("执行main线程,结果: " + sum);
// 方式3:Callable接口,但需要线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> submit = executorService.submit(new Call());
System.out.println("结果: " + submit.get());
executorService.shutdown();
}
}
class Thread01 extends Thread {
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
System.out.println("创建线程的第一种方式,继承Thread类,重写run()方法,结果: " + sum);
}
}
class Run implements Runnable {
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
System.out.println("创建线程的第二种方式,实现Runnable接口,实现run()方法,结果: " + sum);
}
}
class Call implements Callable<String> {
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
System.out.println("创建线程的第三种方式,实现Callable接口,实现call()方法");
return String.valueOf(sum);
}
}
2、Sleep、Join、Yield
- Sleep:线程睡眠一定时间,进入TIMED_WAITING状态。不释放锁。
- Join:在T1线程中执行
T2.join();
,则T1线程必须等待T2线程结束后才继续执行。 - Yield:线程让出资源,此时处于RUNNABLE状态中的就绪状态。如果没有其他等待的线程,该线程重新进入运行状态。
3、线程状态
- NEW 新建
- RUNNABLE 就绪/执行
- BLOCKED 阻塞
- WAITING 等待
- TIMED_WAITING 等待一定时间
- TERMINATED 停止
4、synchronize关键字
- 给对象加锁(并不是给代码块加锁),每个对象头部都有一个monitor(监视器),当线程获取锁时,monitor中的count+1,当线程释放锁时,monitor中的count-1,用count来标志对象锁释放被线程持有。
- 当synchronize加在普通方法与静态方法上时,用ACC_SYNCHRONIZED标识符来标志该方法是一个同步方法。
- 当synchronize加在代码块上时,字节码同步代码块前后分别有monitorenter和monitorexit指令
- 默认给当前对象加锁,也可以给class对象加锁。
- 同步方法与非同步方法可以同时执行
- 不能所在String、Integer等基本类型的包装类对象上
- 锁升级:当只有一个线程时,只是在对象头部标志了线程ID,为偏向锁。当有第二个线程想要获取锁时,自旋获取锁(默认10次),此时为自旋锁;当自旋无法获取锁时,升级为重量级锁,进入等待队列。
5、volatile关键字
- 保证线程间可见,线程是共享堆内存的,线程也有自己的内存,当其中一个线程修改了共享变量时,另一个线程的值可能没有及时更新,加了volatile标志变量为易变的,CPU会实时监听。缓存一致性协议。
- 禁止指令重排,添加内存屏障(例:单例模式中的DCL),Load/Store指令屏障。
6、CAS(Compare And Set 无锁优化 自旋)
- CAS操作CPU原语支持,原子性操作
- 可能出现ABA问题,如果是基本数据类型不影响结果,如果是对象引用,可能出现问题。解决ABA问题可以加上版本号的方法解决。
CAS(Expected, NewValue) {
// 判断当前值是否为期望值
if V == Expected
// 赋予新值
V = NEW
else
// 当前值不为期望值,重新尝试(自旋)或者操作失败
try again or fail
}
- AtomicInteger等Atomic就是使用了CAS锁,使用了Unsafe类,Unsafe类是直接操作虚拟机内存的。
- 加锁代码执行时间长,竞争线程多,使用synchronized,需要系统调用进行加锁
- 加锁代码执行时间短,竞争线程少,使用自旋锁,自旋时需要消耗CPU资源,但是不需要进行用户态和内核态的切换
7、各种锁
/**
* @author IT00ZYQ
* @Date 2021/3/4 10:45
**/
public class SleepUtil {
public static void sleepSecond(int second) {
try {
Thread.sleep(second * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
7.1、ReentrantLock
- 可以有多个等待队列
lock.newCondition()
/**
* @author IT00ZYQ
* @Date 2021/3/4 10:29
**/
public class T01_ReentrantLock {
synchronized void m1() {
for (int i = 0; i < 20; i++) {
SleepUtil.sleepSecond(1);
System.out.println("m1 running ...");
}
}
synchronized void m2() {
System.out.println("m2 running ...");
}
public static void main(String[] args) {
T01_ReentrantLock t = new T01_ReentrantLock();
// m1方法加了synchronized锁,当前对象已被锁住
new Thread(t::m1).start();
SleepUtil.sleepSecond(1);
// m2方法需要获取当前对象锁,必须等待上面的线程运行结束释放锁
new Thread(t::m2).start();
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author IT00ZYQ
* @Date 2021/3/4 10:29
**/
public class T02_ReentrantLock {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock(); // 相当于synchronize(this)
for (int i = 0; i < 20; i++) {
SleepUtil.sleepSecond(1);
System.out.println("m1 running ...");
}
}finally {
lock.unlock();
}
}
void m2() {
try {
lock.lock(); // 相当于synchronize(this)
System.out.println("m2 running ...");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
T02_ReentrantLock t = new T02_ReentrantLock();
// m1方法加了调用了lock(),当前对象已被锁住
new Thread(t::m1).start();
SleepUtil.sleepSecond(1);
// m2方法需要调用lock(),必须等待上面的线程运行结束释放锁
new Thread(t::m2).start();
}
}
7.2、CountDownLatch(倒数阀门)
import java.util.concurrent.CountDownLatch;
/**
* @author IT00ZYQ
* @Date 2021/3/4 11:05
**/
public class T01_CountDownLatch {
static int COUNT = 100;
public static void main(String[] args) {
// 倒数阀门,需要count为0时才能通过
CountDownLatch latch = new CountDownLatch(COUNT);
Thread[] threads = new Thread[COUNT];
// 创建COUNT个线程
for (int i = 1; i <= COUNT; i++) {
int t = i;
threads[i - 1] = new Thread(() -> {
System.out.println("执行倒数, 当前线程: " + t);
latch.countDown();
});
}
// 启动COUNT个线程
for (int i = 1; i <= COUNT; i++) {
threads[i - 1].start();
}
for (int i = 0; i < COUNT; i++) {
System.out.println("CountDownLatch倒数结束,阀门未开启" + i);
}
try {
// 添加阀门,只有countdown为0时才能通过
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("CountDownLatch倒数结束,阀门开启");
}
}
}
7.3、CyclicBarrier(循环障碍)
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author IT00ZYQ
* @Date 2021/3/4 11:16
**/
public class T01_CyclicBarrier {
static int COUNT = 100;
static int BARRIER = 20;
public static void main(String[] args) {
// 循环障碍 线程计数达到20才能通过
CyclicBarrier cb = new CyclicBarrier(BARRIER, () -> {
System.out.println("已满20人,通过");
});
for (int i = 0; i < COUNT; i++) {
int t = i;
new Thread(() -> {
System.out.println("我是线程: " + t + ", 当前等待数" + cb.getNumberWaiting());
try {
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
7.4、Semaphore(信号量)
import java.util.concurrent.Semaphore;
/**
* @author IT00ZYQ
* @Date 2021/3/4 11:36
**/
public class T01_Semaphore {
/**
* 信号量,只有信号量大于0时才能拿到锁
*/
static Semaphore s = new Semaphore(4);
static void testRun() {
try {
System.out.println(Thread.currentThread().getName() + "判断信号量,判断是否能拿到锁,等待拿到锁...");
// 拿到锁需要的信号量,默认为1
s.acquire();
System.out.println(Thread.currentThread().getName() + "成功拿到锁");
SleepUtil.sleepSecond(5);
System.out.println(Thread.currentThread().getName() + "运行结束");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放信号量
s.release();
}
}
public static void main(String[] args) {
new Thread(() -> testRun(), "t1").start();
new Thread(() -> testRun(), "t2").start();
new Thread(() -> testRun(), "t3").start();
new Thread(() -> testRun(), "t4").start();
new Thread(() -> testRun(), "t5").start();
}
}
7.5、ReadWriteLock(读写锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author IT00ZYQ
* @Date 2021/3/4 11:24
**/
public class T01_ReadWriteLock {
/**
* 读写锁
* 读锁是共享锁
* 写锁是排他锁
*/
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
static void read(Lock lock) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "开始Read操作");
SleepUtil.sleepSecond(5);
System.out.println(Thread.currentThread().getName() + "Read操作结束");
} finally {
lock.unlock();
}
}
static void write(Lock lock) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "开始Write操作");
SleepUtil.sleepSecond(3);
System.out.println(Thread.currentThread().getName() + "Write操作结束");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
new Thread(() -> read(readLock), "Read-01").start();
new Thread(() -> read(readLock), "Read-02").start();
new Thread(() -> write(writeLock), "Write-01").start();
new Thread(() -> write(writeLock), "Write-02").start();
new Thread(() -> read(readLock), "Read-03").start();
new Thread(() -> read(readLock), "Read-04").start();
}
}
7.6、LockSupport
import java.util.concurrent.locks.LockSupport;
/**
* @author IT00ZYQ
* @Date 2021/3/4 15:34
**/
public class T07_LockSupport {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("t1线程: " + i);
if (i == 5) {
System.out.println("线程t1 park()等待线程t2运行结束...");
// 停车
LockSupport.park();
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("t2线程: " + i);
}
System.out.println("线程t2运行结束, unPark() t1...");
LockSupport.unpark(t1);
});
t1.start();
t2.start();
}
}
8、AQS
AbstractQuenedSynchronizer抽象的队列式同步器。基于CAS+volatile实现。
9、ThreadLocal(仅当前线程可见)
/**
* @author IT00ZYQ
* @Date 2021/3/4 18:50
**/
public class T08_ThreadLocal {
private static ThreadLocal<Integer> tl = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
System.out.println("5秒后读取ThreadLocal中的变量...");
SleepUtil.sleepSecond(5);
System.out.println(tl.get());
}).start();
new Thread(() -> {
System.out.println("3秒后设置ThreadLocal中的变量...");
SleepUtil.sleepSecond(3);
// 这里设置了ThreadLocal中的值,分析源码可知是设置到当前线程Thread对象中的,
// 其他线程不可见
tl.set(10);
System.out.println("ThreadLocal变量设置完毕!");
}).start();
// ThreadLocal用完要remove,否则会造成内存泄露,对象一直不会被回收
tl.remove();
}
}
10、VarHandle
添加一个引用指向某个对象,通过VarHandle可进行原子性操作(CAS),比反射快,直接操纵二进制码
11、四种引用-强软弱虚
- 强引用:一般的引用都是强引用,如new()的对象
- 软引用:当内存不足时,gc会回收软引用指向的对象,可用作缓存
- 弱引用:只要触发gc,只有弱引用指向的对象会被回收,例ThreadLocal中的ThreadLocalMap
- 虚引用:构造函数有两个参数,一个是虚引用指向的对象,一个是队列,虚引用只要发生gc就会被回收,回收后会将对象入队到队列中。写JVM的人处理对外内存用的。
import java.lang.ref.SoftReference;
public class T02_SoftReference {
public static void main(String[] args) {
SoftReference<byte[]> m = new SoftReference<>(new byte[1024*1024*10]);
//m = null;
System.out.println(m.get());
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(m.get());
//再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用干掉
byte[] b = new byte[1024*1024*15];
System.out.println(m.get());
}
}
import java.lang.ref.WeakReference;
public class T03_WeakReference {
public static void main(String[] args) {
WeakReference<M> m = new WeakReference<>(new M());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
ThreadLocal<M> tl = new ThreadLocal<>();
tl.set(new M());
tl.remove();
}
}
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;
public class T04_PhantomReference {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}).start();
new Thread(() -> {
while (true) {
Reference<? extends M> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
}
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
12、线程池
12.1、为什么建议使用线程池?
- 可以重复利用已有线程执行任务,减少线程的创建和销毁,减少资源消耗。
- 通过线程池管理线程,可根据系统的承受能力调整线程池可运行的数量。
12.2、创建线程池的参数
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:非核心线程,空闲存活时间
- TimeUnit:时间单位
- workQueue:阻塞队列,用于保存任务的阻塞队列
- threadFactory:创建线程的工程类
- handler:拒绝策略
常见的拒绝策略 - AbortPolicy:丢弃任务并抛出RejectedExecutorException异常,默认的拒绝策略、
- DiscardPolicy:丢弃任务但不抛出异常
- DiscardOldestPolicy:丢弃队列最前面的任务,然后尝试重新执行任务。
- CallerRunsPolicy:由调用线程处理任务
12.3、常见线程池
- CachedThreadPool:线程数量没有固定,可达到Integer的最大值(Integer.MAX_VALUE);线程池中的线程可进行重复利用和回收,默认回收时间为1分钟;当线程池中没有空闲线程时,会创建一个新的线程。
- FixThreadPool:线程的数量固定,可很好地控制线程的并发量;线程可以一直存在,直到显示关闭线程池之前;当有新的任务时且没有空闲的线程时,任务进入等待队列。
- SingleThreadExecutor:只有一个线程。
- ScheduledThreadPool:可安排在给定延迟后执行或定期地执行任务;线程池中有固定数量的线程,即便是没有任务也保留线程;
- SingleThreadScheduledExecutor:类似ScheduledThreadPool,不过只有一个线程执行任务。
- WorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时间内最多有几个线程在执行任务;默认为CPU个数。
12.4、 线程池的生命周期
- NEW:处于新建的状态。
- RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务。
- SHUTDOWN:关闭状态,不再接受新的任务,但可以继续处理和执行阻塞队列中已存在的任务。
- STOP:停止,不接受新的任务,也不处理阻塞队列中的任务,会中断正在处理任务的线程。
- TIDYING:所有任务已经中止,且工作线程数量为0,最后变迁到这个状态的线程将要执行terminated()钩子方法,只会有一个线程执行这个方法
- TERMINATED:中止状态,已经执行完terminated()钩子方法
12.5、阻塞队列
- ArrayBlockingQueue:有界队列,先进先出
- LinkedBlockingQueue:无界队列,先进先出
- DelayQueue:无界队列,队列中的元素只有到期了才能被取走,有序队列,队首元素是最早到期的
- PriorityBlockingQueue:无界队列,优先队列
- SynchronousQueue:没有容量,每一个插入操作要阻塞等待一个删除操作,每一个删除操作要阻塞等待一个插入操作
12.6、execute()(submit())方法的执行逻辑
- 如果当前有空闲的线程,则直接将任务交给空闲的线程执行
- 如果当前运行线程数少于核心线程数,则创建新的线程来执行新的任务。
- 如果执行的线程数大于或等于核心线程数,则将新的任务加入阻塞队列。
- 如果阻塞队列已满,则创建新的线程来执行任务。
- 如果线程个数已经超过了最大线程数,则会使用拒绝策略RejectedExecutionHandler来进行处理
13、面试题汇总
13.1、线程与进程的区别
进程是操作系统分配内存资源的基本单位,线程是执行和调度的基本单位。进程独享一块内存空间,一个进程中的线程共享一块内存空间。
13.2、sleep()与wait()的区别
- sleep()不释放锁,wait()释放锁
- sleep()是Thread类中的静态方法,wait()是Object类中的方法
- wait()必须在同步代码块中使用,一般配合notify()使用
13.3、LongAdder(线程安全)
- 使用了分段锁
13.4、如何提高锁的性能?
- 减少锁的持有时间,只锁需要同步的代码块
- 减小锁的粒度,分段锁,1)ConcurrentHashMap内部分成了若干个小的HashMap,称之为段,put()操作时,只锁将要操作的段。2)LongAdder,内部是一个数组,分段加锁,分段计算后再汇总。
- 读写分离锁来替换独占锁,ReadWirteLock
- 锁粗化,锁的请求、同步与释放也是需要资源的。
13.5、公平锁与非公平锁
- 会存在获取锁的线程的等待队列,当有新的线程想要获取锁时,公平锁会先进入等待队列,非公平锁会直接尝试去获取锁。
- ReentrantLock可设置公平与非公平,默认为非公平;synchronize是非公平锁。