并发

175 阅读6分钟

并发

实现多线程的方式
  1. 继承Thread类:Thread 类本质上是实现了 Runnable 接口的一个实例,通过start()方法启动线程,真正实现了多线程运行

  2. 实现Runnable接口:无返回值

  3. 实现Callable接口:有返回值,Call方法执行完后,会返回一个Object对象

  4. 线程池:降低资源消耗,提高响应速度,控制最大并发数,统一分配调优,线程池中的任务是放入到阻塞队列中的

    1. Executors.newFixedThreadPool(int i):固定数量的线程池

    2. Executors.newSingleThreadExecutor:单线程池

    3. Executors.newCacheThreadPool():可扩容线程池

    4. Excutors.newScheduleThreadPool:支持定时的周期性的任务执行,类似于Timer

    5. 默认的Executors创建的线程池,底层都是用的LinkedBlockingQueue,这是一个基于链表的阻塞队列,用两个独立锁(生产者端/消费者端)来提高整个队列的并发性能,但是他的默认容量是 Integer.MAX_VALUE ,相当是无界的了,可能会堆积大量的请求导致OOM内存不足(Out of Memory),所以可以通过ThreadPoolTaskExecutor手动去创建线程池

        public ThreadPoolExecutor(int corePoolSize,  // 核心线程数
                                  int maximumPoolSize, // 最大线程数
                                  long keepAliveTime, // 空闲线程存活时间
                                  TimeUnit unit, // 时间单位
                                  BlockingQueue<Runnable> workQueue, // 阻塞队列
                                  ThreadFactory threadFactory, // 线程工厂
                                  RejectedExecutionHandler handler)//  线程数量达上限后的拒绝策略
                 // caller_runs: 让调用者所在的线程来执行
                 // 有直接抛异常的,有直接丢弃的,还有丢弃最老的任务的
                 // ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理

image.png

线程生命周期
  1. 新建状态:new个Thread对象出来,等待start()

  2. 就绪状态:对象调用start()方法,进入就绪队列中,等待CPU调度

  3. 运行状态:就绪状态的线程获取cpu资源,执行run()方法

  4. 阻塞状态:是指线程因为某种原因放弃了cpu使用权,暂时停止运行

    1. 等待阻塞:执行了wait()方法,线程会释放锁,进入等待池,可以调用notify()方法去唤醒
    2. 同步阻塞:线程在获取 synchronized 同步锁失败
    3. 其他阻塞:调用了sleep() 或 join() 或yield()方法,也会进入阻塞
  5. 死亡状态:线程完成或终止

区别
1. sleep()线程休眠,不会释放锁,时间到了就会自动恢复到可运行状态
2. join()方法只会让主线程进入等待池然后等到调用join方法的线程执行完后才去唤醒它;他是自动唤醒的;不影响同一时刻其他运行状态的线程。(join源码里是调用的wait方法,但是没有调用notify去唤醒,一个线程执行完后会自动调用自己的notifyAll方法,来释放所有资源和锁)
3. yield()方法会让线程直接进入就绪状态,跟其他线程一起重新竞争CPU资源
  1. 死锁:是两个或多个进程在执行过程中,因去争夺资源而造成一种互相等待的现象 产生原因:

    1. 系统资源不足;资源分配不当
    2. 加锁后,没有释放锁,或者还没来得及释放锁,程序挂了,设置过期时间
  2. Synchronized:

    1. 是Java关键字,可以用在修饰代码块或者方法

    2. 是一种非公平锁,不可中断,会自动释放锁,没有绑定条件,要么随机,要么全部唤醒

    3. 在运行时会有三种存在方式:偏向锁,轻量级锁,重量级锁。

      1. 偏向锁:一个线程获取锁之后再尝试获取锁时会自动获取锁(可重入锁)
      2. 轻量级锁:锁是偏向锁时,被另一个线程访问了,会升级为轻量级锁,自旋方式获取(自旋锁)
      3. 重量级锁:自旋时间过长,就会进入阻塞状态,升级为重量级锁,会使其他线程阻塞,性能降低(排他锁)
  3. Lock:

    1. 是一个接口,多个线程只是进行读操作的话不会阻塞
    2. 是一种非公平锁,可中断,可以绑定多个条件,可精确唤醒;
    3. 需要手动释放锁,如果发生异常,没有主动释放锁,会造成死锁,finally块中释放
  4. volatile:仅能使用在变量级别;变量修改可见性;不保证原子性;不会线程阻塞;可以保证下一个读取操作是在上一个写操作之后。本质是在告诉jvm当前变量在工作内存中的值是不确定的,需要从主存中读取

  5. Threadlocal是Java提供的线程本地存储机制,相当于给每个线程创造了一个副本,线程私有,互不影响

    1. Threadlocal底层是通过ThreadLocalMap来实现的,如果不手动remove()的话,会造成内存泄漏

    2. ThreadLocalMap的key是弱引用,会被GC回收,而value是强引用,只要对象一直活着,就不会被回收,比如线程池里

    3. 内存泄露:不再被程序使用的对象或变量一直占据在内存里

      1. 强引用:平时用的new就是强引用,只要对象一直活着,就不会被回收
      2. 软引用:通过SoftReference类实现的,内存有空闲就留,没有就删,比如缓存图片
      3. 弱引用:通过WeakReference类实现的,在GC的时候,不管内存空间足不足都会回收这个对象
      4. 虚引用:通过PhantomReference类实现,任何时候都可能被回收,像没有引用一样(对象被回收之后发一个系统通知)
  6. 公平锁:顺序获取锁

  7. 非公平锁:无法保证等待的线程获取锁的顺序

    1. 可以减少CPU唤醒线程的开销,整体的吞吐效率会高
    2. 缺点:可能会有线程长时间甚至永远获取不到锁,导致饿死
  8. ReadWriteLock:读写锁,对一个资源的访问分成了2个锁,一个读锁和一个写锁。在没有写锁的情况下,读是无阻塞的

  9. ReentrantReadWriteLock:可重入读写锁,readLock(),writeLock()获取读锁和写锁

  10. 线程通信

    1. 通过共享内存或基于网络
    2. Java中的wait(),notify()就是阻塞和唤醒进行通信
    3. while轮询;管道
  11. 业务场景

    1. 并发高,任务执行时间短的业务:线程数设置为cpu核数,减少线程上下文的切换

    2. 并发不高,任务执行时间长的业务分为两种情况

      1. 业务集中IO上,加大线程数,让CPU处理更多的业务,因为IO操作占用CPU资源很少
      2. 业务集中在CPU上,减少线程数,减少上下文切换