2)J3)ReentranLock是AQS实现的
4)读写锁是ReentranLock的子锁
为什么进程之间是相互独立的?不能访问相互的资源和文件?
1)为什么屏蔽进程之间内存的获取和共享,有一些敏感的进程,不能让其他非当前进程来进行访问,比如说我打开一个进程,一个工商银行,不能让其他进程能访问我的私密信息,这就是为什么进程之间不可以相互访问,保护隐私
2)进程就好比一家公司,线程就是公司里面的一个一个的员工
3)futureTask属于同步阻塞
4)Start()方法会改变线程的状态,从NEW状态编程running状态
5)synchronized加锁的对象和调用wait和notify的对象必须是一致的吧
public static void main(String[] args) throws IOException, InterruptedException { Thread thread=new Thread(){ public void run(){ System.out.println(Thread.currentThread().getName()+"正在执行"); } }; thread.start();//thread.run() thread.sleep(1000); System.out.println("main线程正在执行"); }
public static void main(String[] args) throws IOException, InterruptedException { Object object=new Object(); Thread t1=new Thread(){ public void run(){ synchronized (object){ System.out.println(100); try { object.wait(); System.out.println(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t1.start(); Thread.sleep(10000); Thread t2=new Thread(()->{ synchronized (object){ for(int i=0;i<10;i++){ System.out.println(i); } object.notify(); } }); t2.start(); }
每一个等待唤醒的手段都是有着不同的应用场景
一个Lock可以创建多个Condition对象,搞一个Condition叫做生产者,再Condition搞一个叫做消费者,可以有更多的分支,唤醒就变的更加的精准,一堆一组线程可以使用一个Condition来进行等待和唤醒的操作,分两组绑定Condition
一堆生产者可以使用一个Condition对象1来进行唤醒
一堆消费者可以使用一个Contion对象2来进行唤醒
但是两堆生产者和消费者都是加的同一把锁,所以就可以根据哪一个Condition对象来唤醒的是生产者还是消费者,也是随机唤醒,但是也是可以指定唤醒那一组,是生产者还是消费者,但是wait和notify一个锁,一个对象只能有一组
同时生产者也是可以调用消费者的一个Condition进行唤醒了
兄弟们想用一个问题就是在讲到这个并发编程的时候,有生产者和消费者,生产者可能有多个,消费者也有可能有多个,就是我听得不太懂,第一当生产者不生产任务的时候会休眠,消费者当阻塞队列中没有任务的时候会休眠,为什么他们两个加的是同一把锁?
还有就是说当有任务来临的时候,如果时使用的是Object类中的wait和notify方法,可能这个时候随机唤醒的的是生产者可能会导致队列中的任务永远无法被消费,但是为什么会给生产者加锁,然后是为什么新来的那个任务不是生产者产生的任务?
wait方法也是可以指定休眠时间的
还有就是比如说现在有两个线程,线程1进入到了synchronized修饰的方法之后,调用wait方法的那一刻,线程1会放弃synchronzied的那把锁,线程1从进入到waitting状态,线程2获取到了同一把锁,然后执行对象的notifyAll方法,执行完线程2的synchronized方法之后线程2释放锁,然后去尝试唤醒所有wait的线程,然后所有的wait的线程都去尝试争夺这同一把锁
但是如果是线程2调用的是notify方法,然后其他wait的线程只会被唤醒一个,然后尝试获取到锁执行;
yeild方法和join方法都是Thread方法中的常见方法,yield是给线程重新分配资源的一个执令,就是给线程调度器一个建议建议当前调度器可以执行以下调度的方法让其他的线程执行
线程实例的方法join()方法可以使得一个线程在另一个线程结束后再执行,如果join()方法在一个线程实例上调用,当前运行着的线程将阻塞直到这个线程实例完成了执行
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中
介绍线程池:要相比于线程去讲
1)降低资源消耗:普通的线程执行完成任务之后就被销毁了,而线程池中的线程执行完成任务之后会把这个线程继续放到池子里面,降低频繁创建销毁线程会消耗很多资源的
2)提升响应速度:当自己去new出一个线程的时候,要给这个线程分配内存空间,设置线程状态,但是线程池中的线程可以直接拿来用,new对象,不需要频繁分配空间
3)提升稳定性:当在进行创建线程池的时候,可以设置线程池的一个数量,线程只要有任务就开启一个线程,没有统一的协调和限制的,假设此时有一亿个任务,会开一亿个线程,此时程序就会崩溃,但是对于线程池来说,会把这一一个任务放到任务队列中,慢慢的执行;
4)支持队列:可以把任务放到队列中慢慢执行,但是线程有任务就执行,没有任务就销毁
阻塞队列要设计有界队列,不可以使用无界队列,因为如果使用无界队列,那么可能会导致任务过多从而导致内存溢出,线程工厂线程类型,线程优先级,线程的名字
优先级越高就是给线程调度器一个建议,优先调用优先级高的线程,这只是一个建议,这个值可以进行设置,但是实际上线程池如何执行,不知道,这就是为了防止线程饥饿,就是为了防止只是执行高优先级的线程
线程池是一个懒加载的策略,核心线程数是10并不会在JVM启动的时候就会直接创建10个线程,而是有任务来了之后才会进行创建;
实现自定义拒绝策略的时候要做什么?
1)首先将这个任务记录下来,后面追溯问题才能及时发现
2)发一个邮件
execute()执行不回调,submit可以执行回调和不回调
synchronized修饰的代码块,进入到代码块被moniterenter然后退出代码块moniterexit
监视器锁就是类似于一个房间,同一时刻只会允许一个人进来,在任何时候都是只能有一个人进来,是依靠ObjectMoniter实现的
1)_recursions是某一个线程某一次重复获取到锁的次数,可重入锁代表某一个线程可以重复的获取锁,因为synchronized是可重入锁,线程是可以重复的获取到这把锁,那么某一个线程每一次获取到锁的时候,计数器就会记录该线程和获取到锁的次数,每获取到一次锁,进入到这个房间,_recursions++,每当离开这个房间一次,那么这个计数器就--,当_recursions=0的时候,说明此时这个监视器是没有人的,就放开房间让其他线程进入
2)count记录每一个线程获取到锁的次数,就是前前后后这个这个线程一共获取这把锁多少次
偏向锁,指的是偏向某一个线程,指的是所有的线程来了之后会进行判断,对象头中的头部保存当前拥有的锁的线程ID,判断当前线程ID是否等于_owner的线程ID,等于说明你拥有我,就可以进入执行
1)先通过CAS(this.owner,null,Thread.currentThread())尝试获取到这把锁,如果通过自旋一定次数无法获取到这把锁,监视器锁这个线程是获取不到了,但是这个线程会直接进入到entrySet集合里面了,因为这个线程通过一定的努力无法得到这把锁,那么这个线程就放弃了,放到队列里面,如果监视器锁中的线程执行完成之后,那么程序就会通知EntryListSet中的线程来去争抢这个监视器锁;
2)那么这个WaitSet队列有什么用呢?
首先拥有监视器锁的线程正在执行自己代码中的逻辑的时候调用了wait方法,那么此时这个线程会直接释放锁,同时这个拥有这个监视器锁的线程会直接进入到waitSet这个短暂的集合里面,所以当某一个拥有监视器锁的线程调用notify或者是notifyAll方法的时候,会直接通知waitSet中的线程来获取到锁
1)无锁:刚一开始的时候,没有线程访问synchronized修饰的代码,说明此时是处于无锁状态
2)偏向锁:会在锁的对象头里面将线程的ID记录下来,下一次有线程过来的时候,程序会直接判断对象头中的线程ID和实际访问程序的线程ID是否相同,如果是同一个,那么程序会继续向下访问,如果不相同,说明有两个线程以上进行争夺锁,于是升级成轻量级锁
3)轻量级锁:还没有放弃挣扎,还会通过自旋的方式尝试得到锁,如果通过一定的次数得不到锁,synchronized是自适应自旋锁,不同的JVM的自旋次数是不相同的,自旋次数是25次,是根据上一次自旋的结果来去决定这一次自旋的次数的,如果这个线程是通过上一次自旋来获取到锁的话,那么会有极大的大概率这一次也是可能通过自旋的方式来获取到锁的,如果上一次获取次数也比较少,那么这一次自旋的次数也会变少,获取不到,直接阻塞到EntryList
4)重量级锁:升级成重量级锁
非公平锁性能更高,非公平锁可能出现线程饥饿的情况
通过线程工厂可以设置线程的类别,设置线程的类型必须在线程启动之前进行设置
ThreadLocal
Thread会导致内存泄漏,有可能对象清理不掉占用内存空间
最终可能会导致内存溢出,就是没有内存可以分配,内存泄漏是内存溢出的罪魁祸首
ThreadLocal是会造成内存溢出的问题,内存溢出和内存泄漏是两个不同的概念,内存溢出是最终导致的一个结果,内存不够用的,当程序发生OOM的时候,内存泄漏是一个问题,这个问题并不会报错,是慢慢泄露的,内存泄漏会导致内存溢出;
因为Threadlocal里面存储的数据的生命周期是和线程的生命周期和线程池的生命周期是保持一致的,如果说在整个程序的运行期间,线程和线程池都没有进行销毁的情况下,那么ThreadLocal中的保存的数据也不会销毁或者被垃圾回收机制回收,那么这个问题就是内存泄漏的问题,当程序中有大量的ThreadLocal并且ThreadLocal中记录的都是大数据的情况下,有可能会造成OutOfMemory内存溢出的问题,就是ThreadLocal里面存的东西很大很多,而存储的这些数据的生命周期是和线程和线程池绑定在一起的,因为线程或者线程池在程序运行的过程中没有得到销毁,所以此时ThreadLocal也不会销毁,那么ThreadLocal里面的值会占用内存,虽然此时ThreadLocal里面的内容已经不用了,但是线程和线程池没有挂掉,所以ThreadLocal中的内容也不会销毁;
首先ThreadLocal在栈上面有一个地址,是ThreadLocal线程的引用地址,线程引用会指向堆里面的一个线程(new Thread()对象),每一个线程都有一个ThreadLocalMap的一个字典(里面有多个threadlocal),每一个线程都对应着一个ThreadLocalMap,ThreadLocalMap里面存放的是多个Entry,是一个桶,这个桶里面有一个key值和value值,这个key就是ThreadLocal中的key(ThreadLocal的引用),这个key是一个弱引用,进行普通的GC也会回收的,只要发生GC,就一定会把key值回收掉的,但是value再进行设计的时候没有使用弱引用,那么在进行普通垃圾回收的时候只能回收key,而这个value属于强引用,是和具体的线程和线程池绑定在一起的;
key是弱引用被回收是为了减少内存泄露,防止程序员马虎被移除的,value存的是具体的值,这个value是线程直接持有的,这个value值是被放在线程的栈里面的,不能直接回收,是线程持有的,不能直接回收,当手动调用remove
起码key值能被回收尽量能够腾出一些地方,万一value值以后还用怎么办?
以后再存还是比较浪费时间
JDK底层就是key可以被释放,VAUE不可以被释放(新录屏的threadlocal)还有countdownlatch录屏,四大天王问使用场景,AQS重听
CountDownLacth是一个一次性的计数器,当我进行new的时候,计数器的次数是10,当CountDownLatch不为0的时候对应线程await()后面都不能够进行向下执行,而Semporle是拿到计数器就可以执行程序下面的操作了,而CountDownLatch和CyCbarrier是在没有把这个计数器变成0之前,是不能这个执行这个awit()后续的代码的,所以CountDownLatch和信号量的底层实现是完全不一样的思路,就比如说王者荣耀队友的一个加载,一定要等到队友全部加载完成之后才能玩游戏,如果有一个玩家没有加载好这个资源,是绝对不可以开启这个游戏的,游戏经验,防止游戏失去公平性,此时使用的是CountDownLatch和Cycbarrier
但是CountDownLatch只能使用一次
CycBarrier是基于异步非阻塞来实现的,里面参数传递的new Runnable中的run方法是用于当计数器减为0之后然后执行的一个操作,当执行await()操作的时候CycBarrier的计数器会减1,但是当CycBarrier中的计数器没有减到0之前,所有的线程的await方法都是不会向后执行的,那么就会使所有的线程都会阻塞到await方法这里,当最后一个线程成功调用await()方法之后,CycBarrier的计数器减到0之后,那么所有线程原来阻塞在await()方法都会向下执行冲破屏障,继续执行;CycBarrier里面有计数器和ReentranLock
这两个东西都可以等待线程池中的多个任务执行完成以后再执行,从而在来执行后一步的操作,await之前是多个线程同时执行,await之后的代码再来进行统一的数据组装
一般来说数据组装的任务要等到计数器减为0的时候操作,都是等待一组线程执行完成操作之后再来操作,针对于CountDownLatch来说,数据组装可以写在await方法之后,await之前就是多个线程同时执行,但是CycBarrier可以将数据组装写到回调函数里面;
AQS是抽象同步队列,就是一个抽象类,Reentranlock和信号量和CountDownLatch在底层都是基于AQS来实现的,就是实现这些产品的一个公共的方法,无论是锁,是需要竞争那一把锁的,信号量也是需要得到停车位的,计数器-1,倒计时器也是有多个线程来竞争同一把锁,计数器-1,多个产品是具有一个公共的功能的,于是就把这个公共的功能封装起来实现了一个抽象类
AQS里面有两个内容,等待队列是通过链表来实现的,里面存放的是竞争同步状态的线程,同步器就是一种用于实现线程之间用于同步和协调的一种机制,多个线程竞争的就是这个同步状态,如果得到这个状态,就会把这个状态标记成已占用,把占用的线程写成当前的线程,等待队列里面就是所有需要竞争的线程,这些线程就是来竞争这个同步状态的,线程获取锁底层是通过CAS实现的比较并且替换,看源码只看获取锁和释放锁的操作;
Node:等待的线程实例
AQS实现同步状态+队列,尝试通过CAS获取同步状态,如果获取得到,就得到这把锁,如果获取不到这把锁,就加入到队列中,等待其他线程来唤醒
因为AQS本身的等待队列是有顺序的,可以基于AQS来实现公平锁或者是非公平锁
释放资源的时候:如果是同步锁的话,那么直接唤醒一个线程,如果是非公平锁,那么需要唤醒多个线程的
join底层是基于wait实现的
进程通信可以通过公共的资源,共享内存、管道、消息队列、socket
DefaultThreadFactory这是JDK的默认使用的线程工厂
线程资源很宝贵,是一个惰性创建的过程
拒绝策略出事了都不知道怎么出的,所以要实现自定义拒绝策略,灵活性最高,可以写一个通知到通知中心,记录日志,通知相关人员来看这个问题
假设你是一家快递公司的老板,如果快递件很少,那么让正式员工去送,如果快递过多,那么直接放到仓库,如果快递过多到仓库也放不下,那么直接股临时工
让程序变快+让程序变快的同时保证线程安全
synchronized是自适应自旋锁,如果上一次可以通过自旋锁成功了,那么这一次认为这一次有可能获取成功,这一次就多来几次,但是如果上一次没获取成功,这一次就少自旋几次
单例对象加voitile+各线程同时访问的同一个成员变量+设置的flag变量
线程饥饿,可能会使有些线程争抢锁永远也抢不到,永远无法享受CPU资源
获取锁的时候不是死等,GC线程就是守护线程