Java并发编程基础

94 阅读5分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

一、什么是线程

启动一个Java程序,操作系统就会创建一个Java进程。操作系统调度的最小单元是线程,在一个进程里可以创建多个线程,这些线程都有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。

二、线程状态

Java线程在运行的生命周期中可能处于6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态。
image.png

Java程序运行中线程状态并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换
image.png

线程创建之后,调用start()方法开始运行。
当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也主就是超时时间到达将会返回到运行状态。
当线程调用同步方法时,在没有获到锁的情况下,线程将会进入阻塞状态。
线程在执行Runnable的run()方法之后将会进入到终止状态。

三、Daemon线程

Daemon线程是一种支持型线程,它主要被用作程序中后台调度以及支持性工作。当一个Java虚拟机中不存在非Daemon线程时,Java虚拟机将会退出。可以通过调用Thread.SetDaemon(true)将线程设置为Daemon线程。


四、线程间通信

线程开始运行,拥有自己的栈空间,按照既定的代码一步一步地执行,直到终止。如果仅仅是孤立地运行,那么没有一点价值,如果多个线程能够相互配合完成工作,这将会带来巨大的价值。

Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象以及成员变量分配的内存是共享内存中的,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是加速程序的执行,这是多核处理器的一个显著特性),所以程序在执行过程中, 一个线程看到的变量并不一定是最新的。

volatile和synchronized关键字

关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
对于同步块的实现使用monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成,无论采用那种方式,其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。

image.png
任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED.当访问Object的前驱(获得了锁的线程)释放锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。

等待/通知机制
等待/通知的相关方法是任意对象都具备的。因为这些方法被定义在所有对象的超类java.lang.Object上。
image.png
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系如同开关信号一样,用来完成等待方和通知方之间的交互工作。
注意细节:
1)使用wait()、notify()和notifyAll()时需要先对调用对象加锁;
2)调用wait()方法后,线程状态由Running变为Waiting,并将当前线程放置到对象的等待队列;
3)notify()或notityAll()方法调用后,等待线程依旧不会从wait()方法返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回;
4)notify方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由waiting变为Blocked.
5) 从wait()方法返回的前提是获得了调用对象的锁。
image.png

Thread.join()的使用
如果一个线程A执行了thread.join()语句,当前线程A等待thread线程终止之后才从thread.join返回。线程Thread除了提供join方法之外,还提供了join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间时没有终止,那么将会从该超时方法中返回。

ThreadLocal的使用
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程的一个值。
通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。