线程的深入理解

线程

  • 进程:操作系统进行资源调度和分配的基本单位(例如浏览器,APP,JVM)。

  • 线程:进程中的最小执行单位(可以理解为一个顺序的执行流),同一个进程内的多个线程共享资源。

并行和并发

  • 并发:多线程抢占CPU,可能不同时执行,侧重于多个任务交替执行。

  • 现在的操作系统无论是windows,linux还是macOS等其实都是多用户多任务分时操作系统,使用这些操作系统的的用户可以“同时”干多件事情。但实际上,对于单机CPU的计算机而言,在同一时间只能干一件事,为了看起来像是“同时干多件事”分时操作系统把CPU的时间划分成了长短进本相同的时间区间,即“时间片”,通过操作系统的管理,把时间片依次轮流的分配给各个线程任务使用。我们看似的“同时干多件事”,其实是通过CPU时间片技术并发完成的。例如:多个线程并发使用一个CPU资源并发执行任务的线程时序图。

  • 并行:线程不共享CPU,每个线程一个CPU同时执行多个任务。并行只出现在多CPU或多核CPU中

生命周期

  • 一个线程从创建,运行,到最后销毁的这个过程称之为线程的生命周期,这些状态可归纳为:状态分别为新建状态,就绪状态,运行状态,阻塞状态,死亡状态。如下几个状态:

  • 上下文切换,一个线程得到CPU执行的时间是有限的。当此线程用完为其分配的CPU时间以后,cpu会切换到下一个线程执行。但是在这之前,线程需要将当前的状态进行保存,以便下次再次获得CPU时间片时可以加载对应的状态以继续执行剩下的任务。而这个切换过程是需要耗费时间的,会影响多线程程序的执行效率,所以在在使用多线程时要减少线程的频繁切换。减少多线程上下文切换的方案如下:

  • 无锁并发编程:锁的竞争会带来线程上下文的切换

  • CAS算法:CAS算法在数据更新方面,可以达到锁的效果

  • 使用最少线程:避免不必要的线程等待

  • 使用协程:单线程完成多任务的调度和切换,避免多线程

线程安全

  • 多个线程并发执行时,仍旧能够保证数据的正确性,这种现象称之为线程安全。

  • 多个线程并发执行时,不能能够保证数据的正确性,这种现象称之为线程不安全。

  • 产生的因素

  • 多个线程并发执行。

  • 多个线程并发执行时存在共享数据集(临界资源)。

  • 多个线程在共享数据集上的操作不是原子操作。

  • 保证线程安全方法

  • 对共享进行限制访问(例如加锁:syncronized,Lock)

  • 基于CAS实现非阻塞同步(基于CPU硬件技术支持)

  • 取消共享,每个线程一个对象实例(例如threadlocal)

  • JAVA中为了保证多线程并发访问的安全性,提供了基于锁的应用,大体可归纳为两大类,即悲观锁和乐观锁。

  • 悲观锁:假定会发生并发冲突,屏蔽一切可违反数据完整性的操作,例如java中可以基于syncronized,Lock,ReadWriteLock等实现。适合写操作多的场景,先加锁可以保证写操作时数据正确。

class Counter{
        private int count;
        public synchronized int count() {
            count++;
            return count;
        }
}
  • 乐观锁实现:假设不会发生冲突,只在提交操作时检查是否违反数据完整性,例如java中可借助CAS( Compare And Swap)算法实现(此算法依赖硬件CPU)。适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升

class Counter{
    private AtomicInteger at=new AtomicInteger();
    public  int count() {
        return at.incrementAndGet();
    }
}
  • 多个线程互相等待已经被对方线程正在占用的锁,导致陷入彼此等待对方释放锁的状态,这个过程称之为死锁。避免死锁的思路如下

  • 避免一个线程中同时获取多个锁

  • 避免一个线程在一个锁中获取其他的锁资源

  • 考虑使用定时锁来替换内部锁机制,如lock.tryLock(timeout)。

通讯

  • 线程通讯:java中的多线程通讯主要是共享内存(变量)等方式。

  • 进程通讯:java中进程通讯(IPC)主要是Socket,MQ等。

基于wait/nofity/notifyall实现线程通讯

  • Wait:阻塞正在使用监视器对象的线程,同时释放监视器对象

  • notify: 唤醒在监视器对象上等待的单个线程,但不释放监视器对象,此时调用该方法的代码继续执行,直到执行结束才释放对象锁

  • notifyAll: 唤醒在监视器对象上等待的所有线程,但不释放监视器对象,此时调用该方法的代码继续执行,直到执行结束才释放对象锁

  • 使用wait/notify/notifyAll的作用一般是为了避免轮询带来的性能损失。

基于Condition实现线程通讯

  • Condition 是一个用于多线程间协同的工具类,基于此类可以方便的对持有锁的线程进行阻塞或唤醒阻塞的线程。它的强大之处在于它可以为多个线程间建立不同的Condition,通过signal()/signalall()方法指定要唤醒的不同线程。

  • 基于Lock对象获取Condition对象

  • 基于Condition对象的await()/signal()/signalall()方法实现线程阻塞或唤醒。

ThreadLocal

  • 线程本地变量,访问这个变量的每个线程都会有这个变量的一个本地拷贝。

  • 作用,通过这种机制,可以保证一个对象在当前线程只有一份,并且取消了线程间的共享,是线程安全的。

  • 可以将某个对象存储到当前线程的ThreadLocalMap中

  • 可以从当前线程的ThreadLocalMap中获取到指定对象

原理

  • ThreadLocal可能会导致内存泄漏,因为线程中ThreadLocalMap内部对key的引用是弱引用(WeakReference),这个弱引用应用的key可能在GC时,就被移除了.但是value是强引用,key假如被GC了,那value也就不能访问到了,但他还占用着内存,所以它会导致内存泄漏。

线程池

  • 线程池本质上就是一个存储了多个线程的容器,容器中的线程可以实现复用。这种设计一种享元设计(享元模式)。使用线程池的原因

  • 线程对象的创建和销毁非常耗时

  • 复用线程,减少线程创建和销毁次数。

  • 创建

  • 基于Executors类中的方法进行池的创建(不推荐)

  • 基于ThreadPoolExecutor创建(推荐)

  • 进行池设计

  • 核心线程池数

  • 最大线程数

  • 阻塞式任务队列

  • 空闲线程的释放

  • 创建线的工厂

  • 任务拒绝策略


public class ThreadPoolExecutorTests {
    /**核心线程数*/
    static int corePoolSize=2;
    /**最大线程数*/
    static int maximumPoolSize=3;
    /**空闲线程释放时间*/
    static int keepAliveTime=60;
    /**阻塞式任务队列*/
    static BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(1);
    /**创建线程的工厂*/
    private static AtomicLong atomicLong=new AtomicLong(1);
    static ThreadFactory threadFactory=new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "tedu-2207-"+atomicLong.getAndIncrement());
        }
    };
    /**拒绝策略:
     * 1)AbortPolicy 直接拒绝,抛出异常
     * 2)DiscardPolicy 直接丢弃
     * 3)callerRunsPolicy 由调用着线程执行。
     * 4)DiscardOldestPolicy 丢弃队列中最早放入的任务,将当前任务交给线程池。
     * */
    static RejectedExecutionHandler handler=
            new ThreadPoolExecutor.CallerRunsPolicy();
    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor =
                new ThreadPoolExecutor(corePoolSize,//2
                        maximumPoolSize,
                        keepAliveTime,
                        TimeUnit.SECONDS,
                        workQueue,//核心线程都在忙则任务会存储到这个队列
                        threadFactory,
                        handler);
    }
}
  • 执行逻辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值