多线程相关知识点

创建线程

  1. 继承Thread类

  2. 重写run方法

  3. 调用start方法

image-20201212154546078

runnable使用了静态代理模式

  • 真实对象和代理对象都要实现同一个接口

  • 代理对象要代理真实角色

thread

  • 自定义线程类继承Thread类

  • 重写run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程

Runnable

  • 实现接口Runnable 具有多线程能力

  • 启动线程:传入目标对象+Thread对象.start()

  • 比较推荐使用,避免单继承的局限性,灵活方便,方便再同一个对象被多个线程使用

静态代理模式

  • 真实对象和代理模式都要实现同一个接口

  • 代理对象要代理真实角色

停止线程

  1. 建议线程正常停止--利用次数不建议死循环

  2. 使用标志位,设置一个标志位

  3. 不要使用stop或者destroy等过时的方法

private boolean flag=true;//设置标志位    public static void main(String[] args) {//主函数入口  TestThread1 testThread1=new TestThread1();  new Thread(testThread1).start();  for (int i = 0; i < 1000; i++) {  System.out.println("main"+i);  if(i==400) {//到达某个值,将标志位置为假,以此来停止线程  testThread1.Stop();  System.out.println("线程停止了"); } } }  @Override  public void run() {  int i=0;  while (flag){  System.out.println("我还在运行"+i++); } }  public void Stop(){ //停止线程方法  this.flag=false; }

线程休眠sleep

  • sleep 指定当前线程阻塞的毫秒数

  • sleep存在异常,需要捕获

  • sleep时间到达后线程进入就绪状态

  • sleep可以模拟网络样式,倒计时等

  • 每个对象有一个锁,sleep不会释放锁

线程礼让-yield

  • 礼让线程,让当前正在执行的线程暂停,但不堵塞

  • 将线程从运行状态转为就绪状态

  • 让cpu重新调度,礼让不一定成功

线程状态

  • new(新生)、runnable、bloked、waiting、time_waiting、terminated(退出)

线程的优先级

  • java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行

守护(daemon)线程

  • 线程分为用户线程和守护线程

  • 虚拟机不用等待守护线程执行完毕

  • 如,后台记录操作日志,监控内存,垃圾回收等待

public class TestDaemon {   public static void main(String[] args) {  God god = new God();  You you = new You();  Thread thread1=new Thread(god);  thread1.setDaemon(true);//设置为守护线程,默认为flase  thread1.start();  new Thread(you).start();  } } class God implements Runnable{  @Override  public void run() {  while (true){  System.out.println("上帝保佑你~~~");  }  } } class You implements Runnable{  @Override  public void run() {  for (int i = 0; i < 1000; i++) {  System.out.println("我开心的活着~~");  }  } }

线程同步 -重点

  • 加入锁机制 synchronized

形成条件:队列+锁

同步方法

public synchronized void method(){   }
  • 缺陷:将一个大的方法声明为synchronized会影响效率

同步块

synchronized(obj){ }

  • synchronized默认锁的是本身

锁 lock
lock.lock();//加锁 lock.unlock; //解锁

  • 注意:wait() 会释放锁, 而sleep不会释放锁

死锁的四个必要条件

  1. 互斥条件

  2. 请求和保持条件

  3. 不剥夺条件

  4. 循环等待条件

管程法

线程池


8锁现象

  1. synchronized锁方法时,锁的是方法的调用者

    如:同一对象调用两个不同的同步方法,也得按顺序执行

  2. static加上synchronized会导致锁的变成是类对象

  3. 如一个静态同步方法,一个普通同步方法,由于第一个锁的是class模板,第二个锁的是调用的对象,是不同的锁,因此不需要等待

集合类不安全

写入时复制--优化的策略,效率降低不会太多

写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想 是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共 同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用 副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的 调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本 (private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array

hashset 底层是hashmap

Callable实现原理 使用适配类-适配器模式

三个工具类

减法计数器-加法计数器-信号量

读写锁

阻塞队列

同步队列

线程池

考点:三大方法、七大参数、四种拒绝策略

三大方法

三种线程池各有什么特点‘

七大参数

corePollSize :核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建 线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建 一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当 中。

maximumPoolSize :最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于1。

keepAliveTime :空闲的线程保留的时间。

TimeUnit :空闲线程的保留时间单位。 BlockingQueue< Runnable> :阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、 LinkedBlockingQueue、SynchronousQueue可选。 ThreadFactory :线程工厂,用来创建线程,一般默认即可

RejectedExecutionHandler :队列已满,而且任务量大于最大线程的异常处理策略。

四大函数接口

image-20201214105400168

Stream流式计算

分支合并-Forkjoin

其中,工作窃取可以提高效率

异步回调

JMM

可见性--让其他线程可以看到主内存发生的变化

JMM八大指令

lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量 才可以被其他线程锁定

read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便 随后的load动作使用

load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机 遇到一个需要使用到变量的值,就会使用到这个指令

assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的 变量副本中

store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存 中,以便后续的write使用

write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内 存的变量中

a++不是原子性操作

可以使用原子类解决原子性问题

深入单例模式

  • 饿汉式

    一开始就将对象创建出来

  • 懒汉式

    当有线程调用时再创建对象

  • DCL懒汉式

    增加双层锁验证--

    image-20201214173110488

    不是原子性操作,至少会经过三个步骤:

    1. 分配对象内存空间

    2. 执行构造方法初始化对象

    3. 设置instance指向刚分配的内存地址,此时instance !=null;

由于指令重排,导致A线程执行 lazyMan = new LazyMan();的时候,可能先执行了第三步(还没执行第 二步),此时线程B又进来了,发现lazyMan已经不为空了,直接返回了lazyMan,并且后面使用了返回 的lazyMan,由于线程A还没有执行第二步,导致此时lazyMan还不完整,可能会有一些意想不到的错 误,所以就有了下面一种单例模式。

  • 下一种在上面DCL单例模式增加一个volatile关键字来避免指令重排:

  • 静态内部类版--会被反射破坏

  • 使用枚举--最终版

深入理解CAS

简单的说:比较当前值是否还是原来的,如果为true,则修改

CAS:比较并交换 --compareAndSet

核心Unsafe类

image-20201214190359470

底层使用了自旋锁,

缺点:

  1. 循环会耗时

  2. 一次性只能保证一个共享变量的原子性

  3. ABA问题

CAS:ABA问题

简单说:线程A读取了内存某块数据,接着线程B也读取了这块内存数据并将该值改了,后面又改了回来。这时候A线程发现数据还是读取时的数据,因此更新数据,而不知道再读取后这块内存已经被修改过--

解决办法

增加版本号-类似乐观锁的方式

版本号原子引用

坑:如果约定的泛型时包装类,要注意引用的对象是否相同!!!

正常业务中比较的是业务的对象,不存在引用不同的问题

Java各种锁

公平锁

线程按照申请锁的顺序执行,不存在插队

默认是非公平锁

可重入锁

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法 获取锁的时候,在进入内层方***自动获取锁。

自旋锁

例子:原子引用

死锁

产生死锁主要原因:

1、系统资源不足

2、进程运行推进的顺序不合适

3、资源分配不当

解决方法:

  1. 使用 jsp -l 定位进程号

  2. 使用 jstack +进程号 查看堆栈信息

#Java##学习路径#
全部评论

相关推荐

3 27 评论
分享
牛客网
牛客企业服务