并发编程

线程基础、线程之间的共享和协作

在此向各位说声抱歉,此文章还在编辑完善中。

线程比java历史要悠久的多

什么是进程和线程

运行一个程序,需要 CPU,硬盘空间,IO

安装应用程序后,win打开一个应用,

应用程序是死的,进程是活的

进程:操作系统分配资源的最小单位

线程:是CPU调度的最小单位,必须依赖进程

一个进程可以同时拥有多个线程资源,多个线程之间可以共享进程资源,包括,内存,包括磁盘IO

CPU核心数和线程数的关系

CPU内核数和线程数是一对一的关系

CPU时间片轮询机制(RR调度)

上下文切换(切换时需要大概2w个CPU执行周期)

假如CPU有8个核心,所以可以有8个线程并行

并行和并发

并行:有两个咖啡机,两个队伍可以同时接咖啡

并发:不能脱离时间单位,如一台咖啡机,一分钟可以出多少杯咖啡

OS限制:

  • linux:一个进程只能开启1000个线程
  • win:一个进程只能开启2000个线程

原因:每个线程,开启后创建栈空间,每个栈空间1M

创建线程的方式有两种(JDK源码Thread接口明确注释)

  • Thread
  • Runnable
  • 区别:Thread还java中对线程的抽象,Runnable是对业务逻辑的抽象

线程安全停止的方法

JDK线程是协作式(通知),不是抢占式(强制)

  • 不推荐使用
    • stop():强制停止,
    • stop(Throwable obj):
    • destroy():
    • suspend():
    • resume():
    • CountStackFrames():统计此线程中的堆栈帧数,线程必须强制挂起
  • 推荐使用的方式
    • interrupt():中断线程(信号中断),处理逻辑可以忽略中断
    • interrupted():获取线程状态是否已经中断,并且重置中断状态,意思是如果这个方法被连续调用两次,第二次在获取当前状态是,获取到的值为false(一般工作中不会使用,暂不需要深入理解)
    • isInterrupted():获取线程状态是否已经中断
    • isInterrupted(boolean ClearInterrupted):获取线程状态是否已经中断,定义中断状态是否重置(一般工作中不会使用,暂不需要深入理解)

有一种中断方式,是在当前类中定义一个boolean静态变量,来标识当前线程是否被标注中断,这种方式不推荐,因为在执行时,如果被sleep()、wait()、take()几种阻塞,线程无法继续执行,一直被挂起

而interrupt()中断形式,JDK的这些阻塞方法,一定都会抛出InterruptException,在调用interrupt()后,通过try{阻塞方法}cache{侦听到中断信号},抛出异常后,中断信号会被重置为false,需要手动在cache执行一次interrupt()才能达到效果

死锁中不会处理信号中断

start()和run()区别

  • start():为Thread方法,在new Thread()时还没有跟线程交互,真正跟线程产生交互是在执行start()方法时调用的private native void start0()方法,且start()方法在调用时会验证当前线程是否已经处于启动状态,多次调用会抛出IllegalThreadStateException异常
  • run():就是一个类的方法,可以反复调用,调用run方法,在main中执行时,执行线程是main线程,并不是创建的新的线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTYzclt7-1578810602185)(%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B.assets/%E7%BA%BF%E7%A8%8B%E6%89%A7%E8%A1%8C%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.jpg)]

其他线程方法

yield()

使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源。注意:并不是每个线程都需要这个锁的,而且执行yield( )的线程不一定就会持有锁,我们完全可以在释放锁后再调用yield方法。

所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中马上又被执行。

wait()/notify()/notifyAll():后面会单独讲述

join()

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。(此处为常见面试考点)

线程的优先级

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。

守护线程

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。我们一般用不上,比如垃圾回收线程就是Daemon线程。

Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

synchronized内置锁

  • 用处和用法
    • 方法
    • 同步块
  • 对象锁
  • 类锁(JVM中唯一的class对象锁)
错误的加锁和原因分析

Integer 锁错误加锁方式

volatile关键字,最轻量级的同步机制

能保证可见性,不能保证原子性

一写多读的场景

可见性:在主线程改变一个变量时,在子线程可以拿到数据变化

不能够保证在多线程下的线程安全

ThreadLocal

获取当前线程的副本,并在副本的基础上存储数据,实现线程间的数据隔离

  • 用法
//创建ThreadLocal对象
private static ThreadLocal<Long> threadLocal = new ThreadLocal<Long>();
//使用对应的set,get方法存取值
public static void main(String[] args) {
  threadLocal.set(1L);
  threadLocal.get();
  threadLocal.remove();
}
  • 源码分析
//初始化后,使用set
public void set(T value) {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null)
    //如果能取到对应的map(Entry),则返回
  	map.set(this, value);
  else
    //如果没有取到,则创建
  	createMap(t, value);
}
//getMap取Thread下的threadLocals局部变量
ThreadLocalMap getMap(Thread t) {
  return t.threadLocals;
}
//Thread中的局部变量,非静态变量
public class Thread implements Runnable {
  ThreadLocal.ThreadLocalMap threadLocals = null;
}
//如果没有取到,则创建对应的ThreadLocalMap对象,并赋值给Thread下的threadLocals局部变量
void createMap(Thread t, T firstValue) {
  t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocalMap静态内部类构造方法
public class ThreadLocal<T> {
  //ThreadLocal的静态内部类ThreadLocalMap
  static class ThreadLocalMap {
    
  	//Entry[] table数组变量:在项目中new ThreadLocal<T>()必然有多处,然而Thread中的threadLocals变量只有一个,个人猜想,是为了解决这个问题而设定的,每当new ThreadLocal<T>(),都会计算当前对象的hashcode,并对数组对应位置进行赋值(当hashcode重复是,理论上是链表的形式实现)
  	private Entry[] table;
  	//table 数组的初始大小
  	private static final int INITIAL_CAPACITY = 16;
    /**ThreadLocalMap构造方法
    * @param firstKey 当前new ThreadLocal对象作为key
    * @param firstValue
    */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
      table = new Entry[INITIAL_CAPACITY];
      int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
      table[i] = new Entry(firstKey, firstValue);
      size = 1;
      setThreshold(INITIAL_CAPACITY);
    }
    //下一个要调整大小的阈值(未查明)
    private void setThreshold(int len) {
      threshold = len * 2 / 3;
    }
  }
}

  • 内存泄露问题

    弱引用,key在发生GC的时候,弱引用会被强制清除,反应到代码上就是Entry中的key被清除掉了,但是Entry中的value还通过ThreadLocalMap强引用而无法清除,所以会发生内存泄露问题。在实际操作中,发现内存没有爆满,是因为在get,set方法中都可能会执行replaceStaleEntry(key, value, i)方法,清除key不存在的Entry,从而尽可能的保证内存不爆满。

  • 线程不安全问题

    线程的引用是堆中的对象,如果引用是静态变量,那么多个ThreadLocal引用都是同一个对象,就会发生线程不安全的问题

线程协作

线程的并发工具类

原子操作CAS

显示锁和AQS

并发容器

线程池和Exector框架

线程安全

实战项目-并发任务执行框架

实战项目-性能优化实战

JMM和底层原理

Java8新增并发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值