线程七

53 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Synchronized中的锁优化机制(1.8版本)

1.锁膨胀/锁升级

体现了synchronized能够“自适应”这样的能力
一开始是无锁状态
这个时候突然有个线程加锁,这个时候就会变成偏向锁(这个不算是真的锁,只是一个标记,可以减少消耗,类似懒汉模式)
这个时候又有其他线程来竞争了,这个时候就发展成了自旋锁
这个时候竞争加剧,这个时候变成了重量级锁

2.锁粗化

这边的粗细指的是锁的粗细。加锁的时候涉及到的范围。加锁代码范围越大,认为这个锁越粗,反之就是锁越细。
锁越粗,加锁的开销就比较小
锁越细,线程之间的并发程度就高
编译器会有一个优化,如果有个地方太细就会加粗,有个地方太粗就会变细。

3.锁消除

有些代码不用加锁,结果加锁了,编译器就会把锁去掉。

JUC

Callable

这是一个接口,可以创建线程。
解决Runnable不方便返回值的问题。
Callable<Integer> callable = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 0;i<= 100;i++){
            sum += i;
        }
        return sum;
    }
};

这个时候我们就委派好了任务给这个Callable接口,那我们现在怎么运行这个呢?光靠Thread可以吗?明显是不可以的,这个时候我们就要靠FutureTask。Thread就能接收FutureTask了。

FutureTask<Integer> task = new FutureTask<>(callable);
Thread t = new Thread(task);
t.start();
System.out.println(task.get());

ReentrantLock

可重复锁。

基础方法

lock()
unlock()
把加锁和上锁分开了。

和synchronized区别

1.synchronized是一个关键字,它的背后是C语言实现的,ReentrantLock是一个标准库中的类,它背后是java实现的。
2.synchronized不需要手动释放锁,但是ReentrantLock需要手动释放锁
3.synchronized如果锁竞争失败会阻塞,但是ReentrantLock锁竞争失败就会返回
4.synchronized是一个非公平锁,但是ReentrantLock是一个可以转化的,可以变成公平或者非公平
5.基于synchronized衍生出来的等待机制,是wait和notify,功能有限,
  但是ReentrantLock衍生出来的等待机制,是Condition类,功能丰富。
  

semaphore

信号量

是一个更广义的锁,锁是信号量的里第一种特殊情况,叫做‘二元信号量’

Semaphore semaphore = new Semaphore(4);

这个是初始化了一下表示有四个可用资源

semaphore.acquire();

这个表示申请了一个资源

semaphore.release();

这个表示释放了一个资源。

CountDownLatch

门阀

countDown

给每个线程里面去调用,就表示到达终点

await

是给等待线程去调用的,当所有的任务都到达终点的时候,await就从阻塞中返回,就表示任务完成

基本使用

public class Demo31 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);
        for(int i = 0;i<10;i++){
            Thread t = new Thread(()->{
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "到达终点");
                    latch.countDown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
        latch.await();
    }
}

多线程使用哈希表

HashMap本身是不安全的。

1.HashTable(不推荐)

给关键方法加锁,针对this加锁。当我们有多个线程来访问HashTable的时候就会有很大的锁竞争,效率低

2.ConcurrentHashMap(推荐)

1.给每个元素的头结点上锁,这样就可以大大减小锁的竞争,提高效率。

2.只对写加锁,对读只是添加了volatile

3.更广泛的使用了CAS

4.对于扩容的操作优化,让一次put操作搬运一点点的元素直到搬运完毕,老的表才会被销毁。