赞
踩
/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home
@RequestParam Long mailConfigId,
@RequestParam(required = false) String pt,
@RequestParam(required = false) Integer limit,
@RequestBody(required = false) Map<String, String> orderMap
同步(Synchronous):特指在同一个线程里面执行的内容之间的关系是有顺序的;线程之间需要同步时,就需要使用一些显式的同步方式,这也是多线程带来的大问题,在多线程编程需要特别关注。
异步(Asynchronous):特指一些执行内容可以不用直接等待运行结果,异步执行的内容一般是在另一个线程运行,只需要运行完结果反馈回来就行。
这个注解的作用在于可以让被标注的方法异步执行,但是有两个前提条件
配置类上添加 @EnableAsync
注解
需要异步执行的方法的所在类由Spring管理
需要异步执行的方法上添加了 @Async
注解
**进程:**每一个进程都占有 CPU、内存、磁盘、网络等资源。站在操作系统的角度,进程是分配资源的基本单位,也是最小单位。
从 CPU 角度,执行过程是这样子的:CPU 一直在负责执行指令,进程之间互相竞争 CPU 资源,下图有 A 和 B 进程,在一个时间点,CPU 只执行一个进程的指令,因为 CPU 运行很快,所以在咱们看起来,像是多个进程在同时跑。这就是进程带来的好处:提高资源利用率,并发地执行多个程序。
当然引入进程也不是有益无害,它增加了系统的时间和空间开销。空间开销这个好理解,进程有自己的组成部分(下面会讲),这个就占用了空间。时间开销则是进程切换需要时间。
串行(Serial)
现在我们公司附近的快餐,人少的时候,就是排一条队,如下图所示,每个人按顺序排,一直往前走,如果看到想吃的菜,就用手指一指,快餐员工就会给你打菜,这个应该是很多快餐店都采用的方式,容易管理,但是有一点需要强调的就是如果一个同学只想吃米饭和豆芽,他还是需要排一整队到最后的结账台结账。这其实就是咱们计算机世界里面的串行,一条队伍,依次有序的执行着。
并行(Parallel)
不过一到 12 点高峰期上面那种排队方式就撑不住了,分分钟排队排到外面晒太阳,这时为了提高效率,因为快餐店还有一片空地,所以又加了一套打菜装备和员工,这时很好的解决了一条队伍太长的问题,这时就是并行了,2 套打菜装备和员工各自互不干涉,完全独立,每套装备能够解决每个顾客的点菜需求。当然这要求比较高,需要餐厅有足够的空间可以放下两套装备,并且需要雇佣多一倍的员工来为顾客打菜,这里就类似计算机的双核。
并发(Concurrent)
除了上面的两种快餐排队方式,还见过下面这种的,只有一套打菜装备,但是却有很多个队,每个菜排一条队(现实中其实不止一个菜,而是多个菜,这里画图就简化成一个菜),每个人就只需要排自己想吃的菜的队伍,这一道道菜就像计算机的各个资源,比如 CPU、IO 等等,人就像一个一个进程,有些只需要 CPU 计算资源,有些还要 IO 资源,各取所需,这种方式就是并发。这种打菜方式和上面第一种所需要的资源是一样的:一套打菜装备和员工。每个顾客最终都需要走到结账这个步骤,而结账只有一个,也就是类似计算机的单核。
那么回归到计算机世界,这三者是什么东西呢?
早期计算机只有一个 CPU,也就是所谓的单核计算机,只有一个 CPU 去执行任务,所以只能是一个任务一个任务的跑,每个任务跑完才让下一个任务跑,也就是串行的。
后面因为进程和线程概念的提出与实现,它们使得任务以进程的方式运行,拆分成多个时间片运行,而不是一次运行执行完任务,这样子在一个 CPU 运行中可以有多个任务在并发执行,这时还是只有一个 CPU,一个时间点只有一个任务在执行。
而并行是因为 CPU 硬件的发展,出现多核 CPU,所以实现了真正的同一时间点能有多个任务在执行。
如果某一个需求点阻塞了,应该就先做手头上其他工作,如果手头上没其他工作,就跟老板反馈情况后领其他任务做,还要时刻去跟进阻塞的需求点的进度。
阻塞
非阻塞
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YpV3zULU-1595785774605)(http://www.liebrother.com/upload/dd2c2cb241de4733afe6ad1610189194_02.jpg)]
试想一下没有线程的程序是怎么样的?百度网盘在上传文件时就无法下载文件了,得等文件上传完成后才能下载文件。这个我们现在看起来很反人性,因为我们习惯了一个程序同时可以进行运行多个功能,而这些都是线程的功劳。
线程ID:线程标识符。
当前指令指针(PC):指向要执行的指令。
寄存器集合:存储单元寄存器的集合。
堆栈:暂时存放数据和地址,一般用来保护断点和现场。
线程和进程之间的区别,我觉得可以用这个例子来看出两者的不同,进程就是一栋房子,房子住着 3 个人,线程就是住在房子里的人。进程是一个独立的个体,有自己的资源,线程是在进程里的,多个线程共享着进程的资源。
我们看到 Java 源代码里面,线程状态的枚举有如下 6 个。
public enum State { //新建状态 NEW, //运行状态 RUNNABLE, //阻塞状态 BLOCKED, //等待状态 WAITING, //等待状态(区别在于这个有等待的时间) TIMED_WAITING, //终止状态 TERMINATED; }
NEW:新建状态。在创建完 Thread ,还没执行 start() 之前,线程的状态一直是 NEW。可以说这个时候还没有真正的一个线程映射着,只是一个对象。
RUNNABLE:运行状态。线程对象调用 start() 之后,就进入 RUNNABLE 状态,该状态说明在 JVM 中有一个真实的线程存在。
BLOCKED:阻塞状态。线程在等待锁的释放,也就是等待获取 monitor 锁。
WAITING:等待状态。线程在这个状态的时候,不会被分配 CPU,而且需要被显示地唤醒,否则会一直等待下去。
TIMED_WAITING:超时等待状态。这个状态的线程也一样不会被分配 CPU,但是它不会无限等待下去,有时间限制,时间一到就停止等待。
TERMINATED:终止状态。线程执行完成结束,但不代表这个对象已经没有了,对象可能还是存在的,只是线程不存在了。
线程既然有这么多个状态,那肯定就有状态机,也就是在什么情况下 A 状态会变成 B 状态。下面就来简单描述一下。
结合下图,我们 new 出线程类的时候,就是 NEW
状态,
调用 start() 方法,就进入了 RUNNABLE
状态,
这时如果触发等待,则进入了 WAITING
状态,
如果触发超时等待,则进入 TIMED_WAITING
状态,
当访问需要同步的资源时,则只有一个线程能访问,其他线程就进入 BLOCKED
状态,
当线程执行完后,进入 TERMINATED
状态。
其实在 JVM 中,线程是有 9 个状态,如下所示,有兴趣的同学可以深入了解一下。
javaClasses.hpp enum ThreadStatus { NEW = 0, RUNNABLE = JVMTI_THREAD_STATE_ALIVE + // runnable / running JVMTI_THREAD_STATE_RUNNABLE, SLEEPING = JVMTI_THREAD_STATE_ALIVE + // Thread.sleep() JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT + JVMTI_THREAD_STATE_SLEEPING, IN_OBJECT_WAIT = JVMTI_THREAD_STATE_ALIVE + // Object.wait() JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_INDEFINITELY + JVMTI_THREAD_STATE_IN_OBJECT_WAIT, IN_OBJECT_WAIT_TIMED = JVMTI_THREAD_STATE_ALIVE + // Object.wait(long) JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT + JVMTI_THREAD_STATE_IN_OBJECT_WAIT, PARKED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park() JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_INDEFINITELY + JVMTI_THREAD_STATE_PARKED, PARKED_TIMED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park(long) JVMTI_THREAD_STATE_WAITING + JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT + JVMTI_THREAD_STATE_PARKED, BLOCKED_ON_MONITOR_ENTER = JVMTI_THREAD_STATE_ALIVE + // (re-)entering a synchronization block JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER, TERMINATED = JVMTI_THREAD_STATE_TERMINATED };
下面讲一讲在 Java 中如何创建一个线程。众所周知,实现 Java 线程有 2 种方式:继承 Thread 类和实现 Runnable 接口。
继承 Thread 类,重写 run()
方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread");
}
}
实现 Runnable 接口,实现 run()
方法。
class MyRunnable implements Runnable {
public void run() {
System.out.println("MyRunnable");
}
}
这 2 种线程的启动方式也不一样。
MyThread
是一个线程类,所以可以直接 new
出一个对象出来,接着调用 start()
方法来启动线程;
MyRunnable
只是一个普通的类,需要 new
出线程基类 Thread
对象,将 MyRunnable
对象传进去。
下面是启动线程的方式。
public class ThreadImpl {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread myRunnable = new Thread(new MyRunnable());
System.out.println("main Thread begin");
myThread.start();
myRunnable.start();
System.out.println("main Thread end");
}
}
打印结果如下:
main Thread begin
main Thread end
MyThread
MyRunnable
看这结果,不像咱们之前的串行执行依次打印,主线程不会等待子线程执行完。
敲重点:不能直接调用 run()
,直接调用 run()
不会创建线程,而是主线程直接执行 run()
的内容,相当于执行普通函数。这时就是串行执行的。看下面代码。
public class ThreadImpl {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread myRunnable = new Thread(new MyRunnable());
System.out.println("main Thread begin");
myThread.run();
myRunnable.run();
System.out.println("main Thread end");
}
}
打印结果:
main Thread begin
MyThread
MyRunnable
main Thread end
从结果看出只是串行的,但看不出没有线程,我们看下面例子来验证直接调用 run()
方法没有创建新的线程,使用 VisualVM 工具来观察线程情况。
我们对代码做一下修改,加上 Thread.sleep(1000000)
让它睡眠一段时间,这样方便用工具查看线程情况。
调用 run()
的代码:
public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("MyThread"); Thread myRunnable = new Thread(new MyRunnable()); myRunnable.setName("MyRunnable"); System.out.println("main Thread begin"); myThread.run(); myRunnable.run(); System.out.println("main Thread end"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyThread extends Thread { @Override public void run() { System.out.println("MyThread"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyRunnable implements Runnable { public void run() { System.out.println("MyRunnable"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:
main Thread begin
MyThread
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1mmwD1h4-1595785774608)(http://www.liebrother.com/upload/68ae1f24617f4cde8bf1f3382db17e42_dxc_0005_01.jpg)]
只打印出 2 句日志,观察线程时也只看到 main
线程,没有看到 MyThread
和 MyRunnable
线程,印证了上面咱们说的:直接调用 run()
方法,没有创建线程。
下面我们来看看有 调用 start()
的代码:
public class ThreadImpl { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("MyThread"); Thread myRunnable = new Thread(new MyRunnable()); myRunnable.setName("MyRunnable"); System.out.println("main Thread begin"); myThread.start(); myRunnable.start(); System.out.println("main Thread end"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:
main Thread begin
main Thread end
MyThread
MyRunnable
所有日志都打印出来了,并且通过 VisualVM 工具可以看到 MyThread
和 MyRunnable
线程。看到了这个结果,切记创建线程要调用 start()
方法。
1、线程自然终止:自然执行完或抛出未处理异常
2、stop(),resume(),suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()容易导致死锁。
3、使用interrupt()方法
java线程是协作式,而非抢占式
调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
isInterrupted() 判定当前线程是否处于中断状态。
static方法interrupted() 判定当前线程是否处于中断状态,同时中断标志位改为false。
方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。
代码如下
import java.util.concurrent.ExecutionException; /** * @Auther: BlackKingW * @Date: 2019/4/14 12:09 * @Description: */ public class DaemonThread { private static class UseThread extends Thread { @Override public void run() { try { while (!isInterrupted()) { System.out.println(Thread.currentThread().getName() + " I am extends Thread."); } System.out.println(Thread.currentThread().getName() + " interrupt flag is " + isInterrupted()); } finally { System.out.println("...........finally"); } } } public static void main(String[] args) throws InterruptedException, ExecutionException { UseThread useThread = new UseThread(); useThread.start(); Thread.sleep(5); useThread.interrupt(); } }
对象锁,锁的是类的对象实例。
类锁 ,锁的是每个类的的Class对象,每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个。
适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性。
线程变量。可以理解为是个map,类型 Map<Thread,Integer>
难以保证及时性,资源开销很大,
wait() 对象上的方法,将是当前执行线程进行等待。
notify/notifyAll 对象上的方法 发送信号量,唤醒线程。
代码示例:
git@github.com:nignmengcc/spring_boot_demo.git
TestWN
/** * @author didi */ public class Express { public final static String CITY = "ShangHai"; /**快递运输里程数*/ private int km; /**快递到达地点*/ private String site; public Express() { } public Express(int km, String site) { this.km = km; this.site = site; } /** 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理 * */ public synchronized void changeKm(Integer km){ this.km = km; notifyAll(); //其他的业务代码 } /** 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/ public synchronized void changeSite(String site){ this.site = site; notify(); } public synchronized void waitKm(){ while(this.km<=100) { try { wait(); System.out.println("check km thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("the km is"+this.km+",I will change db."); } public synchronized void waitSite(){ while(CITY.equals(this.site)) { try { wait(); System.out.println("check site thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("the site is"+this.site+",I will call user."); } } /** * @author didi */ public class TestWN { /** * 静态类express * */ private static Express express = new Express(0,Express.CITY); /**检查里程数变化的线程,不满足条件,线程一直等待*/ private static class CheckKm extends Thread{ @Override public void run() { // express.waitKm(); synchronized (express) { while(express.getKm()<=100) { try { express.wait(); System.out.println("check km thread["+Thread.currentThread().getId() +"] is waiting be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("the km is"+express.getKm()+",I will change db."); } } } /**检查地点变化的线程,不满足条件,线程一直等待*/ private static class CheckSite extends Thread{ @Override public void run() { express.waitSite(); } } public static void main(String[] args) throws InterruptedException { /**三个检测地点等待线程*/ for(int i=0;i<3;i++){ new CheckSite().start(); } /**三个检测里程数等待线程*/ for(int i=0;i<3;i++){ new CheckKm().start(); } Thread.sleep(1000); /**快递里程变化*/ // express.changeKm(1000); synchronized (express) { express.setKm(1000); express.notifyAll(); } /**快递里程变化*/ express.changeSite("HangZhou"); } }
等待和通知的标准范式
等待方:
通知方来说
notify和notifyAll应该用谁?
应该尽量使用notifyAll,使用notify因为有可能发生信号丢失的的情况
notify()方法只能随机唤醒一个线程。
那么顾名思义,notifyAll()就是用来唤醒正在等待状态中的所有线程的,不过也需要注意以下几点:
(1)notifyAll()只会唤醒那些等待抢占指定object’s monitor的线程,其他线程则不会被唤醒。
(2)notifyAll()只会一个一个的唤醒,而并非统一唤醒。因为在同一时间内,只有一个线程能够持有object’s monitor
(3)notifyAll()只是随机的唤醒线程,并非有序唤醒。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
代码示例:
/** * @author didi */ public class UseJoin { static class JumpQueue implements Runnable { private Thread thread;//用来插队的线程 public JumpQueue(Thread thread) { this.thread = thread; } @Override public void run() { try { System.out.println(thread.getName()+" will be join before " +Thread.currentThread().getName()); thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" terminted."); } } public static void main(String[] args) throws Exception { //现在是主线程 Thread previous = Thread.currentThread(); for (int i = 0; i < 10; i++) { //i=0,previous 是主线程,i=1;previous是i=0这个线程 Thread thread = new Thread(new JumpQueue(previous), String.valueOf(i)); System.out.println(previous.getName()+" jump a queue the thread:" +thread.getName()); thread.start(); previous = thread; } //让主线程休眠2秒 Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + " terminate."); } }
线程在执行yield()以后,持有的锁是不释放的
sleep()方法被调用以后,持有的锁是不释放的
调动方法之前,必须要持有锁。调用了wait()方法以后,锁就会被释放,当wait方法返回的时候,线程会重新持有锁
调动方法之前,必须要持有锁,调用notify()方法本身不会释放锁的
规模为N的问题,N<阈值,直接解决,N>阈值,将N分解为K个小规模子问题,子问题互相对立,与原问题形式相同,将子问题的解合并得到原问题的解
动态规范
就是在任务分割的时候,前面的任务执行可能会比后面的执行速度快,当前面的执行完,后面的还没执行的时候,执行完前面的任务的线程不会停止,而是从后面的任务的尾部取出子任务继续工作。Fork-Join就是实现了这样的机制。
Fork/Join 使用的标准范式
作用:是一组线程等待其他的线程完成工作以后在执行,加强版join
await用来等待,countDown负责计数器的减一
代码示例:
类之间的关系
isDone,结束,正常还是异常结束,或者自己取消,返回true;
isCancelled 任务完成前被取消,返回true;
cancel(boolean):
线程池(thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
优势:
根据线程池的概念,如果要自己创建线程池,应该满足一下条件。
根据以上的条件以及之前我们学的并发编程知识,我们先手动自己尝试写一个线程池
Code:
1、定义WorkThread类,用来表示执行的线程,用于监听阻塞队列任务。
2、创建构建函数,我们将线程池进行初始化,并启动所有的工作线程。workThreads用来保存运行的线程,使用BlockingQueue taskQueue用来保存我们的任务队列
3、创建提交任务方法execute,用于提交我们的任务。
4、创建销毁线程池的方法destroy,用于销毁线程池。
之后我们编写测试类
我们大致了解了线程池的一个机制,那我们看下JDK中,是如何实现线程池的吧。
JAVA中,ThreadPoolExecutor,是所有线程池实现的父类,类结构图如下。
它的构造函数含有以下参数:
参数 | 含义 |
---|---|
int corePoolSize | 线程池中核心线程数,< corePoolSize ,就会创建新线程,= corePoolSize ,这个任务就会保存到BlockingQueue,如果调用prestartAllCoreThreads()方法就会一次性的启动corePoolSize 个数的线程。 |
int maximumPoolSize | 允许的最大线程数,如果BlockingQueue也满了,并且线程数< maximumPoolSize时候就会再次创建新的线程 |
long keepAliveTime | 线程空闲下来后,存活的时间,这个参数只在线程数> corePoolSize才有用 |
TimeUnit unit | 存活时间的单位值 |
BlockingQueue workQueue | 保存任务的阻塞队列 |
ThreadFactory threadFactory | 创建线程的工厂,给新建的线程赋予名字 |
RejectedExecutionHandler handler | 饱和策略AbortPolicy :直接抛出异常,默认;CallerRunsPolicy:用调用者所在的线程来执行任务DiscardOldestPolicy:丢弃阻塞队列里最老的任务,队列里最靠前的任务DiscardPolicy :当前任务直接丢弃实现自己的饱和策略只要实现RejectedExecutionHandler接口即可 |
提交任务
execute(Runnable command) 不需要返回
Future submit(Callable task) 需要返回值
关闭线程池
shutdown(),shutdownNow();
shutdownNow():设置线程池的状态,还会尝试停止正在运行或者暂停任务的线程
shutdown()设置线程池的状态,只会中断所有没有执行任务的线程
1、如果工作线程数小于核心线程数,则创建工作线程
2、如果工作线程数等于或者大于核心线程数,则将任务提交到阻塞队列中
3、如果阻塞队列也满了,但线程数小于最大线程数,则创建新的线程
4、如果创建新的线程也满了,则执行任务饱和策略。
源码如下
根据任务的性质来:计算密集型(CPU),IO密集型,混合型
计算密集型:例如加密,大数分解,正则……等
推荐:机器的Cpu核心数+1,为什么+1,防止页缺失,(机器的Cpu核心=Runtime.getRuntime().availableProcessors()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。