JUC学习笔记(以往知识点补充)

一、以往知识补充

1.程序、进程、线程之间的区别

程序:指令和数据的有序集合,其本身没有任何运行的含义,静态概念
进程:程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
线程:一个进程至少包含一个线程,否则无存在的意义。CPU调度和执行的单位,也就是cpu时间片是根据所有总线程数来分的,一个进程线程越多,所占的cpu处理时间就相对越多。

2.线程的四种创建方式

  • 继承Thread类;
  • 重写run方法并实例化子类的对象t,t.start()开启线程;
  • 实现Runnable接口,重写run方法,new Thread(runnable).start()
  • (本身Thread类也实现了Runnable接口,推荐使用,避免单继承)
  • 实现Callable接口,重写call方法(返回值)+ FuterTask
  • FuterTask需要传递Callable,实现了Runnable
  • new Thread(futureTask).start()

使用线程池:

  • ExcutorService service = Executors.newFixedThreadPool(10);
  • service.execute(runnable)
  • service.submit(callable)、service.submit(runnable)

注意 获得当前线程名字: Thread.currentThread().getName()
线程睡眠:Thread.sleep()

3.静态代理模式

什么是静态代理模式

  • 真实对象和代理对象都需要实现同一个接口
  • 代理对象 绑定 真实对象的调用方法的基础上 ,不在由真实对象调用方法,而是 代理对象间接 、调用 方法 + 其他代理方法
  • 优点就是 代理对象可以完成 真实对象 不能完成的方法,从而让真实对象专注一个自己的方法。

Runnable和Thread和静态代理之间的关系

  • 线程底部实现原理就是 静态代理模式
  • Thread可以理解为代理类,代理真实接口Runnable,真实对象的实例runnable 作为 thread 代理对象的参数,代理对象调用方法

4.Lambda表达式

演化概念

  • 函数式接口:接口中只有一个方法,有了函数式接口才能使用Lambda
  • 实现类需要重写该方法,实现类重写方法定义的位置一般同级实现类
  • 静态内部类:类内方法外,static修饰
  • 局部内部类:方法内
  • 匿名内部类:直接实例化接口,在接口中重写方法
  • 使用Lambda:格式为 接口名 对象 = ()=> { //重写方法}、接口名 对象 = (参数1)=> { //重写方法},简化
    • 简化括号 :当参数为一个时,括号可以省略接口名 对象 = a => { //重写方法}
    • 简化花括号:当语句只有一条时,可以省略花括号 接口名 对象 = a => //重写方法

5.线程状态

  • 创建状态
  • 就绪状态
  • 阻塞状态
  • 运行状态
  • 死亡状态

image.png

线程方法

  • setPriority(int priority) : 更改线程优先级
  • static void sleep(long millis) :线程休眠
  • void join() :线程强制执行
  • static void yield():终止当前正在执行的线程,并执行其他线程
  • void interrupt():终止线程,不推荐使用,建议使用标志位、次数使线程自己终止
  • boolean isAlive() : 测试线程是否存活

注意

  • Thread的stop和destory终止线程的方法已经过时,一般使用 自定义标识变量flag 控制线程结束
  • sleep:每个对象都有一个锁,sleep不会释放锁
  • yield:线程礼让,让cpu重新调度,但是不一定成功
  • join:等到此线程结束完后,在执行其他线程,其他线程阻塞
  • getState()方法可以查看当前线程的状态
  • setPriority(优先级):线程优先级范围1-10,主线程默认优先级为5,大多数情况是优先级高的线程先被调度。
  • setDaemon(true)设置守护线程:垃圾回收线程不用等待守护线程执行完毕,如后台纪录操作日志、监控内存、垃圾回收GC

6.线程同步

概念

  • 并发:多个线程操作一个资源

  • 处理多线程问题需要线程同步,就是等待机制

  • 队列和锁,保证同步队列的安全,同一进程内的多个线程共享同一块存储空间,为了保证数据在方法中被访问的正确性,访问时给数据加上锁机制synchorizoned

  • 加锁出现的一些问题

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起

  • 在多线程的竞争下,加锁释放锁会导致比较多的上下文切换和调度延迟,引起性能问题

  • 如果一个线程优先级高的线程等待一个线程优先级低的线程释放锁,会导致 优先级倒置,引起性能问题

  • 集合线程不安全的问题

  • ArrayList集合是线程不安全的集合,如,模拟1000个线程像其中添加数据,最后测试集合 小于 1000 个元素,因为多个线程同时添加的是一个位置,完成值的覆盖。

测试JUC安全类型的集合CopyOnWriteArrayList

image.png

7.同步synchorizoned 关键字

为了解决同步问题

同步方法:

  • syncchorizoned void xxx 控制对 对象 的访问,每个对象都对应一把锁,调用线程需要获得这个锁,否则就会线程阻塞。执行完方法,就会释放该锁
  • 缺点:会大大影响效率,方法不仅有写,还会有读,而读并不需要加锁,因此引出同步代码块
  • 同步代码块 synchorizoned(obj) {xxxx}

8.死锁、Lock锁

概念

  • 死锁:多个相乘各自占有一些共享资源,并且互相等待对方占用的资源,都停止运行的情况,当一个同步块拥有 两个以上 锁对象 就可能发生 死锁 问题
  • Lock锁,JUC下的包,JDK5.0提供了个更强大的线程同步机制通过显示的定义同步锁对象 来实现同步。同步锁使用Lock独享充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前鲜活的Lock对象
  • ReentrantLock可重入锁实现了Lock,拥有与Synchorizoned相同的并发性和内存语义,在实现线程安全的控制中,较为常用,可以显式加锁、释放锁。

Synchorizoned和Lock的对比

  • Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁、方法锁
  • Lock下JVM花费较少的时间调度进程,性能更好,并且由更好的拓展性(提供更多的子类)
  • 优先使用顺序:Lock > 同步代码块 > 同步方法

9.线程协作(线程之间的通信)

概念

线程之间需要通信 场景就是生产者消费者模式,生产者和消费者共享一个资源,并且生产者和消费者之间互相依赖,互为条件 通信方法

  • wait(): 线程一直等待,直到其他线程通知,与sleep不同的是会释放锁
  • wait(long timeout):指定等待的毫秒数
  • notify():唤醒一个正在wait的线程
  • notifyAll():唤醒ton规格对象上所有调用wait()方法的线程,优先级别高的线程优先调度

解决方式

并发协作模型 “生产者、消费者模式” 管程法

  • 生产者:负责生产数据的模块
  • 消费者:负责处理数据的模块
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间加一层缓冲区,缓存区 数据个数 控制 两个线程,中间加一层,里面根据逻数据个数+辑进行wait和notify

并发协作模型 “生产者、消费者模式” 信号灯法

  • 生产者:负责生产数据的模块
  • 消费者:负责处理数据的模块
  • 信号标记灯:中间加一层,里面根据逻辑采用flag进行wait和notify

管程法

image.png

Guess you like

Origin juejin.im/post/7047313557225996325