java并发编程知识点总结

1、jsconsl检测死锁问题
2、Javap -verbose java.class 打开字节码文件
3、偏向锁:偏向锁等到出现多线程竞争的时候会释放;适用于单线程;当线程进来后检查锁标志位是不是偏向锁,如果是再查看偏向锁的线程id是不是与当前线程id一致,如果是一致的话就直接进入代码中,第一次进来的时候需要获取偏向锁,第二次以后就只需要对比当前线程id是否与偏向锁线程id一致。
3.1轻量级锁:同时让多个线程进入同步代码块;jvm在执行线程代码之前会在jvm的栈帧创建用于存储锁的空间;多线程同时访问时,偏向锁升级成轻量级锁,第二个线程尝试去获取修改mark word获取锁,但是锁已经被第一个线程获取到了,于是第二个线程一直不停的尝试去修改mark word也就是自旋锁,当第二个线程获取到以后就会将锁升级成为重量级锁。
4、锁重入:synchronized锁的是对象的实例;

如上图a方法中可以调用b方法这就是锁的重入,因为拿到的对象锁,访问的是同一个对象。
5、自旋锁、死锁、活锁、独占锁、共享锁,公平锁,非公平锁
(1)自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
(2)死锁:同一时刻A线程去获取B线程的锁,而B线程也去获取A线程的锁,AB线程相互持有对方资源的情况下就产生了死锁。
产生死锁的4个必要条件:
1.互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
2.请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
4.环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
预防死锁: 破坏产生死锁的4个必要条件
1.确定获取锁的顺序
2.超时放弃
(3)活锁:任务没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程;处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
(4)独占锁:该锁每一次只能被一个线程所持有。
(5)共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。
(6)公平锁:首先判断锁是否被占用如果被占用,则加入等待队列中,按先进先出的方式去获取锁。
(7)非公平锁:非公平锁逻辑基本跟公平锁一致,最本质的区别是,当当前的锁状态没有被占用时,当前线程可以直接占用,而不需要判断当前队列中是否有等待线程.
6、Lock指令:在多处理器的系统上
将当前处理器缓存行的内容写回到系统内存
这个写回到内存的操作会使在其他CPU里缓存了内存地址的数据失效
volatile只能保证多线程下原子性操作的读和写的可见性,比如,get和set操作的可见性;不能保 证非原子性操作的原子性,比如a++ 既有加又有赋值的非原子性操作。
7、AQS同步器:
公平锁与非公平锁的区别:公平锁使用hasQueuedPredecessors()方法判断是否有前置节点。
(1)使用AQS实现共享锁重写AQS的tryAcquireShared(int)和tryReleaseShared(int)方法;
(2)使用AQS实现独占锁重写AQS的tryAcquire(int)和tryRelease(int)方法。
(3)读写锁(ReentrantReadWriteLock):读读不互斥,写写,读写、写读互斥
(4)锁降级:
锁降级是指写锁降级为读锁。
在写锁没有释放的时候,获取到读锁,再释放写锁
(5)锁升级:(ReentrantReadWriteLock不支持锁升级)
把读锁升级为写锁
在读锁没有释放的时候,获取到写锁 ,再释放读锁(读写互斥)
8、wait等待、notify唤醒
(1)wait和notify必须在同步代码块中,使用在同一个对象实例中才能执行
(2)notify方法会随机叫醒一个处于wait状态的线程
(3)notifyAll叫醒所有的处于wait线程,争夺时间片的线程只有一个
(4)调用wait方法释放锁;调用notify拿到锁,等到synchronized的代码执行完毕释放锁之后才 能拿到锁
9、双向链表和单向链表原理和实现:双向链表有pre指向上个节点的指针和next指向下个节点的指针,而单向链表则这有next指向下一个节点的指针。
10、condition(等待唤醒机制):await()方法执行源码为,创建线程节点,尝试将线程节点放入到同步队列中去,如果放入失败则会放入到等待队列中,被唤醒之后将线程节点扔到同步队列中去,同步队列中等待抢占CPU资源;doSignal将等待队列中的节点叫醒放入到同步队列中去,如果第一个叫醒不了则会以此往后叫醒直到叫醒到一个可叫醒节点为止。每个线程可以创建多个condition也就是有多个等待队列。
同步队列(调用lock方法进入同步队列,等待抢占CPU资源)、等待队列(调用await()方法进入 等待队列)
ReentrantLock:ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。
ReenttantLock分为公平锁与非公平锁,默认是非公平锁;非公平锁线程首先使用cas获取锁,如 果获取失败则进入tryacquire方法如果是第一个线程进来则能拿到锁,如果是第二个线程则拿不 到, 除非这个线程与当前线程是一致,则可以拿到锁,但是有代价,要更新状态值,如果获取失败 则加入等待队列中,公平锁则直接进入tryacquire方法。
11、数据库连接池可以到开源中国中去看源码
12、join等待加塞方法的使用与运行原理
(1)意思是thread.join()方法阻塞调用此方法的线程进入等待状态,直到线程 thread完成,此线 程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程。
(2)内部使用isAlive()来判断thread对象是否存活,如果存活调用thread.join()方法的线程,在这 里就 是main主线程进入等待状态,直到该对象唤醒main主线程。
详情:https://blog.csdn.net/q5706503/article/details/84592449
13、ThreadLocal(线程局部变量)原理与使用:每个线程有一个局部变量,ThreadLocalMap中保存了 每一个线程的变量key是线程value是值。
(1)多线程的情况下去操作一个ThreadLocal类型的共享变量,每个线程使用set()方法set里面首 先调用getMap()获取当前线程的ThreadLocalMap如果获取不到则创建一个当前线程的 ThreadLocalMap,如果获取得到则覆盖当前线程的ThreadLocalMap;把ThreadLocal对象 当key,放进了ThreadLoalMap中,所以在线程1中set的值,对线程2来说是摸不到的,而 且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。
(2)根据上面Entry方法的源码,我们知道ThreadLocalMap是使用ThreadLocal的弱引用作为 Key;hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部 强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来, ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry 的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在 一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收, 造成内存泄露。
(3)JDK建议将ThreadLocal变量修饰符定义成private static,这样的话ThreadLocal的生命周 期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能 保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防 止内存泄露。
详情:https://blog.csdn.net/q5706503/article/details/85685304
14、CountDownLatch:线程调用await()方法进入等待状态,线程调用countDown()后将计数器减一, 直到count计数器递减到0后,所有调用了await()方法的线程被唤醒。
15、cyclicBarrier:相当于一个同步屏障,当所有的等待方法都到齐以后,所有的等待方法就会被唤醒。
举例:公司聚餐晚上聚餐,公司一共有30个人,那么只有等到所有人到齐了才能开始吃饭,也就 是来早了的只能等到所有人到齐了之后才能开始吃饭。
代码例子:CyclicBarrier barrier = new CyclicBarrier(10);这里的意思就是等到10个线程都调用了 await()方法后,线程才开始执行await()后面的逻辑代码。
16、semaphore(信号量):控制能被多少个线程访问。
17、Exchanger(数据交换):在A线程和B线程中分别使用使用exchanger(value)方法,A线程中的exchanger(aValue)方法的返回值返回的是B线程中exchanger(bValue)进去的值bValue,也就是说在A线程调用exchanger(aValue)的返回值是bValue,B线程则反之。
18、Future(提前完成任务):例如先使用future执行A线程,执行A线程期间主线程可以先执行其它的 逻辑代码,等到A线程执行完了,主线程再去获取A线程的结果。
Callable和Runnable的区别
Runnable run方法是被线程调用的,run方法是异步执行的
Callable的call方法,不是异步执行的,是由Future的run方法调用的
FutureTask源码:run()方法执行入口代码,在run()方法中调用set()方法执行等待操作,get()方法 获取到结果后唤醒所有等待的线程。
19、fork/join框架:意思是将一个大任务按一定的条件切割成小任务,然后小任务又可以再次切割成小任务直到切割满足一个给定条件的小任务,然后再把这些小任务的结果都合并起来。
20、并发容器与同步容器
同步容器:Vector–> ArrayList–>并发容器 CopyOnWriteArrayList
CopyOnWriteArrayList原理:使用add、set、remove操作时,先将原先的数字copy一份到以另一个数组中,然后在这个新的数组中进行add、set、remove操作,再将这个新的数组覆盖原来的数组。
同步容器:Hashtable–> HashMap–>并发容器 ConcurrentHashMap
非阻塞队列ConcurrentLinkedQueue
阻塞队列BlockingQueue对应的阻塞方法:put和take,内部使用condition方法实现。
21、消息队列:点对点和发布-订阅
22、多线程
(1)Thread使用start()和run()方法的区别,为什么一定要用start()方法才是启动线程?
因为thread线程有5种状态,创建-就绪-运行-阻塞-死亡这五种,那么我们的start方法呢就是就绪这一步,因为这个时候我们的线程并没有立即的执行,而是得等待,等到我们的cpu有空闲的时候,才会执行线程里面的run方法,等run方法执行完了,线程就结束了。而run方法是thread里面的一个普通的方法,所以我们直接调用run方法,这个时候它是会运行在我们的主线程中,因为一个程序只有一个主线程,所以这样的话就是按顺序执行,没有做到多线程的目的。
在源码中run方法就是执行run方法本身;而start则是首先判断threadStatus如果不等 于0说明线程被多次调用抛出异常,然后再往下把当前线程加入到线程组中,再调用start0()方法,标识started为true启动状态,如果没有开启,则标识为启动失败。
start0()方法使用了native修饰符。
(2)java中的native是什么?
native主要用于方法上
1、一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法 的实现由非Java语言实现,比如用C或C++实现。
2、在定义一个native方法时,并不提供实现体(比较像定义一个Java Interface),因 为其实现体是由非Java语言在外面实现的
主要是因为JAVA无法对操作系统底层进行操作,但是可以通过jni(java native interface) 调用其他语言来实现底层的访问。
23、线程池ThreadPoolExecutor
(1)创建线程池的参数分为:核心线程池大小、最大线程池大小、线程最大空闲时间、时间单位、 线程等待队列、线程创建工厂、拒绝策略
(2)工作原理:当执行execute()方法提交任务后如下图:

https://www.jianshu.com/p/23cb8b903d2c
24、Executor框架:是基于ThreadPoolExecutor实现的
25、Tomcat实现原理
26、Web服务器:tomcat
27、atomicLong、atomicInteger等等内部实现原理:使用cas自旋的方式去抢占同一个值来保证线程安全问题。
28、LongAdder实现原理:
(1)它在做运算的时候,先尝试cas更新,如果成功则与AtomicLong流程相似,如果失败,就不同了。 它不会死循环不断地进行cas直到成功,而是将线程分散到不同的区域,减轻线程数量太多造成的大 量失败,相当于分散唯一的计数值得热度。这个区域就是Cell 数组
(2)Cell是Striped64 的内部类,包装了long value, 内部使用cas操作。增加了注解 @sun.misc.Contended避免伪共享。缓存系统中是以缓存行(cache line)为单位存储的,当多线 程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪 共享。java8的这个注解避免伪共享的原理是在value前后各增加128字节大小的padding,使用2 倍于大多数硬件缓存行的大小来避免相邻扇区预取导致的伪共享冲突。
(3)原理图如下:

29、StampedLock锁
(1)读锁并不会阻塞写锁:当写的时候读失败了,再重新读。
(2)分为乐观锁和悲观锁,乐观锁时读写不互斥,悲观锁读写互斥。
(3)悲观锁不互斥的原理:获取读写锁的时候都会返回一个标识,然后在 读得时候使用validate(long)方法来判断是否发生了写操作,如果发生了就再次进行读取。
(4)StampedLock的等待队列与RRW的CLH队列相比,有以下特点:
1、当入队一个线程时,如果队尾是读结点,不会直接链接到队尾,而是链接到该读结点的 cowait链中,cowait链本质是一个栈;
2、当入队一个线程时,如果队尾是写结点,则直接链接到队尾;
3、唤醒线程的规则和AQS类似,都是首先唤醒队首结点。区别是StampedLock中,当唤醒的结点是读结点时,会唤醒该读结点的cowait链中的所有读结点(顺序和入栈顺序相反,也就是后进先出)。
4、另外,StampedLock使用时要特别小心,避免锁重入的操作,在使用乐观读锁时也需要遵循相应的调用模板,防止出现数据不一致的问题。
5、详情:
https://segmentfault.com/a/1190000015808032?utm_source=tag-newest
30、重排序
(1)什么是重排序
编译器和处理器为了提高程序的运行性能,对指令进行重排序。
(2)数据依赖性(as-if-serial)
写后读、读后写、写后写都存在数据依耐性。
(3)指令重排序分类
编译器重排序和处理器重排序
(4)为什么要进行指令的重排序
为了提高cpu性能
(5)指令重排序所带来的影响
(6)竞争与同步
31、happens-before
(1)程序顺序规则
单线程中的每个操作,总是前一个操作happens-before与该线程中的任 意后续操作;a happens-before b 意思就是a对b操作可见
(2)监视器规则
对于一个锁的解锁,总是happens-before于随后对这个锁的加锁;
a解锁 happens-before b加锁 意思是a对b可见
(3)volatile变量规则
对一个volatile域的写,happens-before于任意后续对这个volatile 域的读
(4)传递性
a happens-before b
b happens-before c
则 a happens-before c
(5)join和start规则
32、锁的内存语义
(1)锁的释放与获取所建立的happens-before关系
(2)锁的释放和获取的内存语义
锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的 线程发送消息。
线程A将共享变量修改后,会清空B线程的CPU缓存行的共享变量的数 据,当B线程获取的时候就会到主内存中去获取。
33、volatile的内存语义
(1)volatile读写所建立的happens-before关系
(2)Volatile读写的内存语义
当写一个volatile变量时,Java内存模型会把该线程对应的本地内存中的共享变量值刷新到主内存中。
当读一个volatile变量时,Java内存模型会把该线程对应的本地内存中共享变量置为无效,然后从主内存中读取共享变量。
34、final的内存语义
(1)写fianl域的重排序规则
写final域的重排序的规则禁止把final域的写重排序到构造方法之外
(2)读final域的重排序规则
在一个线程中,初次读对象引用和初次读该对象所包含的final域,java 内存模型禁止处理器重排序这两个操作。
(3)final域为静态类型
(4)final域为抽象类型
在构造方法内对一个final引用的对象的成员域的写入,与随后在构造方法外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值