Contents

Java并发编程基础(一)

Java的进程和线程

./Java并发编程基础.assets/image-20250109160507316.png

是一个进程中最大的一块内存,堆是被进程中的所有线程共享的,是进程创建时分配的,堆里面主要存放使用new操作创建的对象实例。

方法区则用来存放JVM加载的类、常量及静态变量等信息,也是线程共享的。

​ 每个线程都有自己的资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,其他线程是访问不了的,除此之外栈还用来存放线程的调用栈帧。

程序计数器是一块内存区域,用来记录线程当前要执行的指令地址。

三种线程创建方式

  1. 实现Runnable接口的run方法
  2. 继承Thread类并重写run的方法
  3. 使用FutureTask方式

​ 使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以。

线程的通知与等待

​ Object类内的wait()函数会释放资源上的锁并且线程会被阻塞挂起,直到发生下面几件事情之一才返回:

(1)其他线程调用了该共享对象的notify()或者notifyAll()方法;

(2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。interrupt是属于Thread类的方法。

synchronized()会尝试获取资源上的锁,如果获取失败则进入该资源的等待队列中。synchronized是关键字,由jvm支持,不属于任何类。

wait(long timeout)函数会在超时后返回,继续往下执行。wait是属于Object类的方法。

wait(long timeout, int nanos) 函数,跟上面类似,只是时间更精确(纳秒)

notify()函数会唤醒被阻塞到该共享变量上的随机一个线程。notify是属于Object类的方法。

notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程,之后开始竞争锁,所以也是随机顺序。notifyAll是属于Object类的方法。

Thread类的join()方法

​ 可以实现的功能:等待某几件事情完成后才能继续往下执行,比如多个线程加载资源,需要等待多个线程全部加载完毕再汇总处理。

​ 线程A调用线程B的join方法后会被阻塞,直到B执行完。

Thread类的sleep()方法

sleep会让线程退出cpu调度,但不释放锁资源。

ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中提供的一个显式锁机制,它提供了与 synchronized 相似的功能,但更加灵活和强大。ReentrantLock 的主要特点包括可重入性、公平性和非阻塞获取锁的能力等。

Thread类的yield 方法

当一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态

线程中断

void interrupt()方法:中断线程

boolean isInterrupted() 方法:检测当前线程是否被中断,如果是返回true,否则返回false

boolean interrupted() 方法:检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是static方法,可以通过Thread类直接调用。

线程死锁

守护线程与用户线程

​ Java中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。只要有一个用户线程还没结束,正常情况下JVM就不会退出。

​ main线程运行结束后,JVM会自动启动一个叫作DestroyJavaVM的线程,该线程会等待所有用户线程结束后终止JVM进程

ThreadLocal

​ 在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量

./Java并发编程基础.assets/image-20250114150129818.png

​ 同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。根据上节的介绍,这应该是正常现象,因为在子线程thread里面调用get方法时当前线程为thread线程,而这里调用set方法设置线程变量的是main线程,两者是不同的线程,自然子线程访问时返回null。

​ 为了解决上节提出的问题,InheritableThreadLocal应运而生。InheritableThreadLocal继承自ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。