二者对比:
新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、时间等待(TIMED_WALTING)、终止(TERMINATED)
可以使用线程中的join方法解决
join() 等待线程运行结束
public class ThreadTest {
public static void main(String[] args) {
//创建线程1
Thread t1 = new Thread(()->{
System.out.println("t1");
});
//创建线程2
Thread t2 = new Thread(() -> {
try {
//加入线程t1,只有t1线程执行完毕之后,才会执行此线程
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
});
//创建线程3
Thread t3 = new Thread(()->{
try {
//加入t2线程,只有当t2线程执行完毕之后,才会执行这个t3线程
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
});
//启动线程
t1.start();
t3.start();
t2.start();
}
}
共同点:
wait()、wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
不同点:
1)方法归属不同:
2)醒来时机不同:
3)锁特性不同:
有三种方式可以停止线程
1)synchronized对象锁采用互斥的方式让同一时刻至多有一个线程能持有对象锁
2)它的底层是由Monitor实现的,Monitor是JVM级别的对象(C++实现),线程获得锁需要使用对象(锁)关联Monitor
3)在Monitor内部有三个属性,分别是owner、entrylist、waitset
1)JMM指的是Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性。
2)JMM把内存分为两块,一块是私有线程的工作区域(工作内存),一块是所有线程的共享区域(主内存)
3)线程跟线程之间是相互隔离,线程跟线程交互需要通过主内存
1)保证线程间的可见性
用volatile修饰共享变量,能够防止编译器等优化发生,让一个线程对共享变量的修改对另一个线程可见
2)进制进行指令重排序
指令重排:用volatile修饰共享变量会在读、写共享变量时加入不同的屏障,组织其他读写操作越过屏障,从而达到阻止重排序的效果。
1)ASQ是多线程中的队列同步器,是一种锁机制,它是作为一个基础框架使用的,像ReentrantLock、Semaphore都是基于AQS实现的。
2)AQS内部维护了一个先进先出的双向队列,队列中存储的排队的线程
3)在AQS内部还有一个属性state,这个state就相当于是一个资源,默认是0(无锁状态),如果队列中有一个线程修改成功了state为1,则当前线程就相当于获取了资源
4)在对state修改的时候使用的cas操作,保证了多个线程修改的情况下的原子性
既可以是公平锁又可以是非公平锁
语法层面:
synchronized是关键字;源码在JVM中,用C++语言实现
Lock是接口,源码由JDK提供,用Java语言实现
使用synchronized时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁
功能层面:
二者均属于悲观锁,都具备基本的互斥,同步,锁重入功能
Lock提供了许多synchronized不具备的功能,例如公平锁,可打断,可超时,多条件变量
Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock(读写锁)
性能层面:
在没有竞争时,synchronized做了很多优化,比如偏向锁、轻量级锁、性能不赖
在竞争激烈时,Lock的实现通常会提供更好的性能
一个线程需要同时获取多把锁,这时就容易发生死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都会停止执行的情况,某一个同步块同时拥有“两个以上对象的锁”时,就有可能发生死锁的问题
也就是多个线程互相拿着对方需要的资源,然后形成僵持
1)底层数据结构:
2)加锁的方式:
1.原子性 synchronized、lock解决
2.内存可见性 volatile、synchronized、lock解决
3.有序性 volatile解决
ArrayBlockQueue和LinkedBlockingQueue的区别?
1)IO密集型任务
一般来说:文件读写、DB读写、网络请求等。核心线程数大小设置为2N+1
2)CPU密集型任务
一般来说:计算型代码,bitmap转换,Gson转换等。核心线程数大小设置为 N+1
N指的是CPU的核数
①、高并发、任务执行时间短----->CPU核数 + 1,减少线程上下文的切换
②、并发不高、任务执行时间长
1.IO密集型的任务------>CPU核数*2 + 1
2.计算密集型任务------->CPU核数 + 1
③、并发高、业务执行时间长,解决这种问题的关键不在线程池而在于整体架构的设计的优化,对架构和业务做优化
①、newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
②、newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(先进先出)执行
③、newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
④、newScheduledThreadPool:可以执行延迟任务的线程池,支持定时及周期性任务执行
参考阿里Java开发手册
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE:可能会堆积大量的请求,从而导致OOM
2)CachedThreadPool:
允许的创建线程数量为:integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM