多线程学习笔记-2
死锁
死锁产生的条件
- 互斥:资源同一时间只能被一个线程使用
- 请求和保持:当前线程已经持有一个资源,同时申请另一个资源
- 不可剥夺:资源只能被持有的线程自己释放,不能被其他线程释放
- 循环等待:多个线程等待的资源形成一个环
避免死锁的方法
破坏死锁产生的4个条件之一。
- 互斥条件:不能破坏,因为本来就是要锁住资源,不让多个线程同时操作
- 请求和保持:一次性申请所有资源
- 不可剥夺:线程进一步申请资源,申请不到时,主动释放自己持有的资源
- 循环等待:都按相同顺序申请资源
例子
public class Main{
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()->{
synchronized (a){
System.out.println(Thread.currentThread().getName()+"持有资源a");
try { Thread.sleep(100); } catch (Exception ignore) {}
synchronized(b){
System.out.println(Thread.currentThread().getName()+"持有资源b");
}
}
}).start();
new Thread(()->{
synchronized (b){
try { Thread.sleep(100); } catch (Exception ignore) {}
System.out.println(Thread.currentThread().getName()+"持有资源b");
synchronized(a){
System.out.println(Thread.currentThread().getName()+"持有资源a");
}
}
}).start();
}
}
线程的生命周期
NEW
新建状态,线程被创建,但是还没有调用start方法
RUNNABLE
运行状态,java线程将操作系统的RUNNING和READY两种状态笼统称作RUNNABLE
BLOCKED
阻塞状态,等待获取锁
WAITING
等待状态,调用Object的wait,Thread的join方法进入,被其他线程通知返回
TIME_WAITING
超时等待,调用Thread的sleep(t)和join(t),Object的wait(t)进入,时间到了或者被notify和notifyAll返回
TERMINATED
终止状态,线程执行完毕
状态切换图

区别和联系
sleep和wait
区别
sleep是Thread的静态类方法,wait是Object的对象方法
sleep不释放锁,wait释放锁
联系
sleep和wait都会让出cpu的执行
线程和进程
一个进程包含多个线程,线程是比进程更小的执行单元,多个线程共享进程的堆和方法区资源,但是每个线程都有自己的程序计数器、虚拟机栈、本地方法栈
并发和并行
并行是同一时间同时执行多个线程,并发不是同时执行
概念
上下文切换理解
线程数大于cpu数目,同一时间cpu上只执行一个线程,为了让用户感觉同时执行,cpu使用时间片轮转策略执行线程,获取时间片的线程用完自己的时间后,就会进入就绪状态,为了下次执行时知道上次执行到哪里了,需要保存现场以备恢复执行,这就是上下文切换
synchronized
用法
修饰代码块,静态方法(和修饰class一样都是锁定的类),实例方法
A a=new A();
synchronized(this){}
synchronized(A.class){}
synchronized(a){}
synchronized void f(){}
synchronized static void f(){}
原理
对于同步代码块,将java文件编译成class后,可以发现在同步代码块开始的地方有monitorenter指令,结束时有monitorexit指令,运行到monitorenter指令时线程会试图获取对象的监视器锁,一旦拿到锁,会将锁计数器+1,线程继续执行,其他线程只能等待,执行到monitorexit后,将锁计数器-1
对于被修饰的方法,编译后会有ACC_SYNCHRONIZED标识,JVM通过这个标识进行同步调用
synchronized和reentrantLock的区别和联系
联系
两者都是可重入锁
区别
- synchronized是基于JVM层面实现的,是java关键字,ReentrantLock是jdk层面实现的,是api
- ReentrantLock使用更加灵活,可以选择通知,可以创建公平锁,可以等待中断
线程池
ThreadPoolExecutor
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
//核心线程数,最小可同时运行的线程数,当任务数不大于这个值时直接启动这么多个线程,超过这个数值会进入阻塞队列
2,
//最大线程数,当阻塞队列也满了时,会增加线程直到达到这个值
5,
//存活时间,空闲线程能存活的时间
3,
TimeUnit.SECONDS,
//阻塞队列,当任务数超过corePoolSize时,任务会进入这里等待
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
//4种拒绝策略,AbortPolicy丢弃且抛异常,DiscardPolicy丢弃不抛异常,DiscardOldestPolicy丢弃最早未处理的任务,CallerRunsPolicy把任务给调用者自己执行
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 0; i < 8; i++) {
int finalI = i;
System.out.println(threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName());
return finalI;
}).get());
}
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
Executors
容易OOM,推荐使用ThreadPoolExecutor
Executors.newSingleThreadExecutor();//只有一个线程的线程池,多的放入阻塞队列,队列长度Integer.MAX_VALUE
Executors.newFixedThreadPool(5);//核心线程数和最大线程数均为5线程池,队列长度Integer.MAX_VALUE
Executors.newCachedThreadPool();//最大线程数Integer.MAX_VALUE,有空闲的线程会复用

浙公网安备 33010602011771号