黑马程序员深入学习Java并发编程,JUC并发编程全套教程_哔哩哔哩_bilibili
黑马满老师,线程池,synchronized,AQS
什么是线程和进程?线程与进程的关系,区别及优缺点?!!!!
- 进程是程序的一次执行过程,是系统资源分配的基本单位,是动态的。
- 线程是比进程更小的执行单位,是处理机cpu任务调度的最小基本单位,一个进程中可以创建多个线程,多个线程可以共享同一进程的资源(如JVM中的堆和方法区)。
- 系统在线程之间 切换开销 比进程之间开销小得多,线程也被称为轻量级的进程。
为什么要使用多线程? 死背咯
- 从计算机底层说:首先线程是轻量级,线程间切换比进程之间低。
-
- 在单核时代多线程主要是为了提高单进程利用 CPU 和 IO 系统的效率,因为线程可能被IO阻塞,当某个线程被阻塞时cpu完全有能力处理其他线程,而且cpu是很快。
- 多核时代,那更要用多线程了,提高进程利用多核 CPU 的能力,可以让每个cpu核心都执行线程。
- 从互联网发展趋势来说:
-
- 现在的互联网动不动就是百万级的并发量,多线程并发编程是开发高并发系统的基础。 利用多线程可以提高系统处理并发请求的能力。
说说 sleep() 方法和 wait() 方法区别和共同点?
同:都可以暂停线程。
不同:
- sleep是Thread类的静态方法,wait是Object类的方法
- sleep() 方法没有释放锁,而 wait() 方法释放了对象的锁,wait方法在同步方法/代码块中调用。
- wait方法线程不会自动醒,需要其他线程调notify/notifyAll方法。sleep方法线程会自动醒。
创建线程四种方式及优缺点(重点)
- 继承Thread类,重写run方法。(用的不多,耦合太大)
- 实现Runnable接口,实现run方法。
- 通过FutureTask,实现Callable接口并实现call方法,有返回值。
- 通过线程池创建。
优势劣势(重点):
- 继承Thread类,采用继承的方式,代码太耦合了。
- 实现runnable接口可以通过,匿名内部类或者lambda表达式去写,方便很多。
- 实现callable接口 可以拿到线程允许的返回值并且可以捕获异常。
- 线程池,可以执行多个任务,线程管理、线程复用、控制并发。
但是但是但是,其实最后都是通过 new Thread().start()创建线程的,只有一种方式。
实现 Runnable 接口和 Callable 接口的区别?
- Callable的call方法有返回值。Runnable没返回值
- Callable是支持泛型的。
- Callable的call()可以抛出异常,被外面的操作捕获。(所以如果可能有异常使用callable接口更好)
- Callable不能直接作为Thread的参数,而需要传递给FutureTask,FutureTask是一个runnable的实现类。
如果执行简单任务不需要结果且不抛出异常可以用runnable。
如果执行复杂的需要返回结果 和 抛出异常的任务,使用callable。
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
//FutureTask is a implementClass of runnable
FutureTask futureTask = new FutureTask(myCallable);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
//get the returnValue of Callable
// 这里会阻塞吗?答:会
Integer r = (Integer)futureTask.get();
System.out.println(r);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call");
return 1024;
}
}
线程的几种状态?可以从操作系统和Java层面分析
操作系统中的五种状态:
- 初始状态(新建状态)
- 就绪状态:有能力争夺cpu时间片
- 运行状态:正在运行
- 阻塞状态:调用了阻塞API
- 终止状态:
Java中线程的六种状态:
可通过 thread.getState() 获取线程的内部枚举
- NEW: 新建初始状态,线程被创建出来但没有被调用 start() 。
- RUNNABLE: 运行状态(包括了操作系统的运行态和就绪态):线程被调用了 start() 等待运行的状态。
- BLOCKED:阻塞状态: 需要等待锁释放,调用了synchronized关键字等待获取对象锁时。
- WAITING:等待状态:表示该线程需要等待其他线程做出一些特定动作(通知或中断),this.wait, this.notify,join()方法
- TIME_WAITING:超时等待状态: 指定的时间后自行返回而不是像 WAITING 那样一直等待,如Thread.sleep方法, 或者是obj.wait(time), t.join(time)
- TERMINATED:终止状态,表示该线程已经运行完毕。
// JDK源码
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程安全是什么意思?
多线程环境下,不会因为多个线程操作共享数据产生数据不一致的问题。
线程安全的 解决方案?
- 锁(乐观锁悲观锁)
- 线程局部变量(Threadlocal)
Java中线程通信的方式?(顺丰)
- volatile 关键字
- wait notify
- Lock中的condition中的 await/signal
- 基于AQS的Semaphore、countDownLatch计数器、
Java中的锁相关
Java中哪些锁(用友)
乐观锁:cas实现,Atomic原子类(unsafe类提供)
悲观锁:synchronized关键字(扩展讲JVM层面实现),ReentrantLock实现了Juc包的lock接口(扩展讲AQS)
分布式锁
实际开发中锁如何选型?(美团,重要)
哪些场景用cas乐观锁,哪些场景用悲观锁?
乐观锁(无锁并发) :
- 适合冲突低并发偏小,并且是读多写少(无锁并发) 的场景(例如配合volatile关键字几个线程写,多个线程读)
- 如果竞争并发太大用cas效率太低,因为大部分线程获取不到锁会进行盲轮询导致cpu资源浪费。
悲观锁:
- 适合写操作多,冲突高的场景。
- 对数据强一致性要求高的场景。
- 并且使用悲观锁的时候要控制好锁的粒度。
介绍可重入锁
概念:可重入锁指的是在一个线程中可以多次获取同一把锁,synchronized和ReentrantLock都是可重入锁。
实现:但他们的实现不一样,扩展讲synchronized和ReentrantLock的区别(三个区别)。或者扩展讲ReentrantLock如何实现可重入。
多个任务的编排可以怎么做?项目用到了 CompletableFuture 吗?