Java多线程复习

1. 进程与线程的区别 ?

进程: 操作系统中程序的一次执行周期(比如登录QQ到退出QQ就伴随着一个进程的创建与销毁)是资源分配的最小单位
线程:线程是进程的一个子任务,是任务分配的最小单位
区别:

  • 创建一个进程的开销比创建一个线程的开销大得多
  • 进程之间通信比线程之间通信复杂的多

2.线程的创建方式

  1. 继承Thread类 覆写run方法 启动用start哦

  2. 实现Runnable接口 覆写run方法 启动用start哦

  3. 实现Callable接口 覆写call方法
    3.1 Callable是具有返回值的
    3.2 启动线程 (RunnableFuture接口实现了Runnable接口和Future接口,FutureTask是RunnableFuture的实现类,也就相当于实现了Runnable接口和Future接口,这个类的构造函数可以传入一个Callable对象,FutureTask类中的get方法可以取得call方法的返回值,利用Thread类中传入一个FutureTask对象启动线程)

  4. 线程池
    4.1 为什么要有线程池?
      4.1.1 重复利用已创建的线程,降低线程创建与销毁带来的损耗 (对于小作业,创建一个线程所耗费的资源和时间极有可能大于作业的实际处理时间)
      4.1.2 统一进行线程分配、调度和监控
    4.2 线程池的继承关系?
    在这里插入图片描述

3. 线程池工作原理?

当一个Runnable或者Callable对象到达线程池时,执行策略如下:
判断当前线程数是否达到了核心线程数,如果没有达到,创建一个线程执行任务,如果达到了核心线程数,则判断线程池中有没有空闲线程,如果有空闲线程的话交给空闲线程处理,如果没有空闲线程,则判断阻塞队列是否已满,如果没有满,将当前线程放入阻塞队列,如果已满,则判断当前线程池的数量有没有到达最大线程数,如果没有创建线程执行任务,如果达到了,执行饱和策略。
饱和策略有四种,JVM默认的饱和策略为AbortPolicy(丢弃、抛出异常)其余的三个为:直接丢弃,也不抛出异常、等待调用者空闲后处理此任务、丢掉阻塞队列中最近的一个任务并执行新任务

阻塞队列也有四种

  • ArrayBlockingQueue 基于数组的有界阻塞队列
  • LinkedBlockingQueue 基于链表的无界阻塞队列
  • SynchronousQueue 不存储元素的无界阻塞队列
  • PriorityBlockingQueue 基于优先级的无界阻塞队列

4. 手工创建线程池?

public ThreadPoolExecutor(
							  int corePoolSize,
                              int maximumPoolSize,  
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
    }
  • corePoolSize 核心线程池大小
  • maximunPoolSize 最大线程池的数量
  • keepAliveTime:线程空闲后的存活时间
  • unit:空闲线程的存活时间单位
  • workQueue :阻塞队列
  • handler 饱和策略
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3, 5, 2000,
                TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());

如上所示核心线程池的大小为3,线程池的最大线程数为5,线程空闲后保持的存活时间是2000ms,阻塞队列为基于链表的阻塞队列。

5.JDK内置的四大线程池 ?

  • Executors.newFixedThreadPool()
    固定大小线程池 阻塞队列为LinkedBlockingQueue
    适合于负载较重的服务器或者执行长期作业
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
线程池的最大线程数 = 核心池的大小 
  • Executors.newCashedThreadPool()
    无大小限制线程池 阻塞队列为SynchronousQueue
    适合于负载较轻的服务器或执行短期异步小任务,但是若执行速度远小于创建速度,或不断创建线程有可能会内存写满
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
无大小限制,线程池的最大线程数为整形的最大值
  • Executors.SingleThreadPool()
    单线程池、执行顺序任务
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
}
线程池的最大线程数 = 核心池的大小 = 1
  • Executors.ScheduledThreadPool()
    定时调度池、阻塞队列为LinkedBlockingQueue,执行定时任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}


6.线程的常用方法?

  • sleep() 线程休眠,调用sleep()方法后线程立马交出CPU从运行状态转为阻塞状态,当方法结束后,从阻塞状态转为就绪状态等待CPU调度、不会释放对象锁
  • yield() 线程让步 调用sleep()方法后线程不会立马交出CPU,不会释放对象锁,只是给相同优先级的线程获取CPU的机会。 调用yield线程会从运行状态到就绪状态
  • join() 线程等待,当前线程等待别的线程执行完毕在恢复执行,会释放对象锁(底层是wait)调用yield线程会从运行状态到阻塞状态
  • interrupted()将当前线程的状态置为中断状态
    • 当线程中有阻塞方法sleep、join、wait时,会抛出中断异常,可以在catch块中处理
    • 当线程中没有阻塞方法时,只是将中断状态置为true
  • wait()/notify() notifyAll() 线程的等待及唤醒,会释放对象锁,必须在同步代码块中使用

7.线程同步

synchronized关键字
在这里插入图片描述

7.1 synchronized的底层原理

对象的monitor机制

获取一个对象的锁即是获取这个对象的monitor,当一个线程访问同步块或者同步方法的时候,首先会检测对象的monitor

  • 若monitor的值为0,表示此时monitor没有被线程持有,则将该对象的持有线程线程改为自己,monitor加1,执行同步代码块。
  • 若monitor的值不为0,则判断该锁的持有线程是不是自己,如果是执行同步代码块,monitor继续加1,如果不是则将该线程阻塞。

7.2 synchronized的优化

  1. CAS(V,O,N) 比较交换

V是内存中实际存的值 0是期望的值/旧值 N是期望更新后的值

如果V和N的值相等则可以将N的值替换为V 如果V和N的值不相等,则替换失败

  1. 偏向锁(JDK1.6之后的默认锁) “乐观锁” 任意时刻只有一个线程获取锁
    当第一次获取锁时,CAS将对象头中的偏向锁ID指向自己,当再次获取锁时,只需要检测对象头中的ID是否偏向自己,如果是执行同步代码块
    当不同时刻有不同的线程获取锁时,偏向锁会升级成轻量级锁
  2. 轻量级锁 “不同时刻有不用的线程获取锁”
    轻量级锁每次获取锁时都需要加锁解锁
    当同一时刻有多个线程尝试获取锁时,轻量级锁会膨胀为重量级锁
  3. 重量级锁的自适应自旋
    获取重量级锁失败的线程并不是立即进入阻塞态,而是自旋一段时间,若在此时间内获取到锁,则下次的等待时间就长一点,若在此时间内没有到锁,则下次自旋时间就短一点。
  4. 锁粗化
    将多个连续的加锁与解锁过程粗化为一次范围大的加锁与解锁,减少因为加锁解锁带来的开销
    eg:多个append连接在一起
  5. 锁消除
    在不会出现锁竞争的场景下,会将线程安全的集合或类中的锁消除

7.3 Lock锁

  1. 死锁的生成 (以下四种情况同时满足会造成程序死锁)
  • 互斥 共享资源只能被一个线程占有
  • 占有且等待 线程A已经取得资源X,在等待资源Y的时候不释放X
  • 不可抢占 线程A获得X后,其他线程不可抢占X
  • 循环等待 线程A占有X、线程B占有Y 线程A等待Y,线程B等待X

如何解决? 解决时只需要破坏其中一个条件即可
Lock锁的出现解决了死锁的问题

  • lockInterruptibly() 响应中断
  • trylock(long time,TimeUnit unit) 支持超时
  • trylock() 非阻塞式获取锁

synchronized和ReentrantLock的区别与联系?

  • 都属于独占锁(任何时候只有一个线程获取锁)、支持可重入的特性
  • 区别?
    • synchronized属于JVM层面、ReentrantLock属于Java语言层面
    • ReentrantLock有一些synchronized不具备的特性例如支持响应中断、支持超时获取锁、支持非阻塞时获取锁、支持公平锁(ReentrantLock的构造方法传入true)、支持多个等待队列

synchronized和Lock的区别与联系?

  • 都支持独占锁的实现,支持可重入的特性,除此之外Lock中还有读写锁,其中读锁属于共享锁的实现
  • 区别?
    • synchronized属于JVM层面、Lock属于Java语言层面
    • Lock有一些synchronized不具备的特性如支持响应中断、支持超时获取锁、支持非阻塞式获取锁、还可以实现公平锁、支持多个等待队列、支持读写锁
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值