1、基本概念

  1、共享资源

  多个线程对同一份资源进行访问(读写操作),该资源被称为共享资源。如何保证多个线程访问到的数据是一致的,则被称为数据同步或资源同步。

  2、线程通信

  线程通信,又叫进程内通信,和网络通信等进程间通信不同;多个线程实现互斥资源访问的时候,会互相发送信号。

  2、同步、异步、阻塞、非阻塞

  同步和异步是 获取结果的方式,阻塞和非阻塞是 等待结果中是否能够完成其他事情。

  同步阻塞(BIO),需要等待读取完客户端的数据,同时需要阻塞的判断客户端是否有数据。

  同步非阻塞(NIO),需要等待读取完客户端的数据,但是轮询的方式判断客户端是否有数据,可以去做其他事情。

  异步阻塞,等待结果回调通知,cpu处于等待的休眠中。

  异步非阻塞(AIO),操作系统来完成读取的操作,读取完毕通知回调,使用线程池的方式去轮询客户端是否有数据,可以去做其他事情。

  并发,单个 cpu 可以处理多个任务,但是同一时刻只有一个任务在运行。

  并行,多个任务在多个 cpu 上运行,实现真正的同一时刻运行。

  2、生命周期

  3、守护线程

  一般使用 new Thread 创建的线程都是非守护线程,也称用户线程。设置为守护进行的方式就是,在 run 之前,调用setDaemon(true)。守护线程,仅仅是为用户线程提供服务,那么一旦所有的用户线程都运行完毕,守护进行也会结束。相反,只要有非守护线程存在,那么守护线程就不会终止。

  守护线程的应用场景:垃圾回收、心跳检测、拼音检查线程

  package com.vim;

  import java.util.concurrent.TimeUnit;

  public class App {

  public static void main( String[] args ) throws Exception{

  Thread t = new Thread(()->{

  while (true){

  try {

  TimeUnit.SECONDS.sleep(1);

  System.out.println("......");

  }catch (Exception e){

  e.printStackTrace();

  }

  }

  });

  //只有t设置了此处,才会随着父进程的退出而退出

  t.setDaemon(true);

  t.start();

  TimeUnit.SECONDS.sleep(5);

  System.out.println("main is over!");

  }

  }

  4、线程 yield、sleep

  yield,称为线程让步,从 RUNNING 状态转为 RUNNABLE 状态,有可能切换之后,再次抢到执行权,进入 RUNNING 状态。

  sleep,会阻塞该线程,进入 BLOCKED 状态,此时是挂起,让出cpu;只有阻塞时间到了,才会进入 RUNNABLE 状态,并不一定马上获取到 cpu 的执行权。

  sleep(0) 的作用是,触发操作系统立刻重新进行一次CPU竞争,竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。

  package com.vim;

  public class App {

  public static void main( String[] args ) throws Exception {

  new Thread(()->{

  try {

  while (true){

  Thread.sleep(0);

  }

  }catch (Exception e){

  e.printStackTrace();

  }

  }).start();

  }

  }

  5、线程 interrupt

  wait、sleep、join 使当前线程进入阻塞状态,而 interrupt 可以打断阻塞,不过仅仅是打算当前的阻塞状态。当前线程会抛出一个 InterruptException 异常,就像一个信号通知。此通知会将 interrupt flag 置为 true,不过针对阻塞状态下的中断,该状态位会被重置为 false。通过 thread.isInterrupted() 来判断,当然,阻塞状态下的,会被清除,从而影响该方法的结果。

  package com.sample.modules.test;

  import java.util.concurrent.TimeUnit;

  public class Test {

  //中断一个线程,中断位 true

  public static void test1() throws Exception{

  Thread t = new Thread(()->{

  //2.获取当前的 interrupt flag 状态为 true

  System.out.println(Thread.currentThread().isInterrupted());

  });

  t.start();

  //1.中断线程

  t.interrupt();

  TimeUnit.MINUTES.sleep(4);

  }

  //中断一个线程,中断位 false

  public static void test2() throws Exception{

  Thread t = new Thread(()->{

  //2.清除中断位

  Thread.interrupted();

  //3.获取当前的 interrupt flag 状态为 false

  System.out.println(Thread.currentThread().isInterrupted());

  });

  t.start();

  //1.中断线程

  t.interrupt();

  TimeUnit.MINUTES.sleep(4);

  }

  //中断一个线程,中断位 false

  public static void test3() throws Exception{

  Thread t = new Thread(()->{

  //2.中断wait、sleep、join导致的阻塞

  try {

  TimeUnit.SECONDS.sleep(5);

  }catch (InterruptedException e){

  System.out.println("i am interrupted");

  }

  //3.获取当前的 interrupt flag 状态为 false

  System.out.println(Thread.currentThread().isInterrupted());

  });

  t.start();

  //1.中断线程

  t.interrupt();

  TimeUnit.MINUTES.sleep(4);

  }

  //阻塞之前中断结果:false、true、i am interrupted、false

  public static void test4(){

  Thread.interrupted();

  System.out.println("interrupt flag: "+Thread.currentThread().isInterrupted());

  Thread.currentThread().interrupt();

  System.out.println("interrupt flag: "+Thread.currentThread().isInterrupted());

  try {

  TimeUnit.SECONDS.sleep(1);

  }catch (InterruptedException e){

  System.out.println("i am interrupted");

  }

  System.out.println("interrupt flag: "+Thread.currentThread().isInterrupted());

  }

  public static void main(String[] args) throws Exception{

  test3();

  }

  }

  6、线程 join

  parent 线程调用 child 线程的 join 方法,实际上调用的是 join(0) ,该方法加了锁,循环判断 child 线程的存活状态,当然,每次循环中调用 wait(0) 方法,这样的话可以让其他线程也进入 join(0) 方法。

  //当前方法没有上锁

  public final void join() throws InterruptedException {

  join(0);

  } 无锡看男科医院哪家好 https://yyk.familydoctor.com.cn/20612/

  //此方法上锁,此时其他线程可以进入 join(),但不可以进入 join(0)

  //不断的检查线程是否 alive,调用 wait(0),这样就释放了锁,其他的线程就可以进入 join(0),也就是可以有多个线程等待某个线程执行完毕

  //一旦线程不在 alive,那么就会返回到 join() 方法,调用的线程就可以继续执行下去

  public final synchronized void join(long millis){

  if (millis == 0) {

  while (isAlive()) {

  wait(0);

  }

  }

  }

  7、线程通知 notify、wait

  这两个方法,来源于 Object 类,两者配合使用。wait 方法属于对象方法,在调用之前必须先获取该对象的 monitor 锁,调用之后,就会释放该对象的 monitor 锁,从而进入该对象关联的 waitset 中,等待其他线程使用 notify 唤醒。

  典型的生产消费场景:

  生产者,在生产产品时,对仓库(同步资源)进行上锁,如果当前仓库没有满,则放入产品,使用 notifyAll 通知消费者;如果当前仓库满了,则使用 wait 释放锁,进入 waitset 阻塞等待 notifyAll 通知。

  消费者,来到仓库消费,对仓库(同步资源)进行上锁,如果当前仓库有产品,则拿走产品,使用 notifyAll 通知生产者;如果当前仓库是空的,则使用 wait 释放锁,进入 waitset 阻塞等待 notifyAll 通知。

  当 notifyAll 来临的时候,针对所有的生产者和消费者来说,都有拿到仓库钥匙的机会,就会再去竞争,再次进入以上判断逻辑。

  8、wait 和 sleep 的区别

  相同之处:

  使线程进入到阻塞状态;可以被 interrupt 中断;

  不同之处:

  wait 是 Object 共有,sleep 是 Thread 特有。

  wait 必须运行在同步方法中,sleep不需要。

  wait 会释放锁,如果sleep放在同步方法中,并不会释放锁。

  9、线程异常处理

  package com.sample.modules.test;

  public class Test {

  public static void main(String[] args) throws Exception{

  Thread t = new Thread(()->{

  int i = 1/0;

  });

  t.setUncaughtExceptionHandler((thread, e)->{

  System.out.println("exception...");

  });

  t.start();

  }

  }

  10、计算机内存模型 和 Java 内存模型

  原理追溯:cpu 在执行指令的时候,数据来源于主内存(RAM),由于两者速度的严重不对等,之间出现了缓存 cache;一般分为 L1、L2、L3 缓存,每个 CPU 核心包含一套 L1,共享 L2 和 L3 缓存。

  缓存一致性问题:当多个处理器的运算任务都涉及同一块主内存区域时,每个 cpu 从主内存中取出变量,放到本地 cache 中,进行计算之后,写入到 cache 中,再由 cache 刷新到主内存中。

  读取主内存 i 到 cache 中

  对 i 进行 ++

  将结果写回 cache

  将 cache 刷新回主内存。

  缓存一致性协议:cpu 在操作 cache 中的数据时,如果发现是一个共享变量,那么在写入的时候,会发出信号通知其他的 cpu 将该变量的 cache line 置为无效状态,其他 cpu 在进行该变量读取的时候,就需要去主内存中读取。

  相比计算机内存模型,Java 内存模型: 线程 == CPU, 工作内存 == CPU cache,主内存 == 主内存。

  12、并发编程三大特性

  原子性,多次操作中,要么全部得到执行,要么全部不执行。

  可见性,一个线程对共享变量,作了修改,那么其他线程立即可以看到修改后的值。

  有序性,程序代码在执行过程中的先后顺序。

  13、synchronized 关键字

  synchronized 关键字提供了一种锁的机制,能够保证共享变量的互斥访问,即同一时刻,只能有一个线程访问同步资源。

  内存方面,monitor enter 和 monitor exit 两个 JVM 指令,保证了任何线程在 monitor enter 之前必须从主内存中获取数据,在 monitor exit 之后,必须把更新的值刷新到主内存中。这两个 JVM 指令,严格的遵守 happends-before 原则,即一个 monitor exit 指令之前必须有一个 monitor enter 指令存在。

  该关键字对于同步资源的排他性访问,很有效,但是其他没有获取到 monitor 的线程,到底阻塞多久,能不能提前解除阻塞,这些都是未知的。为此,java 为我们提供了其他的解决方案,显式锁。如 ReentrantLock。

  14、AQS

  独占模式

  #获取

  1、尝试获取资源成功立即返回;失败的话,创建独占节点,利用CAS加入到队列尾部,进入自旋状态

  2、如果前一个节点是头节点,再次尝试获取资源,成功设置为头节点;否则挂起,等待被前驱节点唤醒

  #针对可中断来说

  1、普通的获取,在发生了中断后,会清除中断位,并在获取资源成功后,触发一次中断

  2、可中断的获取,在发生了中断后,也会清除中断位,但是直接抛出 interrupted 异常

  #释放

  1、释放同步状态

  2、获取当前节点的下一个节点,唤醒

  共享模式

  #获取

  1、获取同步状态,如果返回值>=0,则说明同步状态(state)有剩余,获取锁成功直接返回

  2、失败,向队列尾部添加一个共享类型的Node节点,随即该节点进入自旋状态

  3、前驱节点如果为头节点,再次判断同步状态是否(state)有剩余

  4、如果是,则说明当前节点可执行,同时把当前节点设置为头节点,并且唤醒所有后继节点

  两者区别

  1、独占锁的同步状态值为1,即同一时刻只能有一个线程成功获取同步状态;共享锁的同步状态>1,取值由上层同步组件确定

  2、独占锁队列中头节点运行完成后释放它的直接后继节点;共享锁队列中头节点运行完成后释放它后面的所有节点

  3、共享锁中会出现多个线程(即同步队列中的节点)同时成功获取同步状态的情况

  15、重入锁 ReentrantLock

  #获取

  1、如果当前有锁,且拥有者是当前线程,再次增加重入

  2、如果当前无锁,直接尝试获取锁,公平模式会判断是否有等待的线程,非公平模式下直接尝试独占资源

  16、计数器 CountDownLatch

  1、使用的是共享模式,初始化时设置 state 一个固定的数量

  2、await 方法,调用 sync 的中断 acquireShared 方法,重写 tryAcquireShared 获取方式,当 state 到达0的时候,才表示资源可获取

  3、countDown 方法,调用 sync 的 releaseShared 方法,不断的对 state 进行减一

  17、读写锁 ReentrantReadWriteLock

  写锁

  #获取

  1、当前处于无锁状态,独占模式获取写锁

  2、如果设置了 writerShouldBlock,直接返回 false

  1、当前处于有锁状态

  2、有读锁,直接返回 false

  3、有写锁,如果是当前线程,则重入,否则返回 false

  读锁

  1、如果有写锁,并且写锁不是当前线程,返回 -1

  2、如果没有写锁,尝试获取读锁,如果设置了 readerShouldBlock,进入再次判断

Java的基本知识之线程池篇的更多相关文章

  1. C#多线程之线程池篇3

    在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...

  2. C#多线程之线程池篇2

    在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...

  3. Java 1.ExecutorService四种线程池的例子与说明

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...

  4. JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor

    JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...

  5. JUC源码分析-线程池篇(二)FutureTask

    JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...

  6. JUC源码分析-线程池篇(三)Timer

    JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...

  7. JUC源码分析-线程池篇(一):ThreadPoolExecutor

    JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...

  8. C#多线程之线程池篇1

    在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...

  9. java多线程总结五:线程池的原理及实现

    1.线程池简介:     多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.        假设一个服务器完成一项任务所需时间为:T1 创 ...

随机推荐

  1. MySQL与MariaDB核心特性比较详细版v1.0(覆盖mysql 8.0/mariadb 10.3,包括优化、功能及维护)

    注:本文严禁任何形式的转载,原文使用word编写,为了大家阅读方便,提供pdf版下载. MySQL与MariaDB主要特性比较详细版v1.0(不含HA).pdf 链接:https://pan.baid ...

  2. 【GMT43智能液晶模块】例程十四:MODBUS TCP实验——电源监控

    . 源代码下载链接: 链接:https://pan.baidu.com/s/1S8wZBJBYGxuPaWEkJvMJlg 提取码:5bh3 复制这段内容后打开百度网盘手机App,操作更方便哦 GMT ...

  3. Superset配置mysql数据源

    1.添加mysql数据源 测试连接的时候遇到 No module named 'MySQLdb'" 安装mysqlclient pip install mysqlclient 如果遇到 ER ...

  4. django 未成功初始化自定义表单

    用以下两句 python3 manage.py makemigrations python3 manage.py migrate 成功初始化了数据库,但是只初始化了django自带的表,未初始化我自定 ...

  5. 【NPDP笔记】第一章 新产品开发战略

    1.1 战略很重要 1.2 战略定义 使命/愿景/核心价值观:成为领导者 公司/经营战略:市场份额扩大10% 创新战略:强调技术,外部合作 职能战略:IT战略,人力资源战略 1.3明确组织方向 组织身 ...

  6. Java Web 应用概述

    1.java Web 应用是建立在java语言基础上的企业web应用系统,oracle公司根据行业发展和便于开发制定了一套规范:Java EE规范,截至到当前(2016.3.11)是java EE7规 ...

  7. .net桌面程序或者控制台程序使用NLog时的注意事项

    Nuget添加NLog 添加nlog.config文件,并选择属性->始终复制 不选择始终复制,编译后nlog.config是没有的. 具体使用: private static readonly ...

  8. 解析Java反射java.lang.IllegalArgumentException: wrong number of arguments

    项目中遇到的问题 import org.springframework.util.ReflectionUtils; import java.lang.reflect.Method; public cl ...

  9. Git 删除所有历史提交记录方法

    Git 删除所有历史提交记录方法 切换分支 git checkout --orphan latest_branch 添加所有文件 git add -A 提交更改 git commit -am &quo ...

  10. SQL SERVER 日志写入原理浅析

    昨天看到网上有一个关于SQL SERVER 课件,便随手下载了下来看看主要讲了些什么内容,于是看到了下面两个PPT页面 由于第一张PPT上的内容不太准确(日志文件中没有“日志页”的概念,只有VLF的概 ...