Java多线程和高并发学习笔记7
2021/5/4 20:28:57
本文主要是介绍Java多线程和高并发学习笔记7,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
字符串池
举个例子:
String a = "a" + "b"; System.out.println(a); System.out.println(a.intern()); System.out.println(a == a.intern()); String b = new String("ja" + "va"); System.out.println(b); System.out.println(b.intern()); System.out.println(b == b.intern());
上面的例子,一个返回true,一个返回false
原因是:
System类初始化的时候会声明一个java字符串常量
private static final String launcher_name = “java”;
采用new创建的字符串对象不进入字符串池
intern方法:返回一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池。
尽管在输出中调用intern方法并没有什么效果,但是实际上后台这个方法会做一系列的动作和操作。
在调用”ab”.intern()方法的时候会返回”ab”,但是这个方法会首先检查字符串池中是否有”ab”这个字符串,如果存在则返回这个字符串的引用,否则就将这个字符串添加到字符串池中,然会返回这个字符串的引用。
推荐阅读:深入理解Java虚拟机——JVM高级特性与最佳实践
可重入锁
可重入锁:又名递归锁,是指同一个线程在外层方法获得锁的时候,再进入该线程的内层方法会自动获取锁(前提:锁对象是同一个对象),不会因为之前已经获取过还没释放而阻塞,也就是同一个线程可以多次获取同一把锁。
synchronized和ReentrantLock就是可重入锁,可重入锁的一个优点就是可一定程度上的避免死锁
/* synchronized可重入锁的例子 */ public static final Object OBJECT = new Object(); /* ReentrantLock的可重入锁的例子 */ static Lock lock = new ReentrantLock(); public static class M1 implements Runnable { @Override public void run() { synchronized (OBJECT) { System.out.println("外层调用"); synchronized (OBJECT) { System.out.println("中层调用"); synchronized (OBJECT) { System.out.println("内层调用"); } } } } } public static void main(String[] args) { M1 m1 = new M1(); Thread thread = new Thread(m1); thread.start(); new Thread(() -> { lock.lock(); try { System.out.println("外层锁"); lock.lock(); try { System.out.println("内层锁"); } finally { lock.unlock(); } } finally { lock.unlock(); } }).start();
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加一
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么java虚拟机可以将其计数器加一,否则需要等待,直至持有线程释放该锁
当执行monitorexit时,java虚拟机则需将锁对象的计数器减一,计数器为零则代表该锁已经被释放
LockSupport
LockSupport类:线程等待唤醒机制,park和unpark,阻塞线程和解除线程阻塞
问题:我们已经有了wait/notify和await和single,为什么还需要park/unpark方法?
答案:因为wait/notify和await和single都有限制:
1、线程要先获得并持有锁,且必须在同步代码块中才可以。
2、必须要先等待后唤醒,线程才能被唤醒
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞以后也有对应的唤醒方法,归根结底,LockSupport调用的Unsafe中的native代码
LockSupport和每个使用它的线程都有一个许可(permit)关联,permit相当于1,0的开关,默认是0,调用一次unpark就加1变成1,调用一次park就会消费permit,也就是将1变成0,同时park立即返回
如再次调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时会调用unpark会把permit置位1。每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证
所以我们就可以回答下面的问题了:
问:为什么可以先唤醒线程后阻塞线程?
答:因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,而不会阻塞了
问:为什么唤醒两次后阻塞两次,但是最终结果还是会阻塞线程?
答:因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证,而调用两次park却需要消费两个凭证,证不够,不能放行,所以被阻塞
public static void main(String[] args) { Thread a = new Thread(() -> { try { System.out.println("开始阻塞3秒钟"); TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\tcome in"); LockSupport.park(); System.out.println("被唤醒"); }, "a"); a.start(); Thread b = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\tcome in"); LockSupport.unpark(a); System.out.println("通知a唤醒"); }, "b"); b.start(); }
AQS
AQS:AbstractQueuedSynchronizer(抽象的队列同步器)
线程阻塞机制:排队等候机制
int变量:state状态,0代表无线程占用,1代表有线程占用
CLH队列:双向队列,里面有Node节点存储Thread
Node类:头指针,尾指针,前指针,后指针,waitStatus(每个线程排队的状态)
ReentrantLock的公平锁和非公平锁的区别就在于hasQueuedPredecessors方法,这个方法判断了是否需要排队
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
具体的参考实现可以参考这篇博客:https://www.cnblogs.com/waterystone/p/4920797.html
这篇关于Java多线程和高并发学习笔记7的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-26手写消息中间件:从零开始的指南
- 2024-11-26Java语音识别项目资料:新手入门教程
- 2024-11-26JAVA语音识别项目资料:新手入门教程
- 2024-11-26Java语音识别项目资料:入门与实践指南
- 2024-11-26Java云原生资料入门教程
- 2024-11-26Java云原生资料入门教程
- 2024-11-26Java云原生资料:新手入门教程
- 2024-11-25Java创意资料:新手入门的创意学习指南
- 2024-11-25JAVA对接阿里云智能语音服务资料详解:新手入门指南
- 2024-11-25Java对接阿里云智能语音服务资料详解