美文网首页
线程池原理剖析&锁的深度化

线程池原理剖析&锁的深度化

作者: 未闻花名未见你 | 来源:发表于2019-05-05 17:29 被阅读0次

线程池原理剖析

Java中开辟出了一种管理线程的概念,这个概念叫做线程池,可以方便的管理线程,减少内存消耗。

如何创建一个线程池?Java中已经提供了创建线程池的一个类:Executor
创建时一般使用它的子类:ThreadPoolExecutor。

public ThreadPoolExecutor(int corePoolSize,  
                              int maximumPoolSize,  
                              long keepAliveTime,  
                              TimeUnit unit,  
                              BlockingQueue<Runnable> workQueue,  
                              ThreadFactory threadFactory,  
                              RejectedExecutionHandler handler)

这是其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:



参数释义:

  • corePoolSize:池中的核心线程数量,核心线程在空闲的时候也不会被回收
  • maximumPoolSize:池中可以容纳的最大线程数。
  • keepAliveTime:池中除了核心线程之外的其他线程最长可以保留的时间,因为在线程池中,除了核心线程在无任务的情况下不被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间。
  • util:时间单位。
  • workQueue:等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。
  • threadFactory:创建线程的线程工厂。
  • handler:是一种拒绝策略,我们可以在任务满了知乎,拒绝执行某些任务。

执行流程

四种常见的线程池:

  • CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
  • SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
  • SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
  • FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
    常用线程池的执行流程图的参考文章:https://blog.csdn.net/lchq1995/article/details/85230399

锁的深度化

悲观锁

总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL代码:

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};  

CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。

悲观锁

总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
深入阅读,参考:https://blog.csdn.net/qq_34337272/article/details/81072874

重入锁

锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利。
重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。 在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。

读写锁

相比Java中的锁Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁。在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以应该允许多个线程能在同时读取共享资源。但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写(也就是说:读-读能共存,读-写不能共存,写-写不能共存)。这就需要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。

public class Cache {
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    // 获取一个key对应的value
    public static final Object get(String key) {
        r.lock();
        try {
            System.out.println("正在做读的操作,key:" + key + " 开始");
            Thread.sleep(100);
            Object object = map.get(key);
            System.out.println("正在做读的操作,key:" + key + " 结束");
            System.out.println();
            return object;
        } catch (InterruptedException e) {

        } finally {
            r.unlock();
        }
        return key;
    }

    // 设置key对应的value,并返回旧有的value
    public static final Object put(String key, Object value) {
        w.lock();
        try {

            System.out.println("正在做写的操作,key:" + key + ",value:" + value + "开始.");
            Thread.sleep(100);
            Object object = map.put(key, value);
            System.out.println("正在做写的操作,key:" + key + ",value:" + value + "结束.");
            System.out.println();
            return object;
        } catch (InterruptedException e) {

        } finally {
            w.unlock();
        }
        return value;
    }

    // 清空所有的内容
    public static final void clear() {
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }

    public static void main(String[] args) {
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Cache.put(i + "", i + "");
                }

            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    Cache.get(i + "");
                }

            }
        }).start();
    }
}

CAS无锁机制(待了解_20190505)

自旋锁(待了解_20190505)

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。

分布式锁

如果想在不同的jvm中保证数据同步,使用分布式锁技术。
有数据库实现、缓存实现、Zookeeper分布式锁

相关文章

  • 线程池原理剖析&锁的深度化

    线程池原理剖析 Java中开辟出了一种管理线程的概念,这个概念叫做线程池,可以方便的管理线程,减少内存消耗。 如何...

  • 5.线程池原理剖析&锁的深度化

    线程池 什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线...

  • 多线程(五)——线程池原理剖析&锁的深度化

    什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在...

  • 2021-03-01 深度剖析并发锁之AQS设计思想及原理

    深度剖析并发锁之AQS设计思想及原理 了解锁的场景和基本原理 多线程并发处理,没有同步导致结果异常本质上是由于++...

  • JAVA线程池原理及配置

    线程池原理剖析 提交一个任务到线程池中,线程池的处理流程如下:1、判断线程池里的核心线程是否都在执行任务,如果不是...

  • 线程池

    1.为什么要使用线程池?2.线程池的基本原理是什么?3.怎么学习线程池?线程池使用了池化技术。池化技术用于我们常见...

  • 线程池中你不容错过的一些细节

    线程池的工作原理 首先还是来复习下线程池的基本原理。 我认为线程池它就是一个调度任务的工具。 众所周知在初始化线程...

  • 线程池中你常会被坑的一些细节

    线程池的工作原理 首先还是来复习下线程池的基本原理。 我认为线程池它就是一个调度任务的工具。 众所周知在初始化线程...

  • 线程池的原理

    参考 深入Java源码理解线程池原理 线程池是对CPU利用的优化手段 线程池使用池化技术实现,替他的实现还有连接池...

  • 面试题2019年7月

    线程池原理 参考:Java 线程池原理分析 线程池工作原理:1、线程数量小于 corePoolSize,直接创建新...

网友评论

      本文标题:线程池原理剖析&锁的深度化

      本文链接:https://www.haomeiwen.com/subject/lkiqnqtx.html