为什么使用并发编程?
- 并发编程可以充分利用多核心CPU资源,在单核性能达到瓶颈的情况下能有效提高程序的执行效率,好的并发模型会比单线程应用花费更少的执行时间
- 我们可以对业务进行拆分,让每个线程运行一个任务,会比顺序执行效率更高
缺点:
-
高并发会带来频繁的线程切换,这个过程资源消耗很大,当问题没有达到一定规模时,并发编程的效率甚至不如单线程
-
并发编程由于共享变量会带来线程安全问题
线程安全的定义: 多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
线程安全的根本原因: CPU高速缓存中缓存的数据和主内存中不一致,指令重排序;线程安全的问题一般是因为主内存和工作内存数据不一致性和重排序导致的
多线程中基本概念
同步VS异步
同步:同步调用,调用者一旦调用方法,必须等到方法结束才能进行下一步操作;
异步:调用者调用完该方法,不需要等待就可以执行下一步;
例如泡茶,必须等到水烧开才能开始泡茶,这个是同步;等待烧水的过程中可以读书或者干别的,这个属于异步
并发VS并行
并发指多个任务交替执行,并行指任务同时进行
阻塞VS非阻塞
一个线程占用了资源,导致别的线程不能访问,被挂起,被称为阻塞
临界区
临界区表示公共资源或者共享数据,可以被多个线程共同使用,但是一旦临界区被一个线程使用,其他线程就必须等待
JAVA创建线程
创建线程的方式只有一种Thread 类,Runnable和线程池最终都是创建一个Thread对象去执行Runnable接口中的run方法
1)实现Runnable接口2)继承Thread 类 3)使用ExecutorService线程池 4)Callable、Future 实现带返回结果的多线程
线程状态
线程一共有6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)
NEW: 初始状态,线程被构建,但是还没有调用start 方法
RUNNABLE: 运行状态,JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中”
WAITING: 无限期等待,处于这种状态的线程不会被分配到CPU,需要等到被其他线程唤醒
调用:Object.wait(),Thread.join(),LockSupport.park() 会是线程进入这种状态
BLOCKED: 阻塞状态,当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。如果其线程释放了锁就会结束此状态
TIME_WAITING: 超时等待状态,超时以后自动返回
TERMINATED: 终止状态,表示当前线程执行完毕
阻塞和等待的区别:阻塞是被动的,等待是主动的;阻塞是线程未获取到锁被迫进入阻塞状态,等待是线程正在执行(以获取到锁),主动放弃CPU资源,需要别的线程将其唤醒
线程基本操作
interrupt:线程中断
InterruptedException线程复位,怎么理解?
当我们对某个线程发起中断请求时(interrupt状态置为true),线程会自己判断是否可以中断,然后如果线程判断当前不能中断,就会把把interrupt状态恢复成false,并抛出InterruptedException异常;
wait和notify
void notify():唤醒在锁对象上等待的单个线程。
void notifyAll():唤醒在锁对象监视器上等待的所有线程。
void wait():导致当前的线程等待(并让出锁),直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
public void waitTest() {
Thread thread1 = new Thread(() -> {
synchronized (syn) {
try {
syn.wait(); // 等待,并释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("开始执行");
}
});
Thread thread2 = new Thread(() -> {
synchronized (syn) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
syn.notify(); // 通知单个线程(通知所有线程需要使用notifyAll)
log.debug("阻塞释放");
}
});
thread1.start();
thread2.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
休眠
Thread.sleep(long millis) :线程进入超时等待状态,和wait()相比,sleep不会让出锁,wait会让出锁
让步
Thread.yield():暂停当前正在执行的线程对象,让出 CPU资源,并执行其他线程。
合并
void join():等待该线程终止,然后执行后面的方法,可以将几个并行线程的线程合并为一个单线程执行;当一个线程必须等待其他线程执行完毕才能执行时可以使用join方法
public void joinTest() {
Thread thread1 = new Thread(() -> {
log.debug("开始执行1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("开始执行2");
});
Thread thread2 = new Thread(() -> {
log.debug("before join");
try {
thread1.join(); // 必须等待thread1执行完之后,之后的代码才会执行
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("after join");
});
thread1.start();
thread2.start();
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}