java多线程基础与线程池
1.进程与线程
【进程】
1.进程一般由程序,数据集合和进程控制快三部分组成。
(1)程序用于描述进程要完成的功能,是控制进程执行的指令集;
(2)数据集合是程序在执行时所需要的数据和工作区;
(3)程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志
2.进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进行一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序,数据和进程控制块三部分组成.
【线程】
1.由来:
早期的操作系统中,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。而进程之间切换的开销很大,导致了线程了产生。
2.什么是线程?
**线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。**一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
【进程与线程的区别】
1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位(进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程));
2.一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
3.进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
4.调度和切换:线程上下文切换比进程上下文切换要快得多
5.线程和进程都是一种抽象的概念,线程是一种比进程还小的抽象,线程和进程都可用于实现并发。在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位,它相当于一个进程里只有一个线程,进程本身就是线程。所以线程有时被称为轻量级进程。后来,随着计算机的发展,对多个任务之间上下文切换的效率要求越来越高,就抽象出一个更小的概念-线程,一般一个进程会有多个(也可以是一个)线程。
6.不同进程之间也可以通信,不过代价比较大。现在一般叫单线程与多线程,都是指在一个进程内的单和多
【任务调度】
大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态,等待下一个属于它的时间片的到来。这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发
【为何不使用多进程而是使用多线程?】
线程廉价,线程启动比较快,退出比较快,对系统资源的冲击也比较小。而且线程彼此分享了大部分核心对象(File Handle)的拥有权如果使用多重进程,但是不可预期,且测试困难。
2.线程的创建与启动
Java中启用多线程有两种方式:①继承Thread类;②实现Runnable接口
2.1 继承Thread类
/**
* Dog类继承Thread类
*/
public class Dog extends Thread{
/**
* 覆写run()方法,定义该线程需要执行的代码
*/
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println("Dog线程:"+Thread.currentThread().getName()+":"+i);
}
}
}
public class DogMain {
public static void main(String[] args) {
System.out.println("当前线程:" + Thread.currentThread().getName());
Dog dog = new Dog();
Dog dog1 = new Dog();
dog.start();
dog1.start();
}
}
2.2 实现Runnable接口
/**
* Runable 方式创建线程
*/
public class RunnableThread implements Runnable {
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
RunnableThread runnableThread = new RunnableThread();
Thread rt = new Thread(runnableThread);
rt.start();
}
}
2.3 Callable
实现 Runnable 接口与实现 Callable 接口的方式基本相同,只是 Callable 接口里定义的方法有返回值,可以声明抛出异常而已。因此可以将实现 Runnable 接口和实现 Callable 接口归为一种方式。
/**
* Callable 方式创建线程
*/
public class TestCall {
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int i=0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+"循环变量i:" + i);
}
return i;
});
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"循环变量i:" + i);
if(i == 20){
new Thread(task,"有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:"+task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
2.4如何停止线程
2.4.1判断线程是否终止
JDK提供了一些方法来判断线程是否终止 —— isInterrupted()和interrupted()
2.4.2停止线程的方式
这个是得到线程信息中比较重要的一个方法了,因为这个和终止线程的方法相关联。先说一下终止线程的几种方式:
- 等待run方法执行完
- 线程对象调用stop()
- 线程对象调用interrupt(),在该线程的run方法中判断是否终止,抛出一个终止异常终止。
- 线程对象调用interrupt(),在该线程的run方法中判断是否终止,以return语句结束。
第一种就不说了,第二种stop()方法已经废弃了,因为可能会产生如下原因:
- 强制结束线程,该线程应该做的清理工作,无法完成。
- 强制结束线程,该线程已操作的加锁对象强制解锁,造成数据不一致。
第三种,是目前推荐的终止方法,调用interrupt,然后在run方法中判断是否终止。判断终止的方式有两种,一种是Thread类的静态方法interrupted()
,另一种是Thread的成员方法isInterrupted()
。这两个方法是有所区别的,第一个方法是会自动重置状态的,如果连续两次调用interrupted()
,第一次如果是false,第二次一定是true。而isInterrupted()
是不会的。 例子如下:
public class ExampleInterruptThread extends Thread{
@Override
public void run() {
super.run();
try{
for(int i = 0 ; i < 50000000 ; i++){
if (interrupted()){
System.out.println("已经是停止状态,我要退出了");
throw new InterruptedException("停止.......");
}
System.out.println("i=" + (i + 1));
}
}catch (InterruptedException e){
System.out.println("顺利停止");
}
}
}
测试的代码如下:
public class ExampleInterruptThreadTest extends TestCase {
public void testRun() throws Exception {
ExampleInterruptThread thread = new ExampleInterruptThread();
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
第四种方法和第三种一样,唯一的区别就是将上面的代码中的抛出异常换成return,个人还是喜欢抛出异常,这里处理的形式就比较多,比如打印信息,处理资源关闭或者捕捉之后再重新向上层抛出。 注意一点,我们上面抛出的异常是InterruptedException
,这里简单说一下可能产生这个异常的原因,在原有线程sleep的情况下,调用interrupt终止线程,或者先终止线程,再让线程sleep。
3线程的生命周期(6种状态)
-
初始态(NEW)
创建一个Thread对象,但还未调用start() 启动线程,线程处于初始状态。
-
运行态(RUNNABLE)
就绪态
-
该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行;
-
所有就绪态的线程存方在就绪队列中;
运行态
-
获得CPU执行权,正在执行的线程;
-
由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程
-
-
阻塞态(BLOCKED)
- 当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。
- 而在java中,阻塞态专指请求锁失败时进入的状态。
- 由一个阻塞队列存放所有阻塞态的线程。
- 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。
-
等待态(WAITING)
https://www.jianshu.com/p/b95f59351237 ( park )
-
当前线程中调用wait、join、park函数时,当前线程就会进入等待态。
-
也有一个等待队列放所有等待的线程。
-
线程处于等待态表示它需要等待其他线程的指示才能继续运行。
-
进入等待状态的线程会释放CPU执行权,并释放资源(如:锁)
-
-
超时等待态(TIMED_WAITING)
- 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil 时,就会进入该状态。
- 它和等待状态一样,并不是因为请求不到资源,而是主动进入,并且进入之后需要其他线程唤醒。
- 进入该状态后释放CPU执行权和 占有的资源
- 与等待状态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
-
终止态(TERMINATED)
- 线程执行结束后的状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsKiXg37-1591967888358)(D:\zwh\notes\picture\1589161052975.png)]
/**
* 线程状态转换
* 切换到java的bin目录
* jps
* jstack
*/
public class ThreadState {
public static void main(String[] args) {
new Thread(new TimeWaiting(),"TimeWaitingThread").start();
new Thread(new Waiting(),"WaitingThread").start();
//使用两个Blocker线程,一个获取锁成功,另一个被阻塞
new Thread(new Blocked(),"BlockedThread-1").start();
new Thread(new Blocked(),"BlockedThread-2").start();
}
//该线程不断的进行睡眠
static class TimeWaiting implements Runnable{
@Override
public void run(){
while(true){
SleepUtils.second(100);
}
}
}
//该线程在Waiting.class 实例上等待
static class Waiting implements Runnable{
@Override
public void run() {
while(true){
synchronized (Waiting.class){
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//该线程在Blocked.class实例上加锁后不会释放该锁
static class Blocked implements Runnable{
@Override
public void run(){
synchronized (Blocked.class){
while (true){
SleepUtils.second(100);
}
}
}
}
}
4.线程同步
同步是指程序中用于控制不同线程间操作发生相对顺序的机制。
在共享内存并发模型里,同步是显示进行的。必须显式指定某个方法或者代码段需要在线程之间互斥执行。
在消息传递的并发模型里,由于消息的发送必须在消息的接收之前,因此是隐式进行的。
4.0 并发编程中的三个概念
在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。我们先看具体看一下这三个概念:
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。
- 指令重排序一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
4.1 synchronized 关键字
- 为什么要使用synchronized
在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性)。
- 实现原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
- synchronized的三种应用方式
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
- synchronized的作用
Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码
eg:
/**
* synchronized 实现线程同步
* volatile 关键字实现线程同步
*/
public class SynchronizedThread {
class Bank {
private int account = 100;
//private volatile int account = 100;
public int getAccount() {
return account;
}
/**
* 用同步方法实现
*
* @param money
*/
public synchronized void save(int money) {
account += money;
System.out.println(Thread.currentThread().getName()+ "账户余额为:" + account);
}
/**
* 用同步代码块实现
*
* @param money
*/
public void save1(int money) {
synchronized (this) {
account += money;
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//bank.save1(10);
bank.save(10);
/*
在这里去使用bank.getAccount() 也会有线程安全问题,因为getAccount并没有加锁
*/
//System.out.println(Thread.currentThread().getName()+ "账户余额为:" + bank.getAccount());
System.out.println("----------");//不加这一行线程一会一直占用锁,直到执行完
}
}
}
/**
* 建立线程,调用内部类
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
Thread thread1 = new Thread(new_thread,"线程1");
thread1.start();
Thread thread2 = new Thread(new_thread,"线程2");
thread2.start();
}
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
}
4.2 Lock
Lock完全用Java写成,在java这个层面是无关JVM实现的。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类,实现思路都大同小异。
如果Lock类只有lock和unlock方法也太简单了,Lock类提供了丰富的加锁的方法和对加锁的情况判断。主要有
- 实现锁的公平
- 获取当前线程调用lock的次数,也就是获取当前线程锁定的个数
- 获取等待锁的线程数
- 查询指定的线程是否等待获取此锁定
- 查询是否有线程等待获取此锁定
- 查询当前线程是否持有锁定
- 判断一个锁是不是被线程持有
- 加锁时如果中断则不加锁,进入异常处理
- 尝试加锁,如果该锁未被其他线程持有的情况下成功
实现公平锁
在实例化锁对象的时候,构造方法有2个,一个是无参构造方法,一个是传入一个boolean变量的构造方法。当传入值为true的时候,该锁为公平锁。默认不传参数是非公平锁。
公平锁:按照线程加锁的顺序来获取锁 非公平锁:随机竞争来得到锁 此外,JAVA还提供
isFair()
来判断一个锁是不是公平锁。
获取当前线程锁定的个数
Java提供了getHoldCount()
方法来获取当前线程的锁定个数。所谓锁定个数就是当前线程调用lock方法的次数。一般一个方法只会调用一个lock方法,但是有可能在同步代码中还有调用了别的方法,那个方法内部有同步代码。这样,getHoldCount()
返回值就是大于1。
下面的方法用来判断等待锁的情况
获取等待锁的线程数
Java提供了getQueueLength()
方法来得到等待锁释放的线程的个数。
查询指定的线程是否等待获取此锁定
Java提供了hasQueuedThread(Thread thread)
查询该Thread是否等待该lock对象的释放。
查询是否有线程等待获取此锁定
同样,Java提供了一个简单判断是否有线程在等待锁释放即hasQueuedThreads()
。
下面的方法用来判断持有锁的情况
查询当前线程是否持有锁定
Java不仅提供了判断是否有线程在等待锁释放的方法,还提供了是否当前线程持有锁即isHeldByCurrentThread()
,判断当前线程是否有此锁定。
判断一个锁是不是被线程持有
同样,Java提供了简单判断一个锁是不是被一个线程持有,即isLocked()
下面的方法用来实现多种方式加锁
加锁时如果中断则不加锁,进入异常处理
Lock类提供了多种选择的加锁方法,lockInterruptibly()
也可以实现加锁,但是当线程被中断的时候,就会加锁失败,进行异常处理阶段。一般这种情况出现在该线程已经被打上interrupted的标记了。
尝试加锁,如果该锁未被其他线程持有的情况下成功
Java提供了tryLock()
方法来进行尝试加锁,只有该锁未被其他线程持有的基础上,才会成功加锁。
eg:
/**
* 使用可重入锁实现线程同步
*/
public class LockThread {
class Bank {
private int account = 100;
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
/**
* 用同步方法实现
*
* @param money
*/
public void save(int money) {
lock.lock();
try{
account += money;
System.out.println(Thread.currentThread().getName()+ "账户余额为:" + account);
}finally{
lock.unlock();
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bank.save(10);
System.out.println("----------");
}
}
}
/**
* 建立线程,调用内部类
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
Thread thread1 = new Thread(new_thread,"线程1");
thread1.start();
Thread thread2 = new Thread(new_thread,"线程2");
thread2.start();
}
public static void main(String[] args) {
LockThread lt = new LockThread();
lt.useThread();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yYCQONV8-1591967888361)(D:\zwh\notes\picture\1841232-20191026150717165-1256103415.png)]
4.3 volatile 关键字
https://mp.weixin.qq.com/s/5V187MD9gW_zKVLSIOn2sQ
volatile的特性
理解volatile特性的一个好方法是把对volatile变量的单个读/写,看成是使用同一个锁对这 些单个读/写操作做了同步
简而言之,volatile变量自身具有下列特性:
- 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写
入。- 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不
具有原子性。
volatile的使用场景
synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而 volatile 关键字在某些情况下性能要优于 synchronized,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下 2 个条件:
对变量的写操作不依赖于当前值
该变量没有包含在具有其他变量的不变式中
volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。
1.状态标记量
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
----------------------------------------
2.双重检测
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
4.4 ThreadLocal 本地变量
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
public class MyBank {
private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
public void save(int money){
account.set(account.get()+money);
System.out.println(Thread.currentThread().getName()+ "账户余额为:" + account.get());
}
}
/**
* 使用局部变量实现线程通信
*/
public class TestThreadLocal {
class NewThread implements Runnable {
public MyBank bank = new MyBank();
public NewThread(MyBank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bank.save(10);
System.out.println("----------");
}
}
}
/**
* 建立线程,调用内部类
*/
public void useThread() {
MyBank bank = new MyBank();
NewThread new_thread = new NewThread(bank);
Thread thread1 = new Thread(new_thread,"线程1");
thread1.start();
Thread thread2 = new Thread(new_thread,"线程2");
thread2.start();
}
public static void main(String[] args) {
TestThreadLocal lt = new TestThreadLocal();
lt.useThread();
}
}
4.5 死锁------------
public class TestA {
public synchronized void foo(TestB b){
System.out.println("当前线程名:"+Thread.currentThread().getName()+"-进入A 实例的 foo方法");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"-企图调用B 的方法");
b.last();
}
public synchronized void last(){
System.out.println("进入了A 类的last 方法");
}
}
----------------------------------------------------------------------------------------
public class TestB {
public synchronized void bar(TestA a){
System.out.println("当前线程名:"+Thread.currentThread().getName()+"-进入B 实例的 bar方法");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"-企图调用A 的方法");
a.last();
}
public synchronized void last(){
System.out.println("进入了B 类的last 方法");
}
}
----------------------------------------------------------------------------------------
public class TestDeadLock implements Runnable{
TestA a = new TestA();
TestB b = new TestB();
public void init(){
Thread.currentThread().setName("主线程");
a.foo(b);
System.out.println("进入主线程之后");
}
@Override
public void run(){
Thread.currentThread().setName("副线程");
b.bar(a);
System.out.println("进入副线程之后");
}
public static void main(String[] args) {
TestDeadLock tdl = new TestDeadLock();
Thread dt = new Thread(tdl);
dt.start();
tdl.init();
}
}
5.线程间的通信
通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信
java的并发采用的是共享内存模型,java之间的通信总是隐式进行,整个通信对程序员完全透明。
5.1 等待(wait)/通知(notify)机制
/**
* 等待/通知机制
* (1) 使用wait()、notify()和notifyAll()时需要先对调用对象加锁。
* (2) 使用wait() 方法后,现场装填由RUNNING变为WAITING,并将当前线程放置到对象的等待队列
* (3) notify 或 notifyAll方法调用后,等待线程依旧不会从wait 返回,
* 需要调用notify 或 notifyAll 的线程释放锁之后,等待线程才有机会从wait返回
* (4) notify 方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll
* 方法则是将所有的线程全部移到同步队列,被移动的线程装填由WAITING变为BLOCKED
* (5) 从wait 方法返回的前提是获取了调用对象的锁
*/
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws Exception {
Thread waitThread = new Thread(new Wait(),"WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(),"NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable{
@Override
public void run(){
//加锁
synchronized (lock){
//当条件不满足时,继续wait,同时释放了lock的锁
while (flag){
try {
System.out.println(Thread.currentThread()+"flag is true. wait@" +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread()+"flag is false. running@" +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run(){
//加锁
synchronized (lock){
//获取lock的锁,然后进行通知,通知不会释放锁,直到当前线程释放了lock之后,WaitThread才能从wait方法中返回
System.out.println(Thread.currentThread() + "hold lock. notify @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
//再次加锁
synchronized (lock){
System.out.println(Thread.currentThread() + "hold lock again. sleep @ " +
new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
5.2 join
/**
* 如果一个线程A执行了threadB.join() 语句,其含义是:当前线程A等待threadB 线程终止之后才从
* threadB.join() 返回。线程Thread 从出了提供join 方法之外,还提供join(long millis) 和
* join(long millis,int nanos) 两盒具备超时特性的方法。如果线程threadB 在给定的超时时间里
* 没有终止,那么线程将会从该超时方法中返回。
*/
public class TestJoin {
public static void main(String[] args) throws Exception{
Thread previous = Thread.currentThread();
for(int i=0;i<10;i++){
Thread thread = new Thread(new Domino(previous),String.valueOf(i));
thread.start();
previous = thread;
}
SleepUtils.second(5);
System.out.println(Thread.currentThread().getName() + " terminate . ");
}
static class Domino implements Runnable{
private Thread tHread;
public Domino (Thread thread){
this.tHread = thread;
}
@Override
public void run() {
try{
tHread.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " terminate .");
}
}
}
5.3 ThreadLocal
6.线程池
在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
java中的线程池是运行场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理的使用线程池可以降低资源消耗、提高相应速度、提高线程的可管理性。
6.1 ThreadPoolExecutor的几个重要参数
https://blog.csdn.net/weixin_28760063/article/details/81266152?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
-
corePoolSize(核心线程池的大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行,新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。
-
runnableTaskQueue(任务对列):用于保存等待执行的任务的阻塞对列。可以选择以下几个阻塞队列。
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
PriorityBlockingQueue
-
maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
-
ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
-
RejectedExecutionHandler(饱和策略)。
实际中,如果Executors提供的静态方法能满足要求,就尽量使用它提供的方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。
6.2 Executors提供的几个静态方法
Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口
/**
* 线程池工具类
*/
public class ThreadPoolUtil implements Runnable{
private Integer index;
public ThreadPoolUtil(Integer index) {
this.index = index;
}
@Override
public void run() {
try {
System.out.println(index+"开始处理线程!");
Thread.sleep(50);
System.out.println("线程标识是:"+this.toString());
System.out.println(index+"处理结束!");
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
6.2.1 newFixedThreadPool
定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
eg:
/**
* 是四个线程,但是这时设置了线程固定大小是2,所以两个线程先新建,运行完毕后剩下的再执行。
*/
public class TestFixedThreadPool {
public static void main(String[] args) {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
System.out.println("------------newFixedThreadPool-------------");
for(int i = 0; i < 4; i++) {
int index = i;
newFixedThreadPool.execute(new ThreadPoolUtil(index));
}
newFixedThreadPool.shutdown();
}
}
6.2.2 newSingleThreadExecutor
只有一条线程来执行任务,适用于有顺序的任务的应用场景。
newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
eg:
/**
* 每次只执行一个线程,并且是按顺序执行。
*/
public class TestSingleThreadExecutor {
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
System.out.println("---------------newSingleThreadExecutor--------------");
for(int i = 0; i < 4; i++) {
final int index = i;
newSingleThreadExecutor.execute(new ThreadPoolUtil(index));
}
}
}
6.2.3 newCachedThreadPool
可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
eg:
/**
* 设置新建四个线程,四个线程同时进行
*/
public class TestCachedThreadPool {
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
System.out.println("---------------newCachedThreadPool--------------");
for(int i = 0; i < 4; i++) {
int index = i;
newCachedThreadPool.execute(new ThreadPoolUtil(index));
}
}
}
6.2.4 newScheduledThreadPool
计划线程池,支持定时及周期性任务执行
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
eg:
/**
* 除了延迟3秒执行,别的和newFixedThreadPool相同
*/
public class TestScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);
System.out.println("---------------newScheduledThreadPool--------------");
for(int i = 0; i < 4; i++) {
final int index = i;
//延迟3秒执行
newScheduledThreadPool.schedule(new ThreadPoolUtil(index),3, TimeUnit.SECONDS);
}
}
}