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

你可能感兴趣的:(多线程和高并发)