多线程学习笔记-2

死锁

死锁产生的条件

  1. 互斥:资源同一时间只能被一个线程使用
  2. 请求和保持:当前线程已经持有一个资源,同时申请另一个资源
  3. 不可剥夺:资源只能被持有的线程自己释放,不能被其他线程释放
  4. 循环等待:多个线程等待的资源形成一个环

避免死锁的方法

破坏死锁产生的4个条件之一。

  1. 互斥条件:不能破坏,因为本来就是要锁住资源,不让多个线程同时操作
  2. 请求和保持:一次性申请所有资源
  3. 不可剥夺:线程进一步申请资源,申请不到时,主动释放自己持有的资源
  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

终止状态,线程执行完毕

状态切换图

image

区别和联系

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,有空闲的线程会复用
posted @ 2021-04-10 21:40  我吃水煮虾  阅读(88)  评论(0)    收藏  举报