锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

java并发包和类总结-JUC总结

时间:2023-03-12 15:00:00 aqs继电器14r继电器输出双重pid

java合同和类别总结-JUC总结

多线程课程
JUC课程
实战Java高并发
Java并发编程艺术

多线程

  1. 程序:是一个静态概念,通常对应于操作系统中的可执行文件。集合一组指令。
  2. 过程:是一个动态概念,执行中的程序称为过程。1.过程是程序的动态执行过程, 占用特定的地址空间。2.每个过程由三部分组成:cpu、data、code。每一个过程都是独立的,有自己的cpu时间、代码和数据,即使用同一个程序生成几个过程,它们之间仍然有自己的三件事,缺点是:浪费内存,cpu负担重。3.多任务(Multitasking)操作系统将CPU时间动态划分为每个过程,操作系统同时执行多个过程,每个过程独立运行。从过程的角度来看,它会认为它是独家的CPU的使用权。其实这是并行执行。
  3. 线程:一个过程可以产生多个线程。就像多个过程可以共享操作系统的某些资源一样,同一过程的多个线程也可以共享这个过程的某些资源(如代码和数据),因此线程也被称为轻量级过程(lightweight process)。1.一个执行单元内部的过程,它是一个单一的顺序控制过程。2.一个过程可以有多个平行过程(concurrent)线程。3.一个过程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,并从同一堆中分配对象进行通信、数据交换和同步操作。线程有自己的计数器、堆栈和局部变量。4.由于线程间的通信是在同一地址空间进行的,因此不需要额外的通信机制,这使得通信更容易,信息传输更快。5.线程启动、中断、消亡,消耗的资源很少。
  4. 流程与线程的区别:1。每个流程都有独立的代码和数据空间(流程上下文),流程之间的切换成本会更高。
    2.线程可视为轻量级过程,属于同一过程的线程共享代码和数据空间。每个线程都有独立的操作堆栈和程序计数器(PC),线程切换成本小。
    3.线程与过程最根本的区别在于:过程是资源分配单位,线程是调度和执行单位。
    4.多进程: 多个任务(程序)可以在操作系统中同时运行。
    5.多线程: 多个顺序流同时执行在同一应用程序中。
    6.线程是过程的一部分,所以线程有时被称为轻量级过程。
    7.无线程的过程可视为单线程。如果一个过程中有多个线程,则该过程的执行过程不是由一条线(线程)完成的,而是由多条线(线程)完成的。
    8.系统将为每个过程分配不同的内存区域,但不会为线程分配内存(线程使用的资源是其过程的资源),线程组只能共享资源。也就是说,除了CPU除了(线程在运行时应占用)CPU计算机内软硬件资源的分配与线程无关,线程只能共享其流程的资源。
  5. 过程和程序之间的区别:一个过程必须对应于一个程序,只有一个,但一个程序可以有多个过程,或者没有一个过程。此外,该过程还具有并发性和沟通性。简单地说,过程是程序的一部分,当程序运行时会产生过程。
  6. 创建线程的三种方式:继承Thread类、实现Runnable接口、实现Callable接口。通过线程池创建。
  7. 创建:继承Thread 重写run,启动: 创建子类对象 start
  8. 必须调用执行程序start(),添加到调度器中,不一定立即执行,系统安排调度分配执行;打开线程后,当方法执行时,调用run()代码将被执行。run()方法是线程体的入口点,线程执行的代码;直接调用run()方法不是打开多线程,而是调用普通方法run()方法会自己调用start()方法。
  9. 创建:实现Runnable 重写run启动: 创建实现对象 Thread代理类对象 start(启动线程还是要借用Thread类,只有 thread才具有和cpu直接 打交道的能力,即启动start())。我们使用实现runnable接口重写run启动线程时,必须使用该方法Thread对象,该对象为代理对象:即真实角色和代理角色实现相同的界面,代理角色包含真实角色的引用,可调用真实角色的操作。
  10. Thread类的结构方法
    Thread() Thread(Runnable target) Thread(Runnable target,String name) Thread(String name)
    Thread(ThreadGroup group,Runnable target) Thread(ThreadGroup group,Runnable target,String name)
    方法:start():导致此线程开始执行,Java虚拟机调用此线程run()方法
    run():如果使用单独的线程Runnable如果运行对象,那么这个Runnable对象的run()调用方法,否则该方法不执行任何操作并返回
  11. Runnale对于同一资源,可以有多个代理,共享资源并发(确保线程安全,以后会讲)。
  12. 创建:实现Callable接口 重写call()方法,run()方法不能抛出异常和无返回值call()方法可以抛出异常和返回值。需要服务和线程池。创建目标对象:创建执行服务: 提交执行: 结果:关闭服务: 。
  13. Thread结构方法需要的是runnable,有的是callable,如何增加它们之间的关系:runnable实现了FutureTask,而FutureTask需要的结构方法callable。FutureTask futuretask=new FutureTask<>(new MyThread());Thread t=new Thread(futuretask,”AA”);t.start();适配模式。使用多个线程callable,每次都要返回成功或失败的返回值。futuretask.get();建议放在最后,因为我们不会等这个线程,给它足够的时间计算。如果把get放到前面,mian线程被堵塞。这里可以加一个循环判断!等完了再往下做。两个线程都开始做同样的任务,只执行一次!即复用。若非要进去!所以要开始多个futuretask。
    当主线程需要进行耗时的操作,但不想阻塞主线程时,可以将这些操作交给Future对象在后台完成,
    未来主线程需要时,可以通过Future对象获得后台操作的计算结果或执行状态。

一般FutureTask它主要用于耗时的计算,主线程可以在完成任务后获得结果。

只有在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,计算就不能重新开始或取消。get方法和获取结果只能在计算完成时获得,否则将被阻塞,直到任务转移到完成状态,然后返回结果或抛出异常。

  1. 函数编程:与面向对象的比较:
  2. Lamda表达式:函数式 编程。避免定义匿名内部类太多。
    拷贝中括号 写死右箭头 落地大括号。只有在一个接口中有一个接口,只有一个方法(函数接口),才能使用它Lambda Express。
    (params)->expression (params)->statements (params)->{statements}
    如:new Thread({}->System.out.println(多线程学习).start();
    线程较少,只关注线程体。
    Lambda表达式 使用简化线程(使用一次)。
    静态内部类随外部类加载而加载;局部内部类,将类扔进方法内部;匿名内部类 必须使用接口或父类;jdk8 简化 lambda表达式 只需注意线程体,删除接口名,删除方法名,注意您传输的参数和实现的方法
  3. lambda推导,没有参数,没有返回值,lambda推导必须有类型。类型(变量名)=()->{表达式};
  4. lambda推导 参数,只要方法 复制,不需要方法名;类型也可以删除;括号也省略;只有一行代码,花括号也省略。->表达式
  5. lambda推导 参数 返回值,删除类型,多个参数括号不能省略;假设只有一行代码,可以省略return。(参数1,参数2)->表达式
  6. 线程状态:新状态NEW、start()就绪状态,获得/失去执行权RUNNABLEyield()、run()方法结束死亡状态TERMINATED、阻塞状态(从运行状态wait()等待堵塞WAITING、TIME WAITINGsynchronized同步阻塞BLOCKED、sleep()/join()/IO其他阻塞其他阻塞;等待阻塞notify()同步阻塞和同步阻塞可使用就绪状态和其他阻塞获得对象的锁标记sleep()休眠时间到/join()完成联合线程执行/()IO流堵结束时就绪状态)

当线程执行wait()方法结束后,线程进入等待状态。进入等待状态的线程需要通过其他线程的通知才能返回到运行状态,而加班等待状态相当于在等待状态的基础上增加加班限,即加班到达时返回到运行状态。当线程调用同步方法时,线程将进入阻塞状态,而无需锁定。
20. 新生状态(New):用new关键字建立线程对象后,线程对象处于新状态。新状态的线程有自己的内存空间,new 线程有自己的工作空间,一个工作空间针对一个线程。
线程对象在构提供线程所需的属性,如线程组、线程优先级、是否Daemon线程等信息。新构造的线程对象由其组成parent线程分配空间,child线程继承了parent是否为Daemon、优先级和加载资源contextClassLoader还有可继承的ThreadLocal,它还将分配一个唯一的ID来标识这个child线程。到目前为止,一个能够运行的线程对象已经初始化,并在堆内存中等待运行。

  1. 就绪状态(Runnable):具备运行条件,尚未分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。(有四种方法可以进入就绪状态1start()2堵塞解除3yield()4 JVM将CPU从本线程到其他线程的资源切换。
    线程start()方法的含义是:当前线程(即parent同步通知线程)Java只要线程规划器闲置,虚拟机应立即启动调用start()方法的线程。
  2. 运行状态(Running):在运行状态下执行线程run该方法中的代码。直到调用其他方法终止或等待资源并阻止或完成任务而死亡。如果在给定的时间片中没有执行,系统将被替换并返回就绪状态。它也可能会因为一些阻塞事件而进入阻塞状态。
  3. 阻塞状态(Blocked):阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。(有四种方法可以进入阻塞状态1sleep(int millsecond)2wait()-notify()3join()-另一个线程执行完4read()write())
  4. 死亡状态(Terminated)1正常终止run()方法运行完毕2线程被强制终止stop() 或destory()当一个线程进入死亡状态以后,就不能再回到其它状态了。
  5. 线程方法:
    sleep()使线程停止运行一段时间,将处于阻塞状态。如果调用了sleep()方法后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行。
    join()阻塞指定线程等到另一个线程完成以后再继续执行。
    yield()让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态;调用以后,如果没有其他等待执行的线程,此时当前线程马上就会恢复执行
    setDaemon()可以将指定的线程设置成后台线程,守护线程;创建用户线程的线程结束时,后台线程也将随之消亡;只能在线程启动之前把它设为后台线程
    setPriority(int newPriority) getPriority()线程的优先级代表的是概率 范围从1到10,默认为5
    stop()不推荐使用
  6. 终止线程的典型方式:终止线程我们一般不使用JDK提供的stop()/destroy()方法(它们本身也被JDK废弃了)。通常的做法是使用中断状态interrupt()或者提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。1.线程类中定义线程体使用的标识2.线程体使用该标识3.对外提供方法改变标识

暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。这都是过期方法不建议使用,以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
27. 暂停线程执行:sleep(时间)指定当前线程阻塞的毫秒数;存在异常InterruptedException;时间达到后线程进入就绪状态;可以模拟网络延时、倒计时等;每个对象都有一个锁,sleep()不会释放锁:特点是抱着锁睡觉,站在马路中间谁都过不去。
sleep()方法与对象没关系,谁执行这个线程体,谁就去执行sleep()操作
28. 暂停线程:yield(),可以引起线程切换,但运行时没有明显延迟。礼让线程,让当前正在执行的线程暂停;不是阻塞线程,而是将线程从运行状态转入就绪状态,让CPU调度器重新调度,可能礼让成功,也可能又重回来调用自己了。即有时候礼让成功,有时候礼让不成功。
29. 暂停线程执行常用的方法有sleep()和yield()方法,这两个方法的区别是:
1.sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。
2.yield()方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。
30. 线程的联合:join(),合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。又叫做插队线程,一个车插到别的车前面,别的车就得等插队车走后才能走,并且别的车进入阻塞状态。join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远等待。其中,wait(0)表示永远等待下去。直到join线程中止后,线程的this.notifyAll()方法会被调用,调用notifyAll()方法是在JVM里实现的。CountDownLatch也可以实现join的功能,并且比join的功能更多。join()写在谁A的run方法体中就阻塞谁,谁去调用join()谁B就去插队。
线程A在运行期间,可以线程B调用的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。“爸爸线程A”要抽烟,于是联合了“儿子线程B”去买烟,必须等待“儿子线程B”买烟完毕,“爸爸线程A”才能继续抽烟。
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。

  1. 线程优先级priority:Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应该调度哪个线程来执行。线程优先级用数字来表示,范围从1到10。Thread.MIN_PRIORITY=1;Thread.MAX_PRIORITY =10;Thread.NORM_PRIORITY=5;默认为5。使用以下方法获得或设置线程对象的优先级:
    int getPriority()void setpriority(int newPriority)优先级的设定建议在start()调用前。优先级低只是意味着获得调度的概率低,并不是绝对先调用优先级高后调用优先级低的线程。线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java线程对于优先级的设定。

优先级高的线程分配时间片的数量要多于优先级低的线程。设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。
32. 获取线程基本信息的方法:isAlive()setName()getName()currentThread()
33. 线程组:ThreadGroup th=new ThreadGroup(“pg”);Thread t1=new Thread(th,new ThreadGroupName(),”T1”);使用构造函数指定线程所属的线程组th.activeCount()获得活动线程的总数 th.list()获得整个线程组中所有线程的信息
34. 守护线程:线程分为用户线程和守护线程;Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。虚拟机必须确保用户线程执行完毕;虚拟机不用等待守护线程执行完毕;如后台记录操作日志,监控内存使用等等。Thread.setDaemon(true)将线程设置为守护线程。Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。

  1. Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,(main线程(非Daemon线程)在启动了线程DaemonRunner之后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有非Daemon线程,虚拟机需要退出。Java虚拟机中的所有Daemon线程都需要立即终止,因此DaemonRunner立即终止,但是DaemonRunner中的finally块并没有执行。)在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

  2. 线程中断:中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼,其他线程通过调用该线程的interrupt()方法对其进行中断操作。线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。
    从Java的API中可以看到,许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false。

  3. 并发:同一个对象多个线程同时操作。(同时操作同一个账户;同时购买同一车次的票;操作容器)《比如说:多个代理同时去访问:有负数的情况、有相同的情况。分析:负数:临界值没有控制 相同的值:拷贝10的时候已经都拿到自己的工作台。》

  4. 线程同步:处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候,我们就需要用到线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

  5. 锁机制(synchronized):由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制(synchronized),当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    1.一个线程持有锁会导致其他所有需要此锁的线程挂起。
    2.在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
    3.如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。

  6. synchronized关键字:由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是
    synchronized关键字,它包括两种用法:synchronized方法和synchronized块。

  7. 同步方法:public synchonized void method(int args){}
    synchronized方法控制对"成员变量|类变量"对象的访问:每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞。方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方法方能获得该锁,重新进入可执行状态。缺陷:若将一个大的方法声明为synchonized将会大大影响效率。
    synchronized操作了什么,锁了对象的资源、而不是锁方法,锁是当前实例对象。而静态同步方法,锁是当前类的Class对象。

1 标准访问,请问先打印短信还是email?(两个线程同时访问同一个资源,谁先谁后是不一定的;在两个线程之间加上Thread.sleep(100);就能确定先后了)
也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
所有的非静态同步方法用的都是同一把锁——实例对象本身,
2 sendSMS()睡觉4秒钟,请问先打印短信还是email?(刚开始等了4秒,然后先短信后email)(同一个同学同一时刻只能使用手机的一个功能!)
也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
所有的非静态同步方法用的都是同一把锁——实例对象本身,
3 新增普通方法openPC,请问先打印短信还是openPhone?(先开机【因为这个没有同步方法啊】,等了4秒,然后打印短信)
加个普通方法后发现和同步锁无关
4 有两部手机,请问先打印短信还是email?(先打印邮件,等了4秒,然后打印短信)
可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
换成两个对象后,不是同一把锁了,情况立刻变化。
所有的 非静态同步方法用的都是同一把锁——实例对象本身,
5 两个静态同步方法,同一部手机,请问先打印短信还是email?(等了4秒,先打印短信,然后打印邮件)
6 两个静态同步方法,2部手机,请问先打印短信还是email?(等了4秒,先打印短信,然后打印邮件)
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
所有的静态同步方法用的也是同一把锁——类对象本身,
7 1个静态同步方法,1个普通同步方法,同一部手机,请问先打印短信还是email?(先打印邮件,等了4秒,打印短信)
8 1个静态同步方法,1个普通同步方法,2部手机,请问先打印短信还是email?(先打印邮件,等了4秒,打印短信)
这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。

一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法

加个普通方法后发现和同步锁无关
换成两个对象后,不是同一把锁了,情况立刻变化。

都换成静态同步方法后,情况又变化
所有的非静态同步方法用的都是同一把锁——实例对象本身,

也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,
可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,
所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。

所有的静态同步方法用的也是同一把锁——类对象本身,
这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,
还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
42. 同步块:synchronized(obj){},obj称之为同步监视器。
obj可以是任何对象,但是推荐使用共享资源作为同步监视器。
同步方法中无需执行同步监视器,因为同步方法的同步监视器是this即该对象本身,或class即类的模子。
同步监视器的执行过程:
第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,解锁同步监视器
第二个线程访问,发现同步监视器未锁,锁定并访问。

方法里面的块:局部块
构造块:对象的信息
静态块:类的信息
同步块:
43. Synchronized的实现原理:JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。无论采用哪种方式,其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。

锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语义。

线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。•线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。

  1. 无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态
  2. 并发容器:CopyOnWriteArrayList
  3. 死锁:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。某一个同步代码块同时拥有"两个以上对象的锁"时,就可能会发生死锁问题。

死锁: 过多的同步可能造成相互不释放资源。从而相互等待,一般发生于同步中持有多个对象的锁
避免: 不要在同一个代码块中,同时持有多个对象的锁解决方式:后一种 往外挪一下,不要锁套锁
47. 线程通信: volatile修饰成员变量,保证对线程的可见性。Synchronized修饰方法或块,保证多个线程在同一时刻只能有一个线程处于方法或块中,保证了线程对变量访问的可见性和排他性。
应用场景-生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖互为条件。
对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又要马上通知消费者消费。
对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新的产品以供消费。
48. 在生产者消费者问题中,仅有synchronized是不够的:
synchronized可以阻止并发更新同一个共享资源,实现了同步。
synchronized不能用来实现不同线程之间的消息传递(通信)
49. 解决方法1:并发协作模型"生产者/消费者模式"–》管程法:生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)消费者:负责处理数据的模块(这里模块可能是:方法、对象、线程、进程) 缓冲区:消费者不能直接使用生产者的数据,他们都之间有个缓冲区。生产者将生产好的数据放入缓冲区,消费者从缓冲区中拿走要处理的数据。《根据容器进行交流的》缓冲区是实现并发的核心,缓冲区的设置有3个好处:
Ø 实现线程的并发协作
有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。
Ø 解耦了生产者和消费者
生产者不需要和消费者直接打交道。
Ø 解决忙闲不均,提高效率
生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。
50. 解决方法2:并发协作模型"生产者/消费者模式"–》信号灯法:借助标志位
51. Java提供了三个方法解决线程之间的通信问题:等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
wait()表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout)指定等待的毫秒数
notify()唤醒一个处于等待状态的线程
notifyAll()唤醒同一个对象上所有调用
wait()方法的线程,优先级别高的线程优先调度
以上方法均是java.lang.Object类的方法;都只能在同步方法或者同步代码块中使用,否则会抛出异常

1) 使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
2)调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
3)notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
4)notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。
5)从wait()方法返回的前提是获得了调用对象的锁。
从上述细节中可以看到,等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改。WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。由于WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。

2) 等待/通知的经典范式:
等待方(消费者)遵循如下原则。1)获取对象的锁。2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。3)条件满足则执行对应的逻辑。synchronized(对象) { while(条件不满足) { 对象.wait(); } 对应的处理逻辑 }
通知方遵循如下原则。1)获得对象的锁。2)改变条件。3)通知所有等待在对象上的线程。synchronized(对象) { 改变条件 对象.notifyAll(); }

3) 等待超时模式:假设超时时间段是T,那么可以推断出在当前时间now+T之后就会超时。这时仅需要wait(T)即可,在wait(T)返回之后会将执行T=FUTURE–now。如果T小于等于0,表示已经超时,直接退出,否则将继续执行wait(T)。可以看出,等待超时模式就是在等待/通知范式基础上增加了超时控制,这使得该模式相比原有范式更具有灵活性,因为即使方法执行时间过长,也不会“永久”阻塞调用者,而是会按照调用者的要求“按时”返回。

4) 使用等待超时模式来构造一个简单的数据库连接池,在示例中模拟从连接池中获取、使用和释放连接的过程,而客户端获取连接的过程被设定为等待超时的模式,也就是在1000毫秒内如果无法获取到可用连接,将会返回给客户端一个null。设定连接池的大小为10个,然后通过调节客户端的线程数来模拟无法获取连接的场景。

  1. java.util.Timer类:Timer类作用是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其它线程的。t1.schedule(task1,3000); //3秒后执行;t1.schedule(task1,5000,1000);//5秒以后每隔1秒执行一次!t1.schedule(task1,calendar1.getTime()); //指定时间定时执行;构造方法
    Timer() Timer(boolean isDaemon) Timer(String name) Timer(String name,boolean isDaemon)
    方法
    cancel() purge() schedule(TimerTask task,long delay) schedule(TimerTask task,long delay,long period)
    schedule(TimerTask task,Date time) schedule(TimerTask task,Date firsttime,long period)

  2. java.util.TimerTask类:TimerTask类是一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力。在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间需要完全独立的话,最好还是一个Timer启动一个TimerTask实现。构造方法
    TimeTask()
    方法
    cancel() run() scheduledExecutionTime()

  3. quanz:任务定时调度框架。Schdule调度器,控制所有的调度Trigger 触发条件,采用OSL模式JobDetail 需要处理的jobJob 执行逻辑
    DSL Domain-specific language领域特定语言,针对一个特定的领域,具有受限表达性的一种计算机程序语言,即领域专用语言,声明式编程:
    1Method Chaining方法链 Fluent Style流畅风格 builder模式构建器
    2Nested Functions 嵌套函数
    3Lambda Expressions/Closures
    4Functional Sequence

  4. happenbefore:在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。你写的代码可能根本没有按照你期望的顺序执行,因为编译器和CPU会尝试重排指令使得代码更快的运行
    第一步从内存中获取指令fetch将指令进行解码翻译
    第二步从寄存器中拿出对应的值、工作内存,需要拷贝
    第三步计算
    第四步同步到主存
    看到下一个指令与上一条无关,那么就提前执行了指令重排对我们的指令是有影响的
    执行代码的顺序可能与编写代码不一致,即虚拟机优化代码顺序,则为指令重排
    happen-before:即,编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段
    在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则将程序编写顺序打乱–
    即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行–以即可能充分的利用CPU。

•对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序。•对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求(JMM允许这种重排序)1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。•as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。•as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。
56. 虽然这里有两种情况:后面的代码先于前面的代码开始执行;前面的代码先开始执行,但当效率较慢的时候,后面的代码开始执行并先于前面的代码执行结束。
不管谁先开始,总之后面的代码在一些情况下存在先结束的可能。
在硬件层面,CPU会接收到的一些指令按照其规则重新排序,同样是基于CPU速度比缓存速度块的原因,和上面的一点的目的类似。只是虚拟机可以在更大层面更多指令范围内重新排序。

  1. 数据依赖:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖。数据依赖分为以下三种类型:
    1:写后读
    2:写后写
    3:读后写
    以上三种情况,只要重新排序两个操作的执行顺序,程序的执行结果将会被改变,所以,编译器和处理器在重新排序时,会遵守数据依赖性,编译器和处理器不会改变数据依赖关系的两个操作的执行顺序。这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

  2. 顺序一致性:一个线程中的所有操作必须按照程序的顺序来执行。(不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

  3. volitale:是Java虚拟机提供的轻量级的同步机制(轻量级的synchronized)。它能保证线程之间的变量的可见行,简单的说就是当一个线程对一个共享变量进行了修改后,另外一个线程能读到这个修改的值。比synchronized成本低因为它不会引起线程上下文切换和调度。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。它能保证可见性,不保证原子性,禁止指令重排。
    更详细的说是要符合以下两个规则:
    线程对变量进行修改之后,要立刻会写到主内存
    线程对变量读取的时候,要从主内存中读,而不是缓存(这样就形成了消息通信)
    Java线程—工作内存—save和load操作—主内存

  4. Volitale不保证原子性:对任意单个volatile变量的读写具有原子性,但类似于volatile++这种复合操作不具有原子性。原子性:不可分割、完整性,也就是某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败。拷贝回自己的内存空间,每个人都拿到0,写回到主内存时,线程1写回到的时候被挂起了,线程2的写回了。然后线程1恢复后又写回了一遍,把原来的1给覆盖了。如何解决:1.使用同步方法2.使用atomic类进行解决。

  5. Volitale禁止指令重排:从而避免多线程环境下程序出现乱序执行的现象。内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特定实现volatile的内存可见性)。

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。

内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此在任何CPU上的线程都能读取到这些数据的最新版本。对volatile变量进行写操作时,会在写操作后加入一条store屏障指令,将工作内存中的共享变量值刷新回到主内存。对volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

《当第二个操作是volatile写时,不管第一个操作是什么都不能重排序。确保写之前的操作不会被编译重排序到写之后。
当第一个操作是volatile读时,不管第二个操作是什么都不能重排序。确保读之后的操作不会被编译重排序到读之前。
当第一个操作是volatile写,第二个操作是volatile读时,不能重排序,》

写:前StoreStore<禁止上面的普通写和下面的volitale写重排序>后StoreLoad<防止上面的volatile写和下面可能有的volitale读/写重排序> 读:后:LoadLoad<禁止下面的所有普通读操作和上面的volatile读重排序>后LoadStore<禁止下面所有的写操作和上面的volatile读重排序>

  1. JMM:Java内存模型(Java Memory Model),本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素—堆内存在线程之间共享)的访问方式。局部变量、方法定义参数、异常处理器参数不会在线程间共享,不会有内存可见性问题,不受内存模型的影响。

特性:可见性、原子性、有序性。

线程通信:线程之间以何种机制来交换信息。共享内存(线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。)和消息传递(线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信。)。

线程同步:程序中用于控制不同线程间操作发生相对顺序的机制。共享内存(同步是显式进行的。程序员必须显式指定某个方法或某段代码需要在线程之间互斥执行。)和消息传递(由于消息的发送必须在消息的接收之前,因此同步是隐式进行的。)。

JMM是一个语言级的内存模型,处理器内存模型是硬件级的内存模型,顺序一致性内存模型是一个理论参考模型。

  1. JMM内存模型的可见性:由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(栈空间),工作内存是每个线程的私有数据区域。而JMM中规定所有的变量都存储在主内存,主内存时共享内存区域,所有的线程都可以访问。但线程对变量的操作必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。总结:各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后再写回到主内存中的。《工作内存和主内存的同步延迟问题造成了可见性问题。》所以我们需要有一个机制:JMM内存模型的可见性,只要有一个线程改变数据后要写回到主内存中,其它的线程马上就会知道主内存中的数据已经改变了。

JMM关于同步的规定:1线程解锁前,必须把共享变量的值刷新回主内存2线程加锁前,必须读取主内存的最新值到自己的工作内存3加锁解锁是同一把锁。

  1. JMM内存模型的原子性:number++在多线程下是非线程安全的,如何不加synchronized解决?被拆分为3个指令:执行getfield拿到原始的number,执行iadd进行加1操作,执行putfield写把累加后的值写回。三个线程都拿到1,都在各自的工作内存中加1,写回到的时候,没有拿到最新的值就又写了,写覆盖。使用atomic类解决。

原子操作是指不可中断的一个或一系列的操作,一般的处理器是利用总线加锁或者缓存加锁的方式实现多处理器之间的原子操作。

总线锁定:处理器提供一个LOCK # 信号,刚一个处理器在总线上输出一个信号时,其它的处理器请求就会被阻塞住,那么该处理器就可以独占共享内存了。

缓存锁定:内存区域如果被缓存在处理器的缓存行中,在Lock操作期间被锁定,在执行锁操作回写到内存时,处理器不在总线上声言LOCK #信号,而是修改内存地址,缓存的一致性机制会阻止同时修改由2个以上处理器缓存的内存区数据,其他的处理器成功回写被锁定的缓存行数据时,会使缓存行无效,其它的处理器就不能在使用这个缓存行了。

Java中是通过使用锁和循环CAS(atomic包)的方式来实现原子操作。如:AtomiclBoolean、AtomicInteger、AtomicLong.。

CAS实现原子操作的三大问题:1. ABA问题->使用版本号,AtomicStampReference
2. 循环时间长开销大
3. 只能保证一个共享变量的原子操作,可以考虑把多个变量合成一个共享变量,或者使用锁:偏向锁,轻量级锁和互斥锁。除了偏向锁,其他实现锁的方式都使用了循环CAS获得和释放锁

  1. JMM内存模型的有序性:计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,编译器优化的重排、指令并行的重排、内存系统的重排。单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致,处理器在执行重排序时必须考虑指令之间的数据依赖性。多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。Memory Barrier实现禁止指令重排。加了volatile之后是禁止指令重排。

  2. 线程安全性获得保证:对于工作内存和主内存同步延迟线程导致的可见性问题,使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。对于指令重排导致的可见性问题和有序性问题,可以利用volatile关键字解决,因为它的另外一个作用就是禁止重排序优化。

  3. Volitale应用:DCL单例模式(Double Check Lock双端检锁机制): 懒汉式套路基础上加入并发控制,在加锁之前和之后都进行一次检测,保证在多线程环境下,对外存在一个对象。在多线程坏境下,单例模式出现了问题,如果加上synchronized,在多线程的环境控制住了,但是太重了,并发性下降了。
    1、构造器私有化 -->避免外部new构造器
    2、提供私有的静态属性 -->存储对象的地址)(没有volatile其他线程可能访问一个没有初始化的对象,保证同步更新。)
    3、提供公共的静态方法 --> 获取属性(避免创建两个对象,所以这里同步,锁定这个class;1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用)

如果第一次检查instance不为null,那么就不需要执行下面的加锁和初始化操作。因此,可以大幅降低synchronized带来的性能开销。上面代码表面上看起来,似乎两全其美。
双端检测机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排。原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。Instance=new Singleton();可以分为以下3个步骤完成:memory=allocate();instance(memory);instance=memory;1.分配对象内存空间2.初始化对象3.设置instance指向刚分配的内存之地,此时instance!=null。步骤2和3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的,所以就会导致对象还没有完成初始化,但是instance就不为null了,造成了线程安全问题。地址不为空,但是内容为空,所以要在instance变量上面加上volatile。
另一个并发执行的线程B就有可能在第4行判断instance不为null。线程B接下来将访问instance所引用的对象,但此时这个对象可能还没有被A线程初始化!此时,线程B将会访问到一个还未初始化的对象。在知晓了问题发生的根源之后,我们可以想出两个办法来实现线程安全的延迟初始化。1)不允许2和3重排序。2)允许2和3重排序,但不允许其他线程“看到”这个重排序。
把instance声明为volatile型,就可以实现线程安全的延迟初始化。2和3之间的重排序,在多线程环境中将会被禁止。

还有一种方式是通过静态内部类的方式实现:JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。2和3重排序,但不允许非构造线程(这里指线程B)“看到”这个重排序。Java初始化一个类或接口的处理过程如下:
第1阶段:通过在Class对象上同步(即获取Class对象的初始化锁),来控制类或接口的初始化。这个获取锁的线程会一直等待,直到当前线程能够获取到这个初始化锁。第2阶段:线程A执行类的初始化,同时线程B在初始化锁对应的condition上等待。第3阶段:线程A设置state=initialized,然后唤醒在condition中等待的所有线程。第4阶段:线程B结束类的初始化处理。第5阶段:线程C执行类的初始化的处理。

通过对比基于volatile的双重检查锁定的方案和基于类初始化的方案,我们会发现基于类初始化的方案的实现代码更简洁。但基于volatile的双重检查锁定的方案有一个额外的优势:除了可以对静态字段实现延迟初始化外,还可以对实例字段实现延迟初始化。字段延迟初始化降低了初始化类或创建实例的开销,但增加了访问被延迟初始化的字段的开销。在大多数时候,正常的初始化要优于延迟初始化。如果确实需要对实例字段使用线程安全的延迟初始化,请使用上面介绍的基于volatile的延迟初始化的方案;如果确实需要对静态字段使用线程安全的延迟初始化,请使用上面介绍的基于类初始化的方案。

  1. ThreadLocal:即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
    1.在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程。
    2.ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到线程安全的目的。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全,常用的方法,就是get/set/initiaValue方法。
    3.JDK建议ThreadLocal定义为private static
    4.ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的方法都可以非常方便的访问这些资源(Hibernate的Session工具类HibernateUti、通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性)

  2. ThreadLocal:每个线程自身的存储本地、局部区域get/set/initialValue
    ThreadLocal:每个线程自身的数据,更改不会影响其他线程
    ThreadLocal:分析上下文 环境 起点1、构造器: 哪里调用 就属于哪里 找线程体2、run方法:本线程自身的
    InheritableThreadLocal:继承上下文 环境的数据 ,拷贝一份给子线程

  3. 可重入锁:锁作为并发共享数据保证一致性的工具,大多数内置锁都是可以重入的,也就是说,如果每个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器会递减,当计数值等于0时,锁释放。如果没有可重入锁的支持,在第二次企图获得锁时会进入死锁状态。可重入锁随处可见。

  4. CAS:对于并发控制而言,锁是一种悲观的策略,它总是假设每一次的临界区操作会产生冲突,于是对每次操作都小心翼翼,而无锁是一种乐观策略,它会假设对资源的访问是没有冲突的,自然不需要等待,所以所有的线程都可以在不停顿的状态下持续执行,遇到冲突的话,使用CAS来鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。compare and set ,它是一条CPU并发原语。比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。这个过程是原子的。《多个线程去操作主内存中的数据。一个叫做期望值、一个叫做更新值。主内存的值,一个线程拷贝回去自己的工作内存,对它进行修改,然后写回到主内存的时候,会进行比较和交换,如果和拷贝的数据一样的话,就将改变后的数据写回去;否则的话,就不进行写回。》 CAS并发原语体现在Java语言中sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。原语属于操作系统用语,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。例子:使用atomicInteger的compareAndSet()方法

  5. CAS底层原理?atomicInteger.getAndIncrement()方法的源代码public final int getAndIncrement(){return unsafe.getAndInt(this,valueoffset,1)}《AtomicInteger 的getandincrement方法底层其实是CAS思想,套的是unsafe类的CPU原语来保证原子性,底层思想是比较并交换,真实值和期望值相等就交换成功,否则就失败,失败就再来,直到比较成功为止。》
    compareAndSwapInt(Object o,long offset,int expected,int x)
    1.UnSafe类:UnSafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据。UnSafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于UnSafe类的方法。注意UnSafe类中的所有方法都是native修饰的,也就是说UnSafe类中的方法都直接调用操作系统底层资源执行相应任务。

2.变量valueOffset:表示该变量值在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的。

3.变量value用volatile修饰,保证了多线程之间的内存可见性。

Unsafe.getAndAddInt():
public final int getAndAddInt(Object var1,long var2,int var4){int var5;do{var5=this.getIntVolatile(var1,var2);}while(!this.compareAndSwapInt(var1,var2,var5,var5+var4));return vat5;}
var1:AtomicInteger对象本身var2:该对象指的引用地址var4:需要变动的数量var5:是用var1var2找出的主内存中真实的值。用该对象当前的值与var5比较,如果相同,更新var5+var4并且返回true,如果不同,继续取值然后再比较,直到更新完成。

如果是多线程同时执行getAndAddInt()方法,假设线程A和线程B同时执行getAndAddInt操作:1、AtomicInteger里面的value原始值为3,即主内存中的value值为3,根据JMM模型,线程A和线程B各自持有一份值为3的副本分别到各自的工作内存。2、线程A通过getIntVolatile(var1,var2)拿到value值3,此时线程A被挂起。3、线程B通过getIntVolatile(var1,var2)拿到value值3,此时线程B没被挂起并执行compareAndSwapInt方法比较内存值也是3,成功被修改内存值为4,线程B收工。4、此时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值3和主内存中的额值4不一致,说明该值已经被其他线程抢先一步修改过了,那么线程A本次修改失败,只能重新读取重新来一遍了5、线程A重新获取value值,因为变量vlaue被volatile修饰,所以其他线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较并替换,直到成功。

  1. CAS缺点:1.循环时间长开销很大,使用dowhile语句,如果CAS失败会一直尝试,如果CAS长时间一直不成功,可能会被CPU带来很大的开销。2.只能保证一个共享变量的原子操作。对于多个共享变量操作,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。3.引出ABA问题。原子类AtomicInteger的ABA问题。
    CAS—UnSafe—CAS底层原理—ABA—原子引用更新—如何规避ABA问题
  2. 锁分为两种:悲观锁:synchronized是独占锁即悲观锁,会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止-CAS。
  3. 乐观锁的实现:
    有三个值:一个当前内存值V、旧的预期值A、将更新的值B。先获取到内存当中当前的内存值V,
    再将内存值V和原值A进行比较,要是相等就修改为要修改的值B并返回true,否则什么都不做,并返回false
    CAS是一组原子操作,不会被外部打断;
    属于硬件级别的操作(利用CPU的CAS指令,同时借助JNI来完成的非阻塞算法),效率比加锁操作高。
  4. ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了么?
    如果在这段期间曾经被改成B,然后又改回A,那么CAS操作就会误认为它从来没有被修改过。
  5. 原子类AtomicInteger的ABA问题:CAS会导致ABA问题,CAS算法实现一个重要前期需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。一个线程1从内存位置V中取出A,另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中仍然是A,线程1操作成功。但整个过程是有问题的。
  6. AtomicReference原子引用:原子引用的泛型类AtomicReference a = new AtomicReference<>(); a.compareAndSet(z2,li4);
  7. AtomicStampedReference时间戳原子引用:–解决ABA问题,就是修改版本号,类似于时间戳。T1 100 1 T2 100 1 T2 101 2 T2 100 3 T1 1111 2AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100,1);atomicStampedReference.
锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章