发布于2021-05-29 21:20 阅读(910) 评论(0) 点赞(10) 收藏(1)
进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。
线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IxWRaiON-1621858419431)(C:\Users\kaico\Documents\WXWork\1688851869174369\WeDrive\room19\我的文件\学习文件\md学习笔记\Java基础\images\进程与线程的关系图.png)]
被存储在磁盘或其他的数据存储设备中的可执行文件,也就是一堆静态的代码。
是程序的一次动态的执行过程,操作系统同时管理一个计算机中的多个进程。(有独立的内存空间和系统资源),每一个进程的内部数据和状态都是完全独立的。
是进程中执行运算的最小单位,客完成一个独立的顺序控制流程
指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行,一个进程可能包含了多个同时执行的线程。多个线程交替占用CPU资源,而非真正的并行执行。
并发:两个队列交替使用一台咖啡机(同一时刻只有一台在使用)
并行:两个队列分别使用(同时)两个咖啡机
同步:就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由调用者主动等待这个调用的结果。
异步:调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用*发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
为什么要用同步?
Java中当多个线程同时操作一个可共享的资源变量时,可能会因为多个线程操作资源变量不同步而导致数据冲突,因此加入同步锁以避免在该线程没有完成操作之前被其他线程调用, 从而保证了该变量的唯一性。
锁在代码中指的是在同一时刻、同一JVM中同一段代码(或者同一个数据)只能被一个线程执行(操作)。
作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等 ) 。
指两个或以上个线程都在等待其他线程先完成,造成了程序的停滞。
java.lang.Thread类代表线程,任何线程都是Thread类(子类)的实例。查看JDK文档
自定义类继承Thread类并根据自己的需求重写run方法,然后在主类中创建该类的对象调用start方法,这样就启动了一个线程。使用方式就是自定义一个类继承Thread类,作为一个可以备用的线程类。
public class SubThreadRun extends Thread {
@Override
public void run() {
//打印1~20的整数值
for (int i = 0; i < 20 ; i ++) {
System.out.println("SubThreadRun线程中:" + i);
}
}
}
这里启动线程有两个方法:run()方法和start()方法
区别如下:
假如SubThreadRun类已经继承了一个父类,这个时候我们又要把该类作为自定义线程类,如果还是用继承Thread类的方法来实现的话就违背了Java不可多继承的概念。所以使用接口的方式就可以避免这种问题。
自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法。查看JDK文档
//创建一个自定义类SubRunnableRun实现Runnable接口
public class SubRunnableRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20 ; i ++) {
System.out.println("SubRunnableRun线程中:" + i);
}
}
}
创建线程调用的是Thread的有参构造方法,参数是Runnable类型的。
//1.创建自定义类型的对象
SubRunnableRun srr = new SubRunnableRun();
//2.使用该对象作为实参构造Thread类型的对象
Thread t1 = new Thread(srr);
//3.使用Thread类型的对象调用start方法
t1.start();
上面两种创建线程的方式都需要单独创建一个类来继承Thread类或者实现Runnable接口,并重写run方法。而匿名内部类可以不创建单独的类而实现自定义线程的创建。这种方式只是前两种创建线程方式的简写。
public static void main(String[] args) {
//匿名内部类的语法格式:父类/接口类型 引用变量 = new 父类/接口类型 {方法的重写};
//1.使用继承加匿名内部类的方式创建并启动线程
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("继承Thread类方式创建线程...");
}
};
t1.start();
//2.使用接口加匿名内部类的方式创建并启动线程
Runnable ra = new Runnable() {
@Override
public void run() {
System.out.println("实现Runnable接口方式实现线程...");
}
};
Thread t2 = new Thread(ra);
t2.start();
}
这两个利用匿名内部类创建线程的方式还能继续简化代码,尤其是使用Runnable接口创建线程的方式,可以使用Lambda表达式进行简化。
前两种方式创建线程存在一个问题,就是run方法是没有返回值的,所以如果我们希望在线程结束之后给出一个结果,那就需要用到实现Callable接口创建线程。
从Java5开始新增创建线程的第三中方式为实现java.util.concurrent.Callable接口。查看JDK文档
常用方法:
V call() :计算结果,如果无法计算结果,则抛出一个异常;
启动线程只有创建一个Thread类并调用start方法,如果想让线程启动时调用到Callable接口中的call方法,就得用到FutureTask类。
java.util.concurrent.FutureTask类实现了RunnableFuture接口,RunnableFuture接口是Runnable和Future的综合体,作为一个Future,FutureTask可以执行异步计算,可以查看异步程序是否执行完毕,并且可以开始和取消程序,并取得程序最终的执行结果,也可以用于获取调用方法后的返回结果。查看JDK文档
常用方法:
从上面的概念可以了解到FutureTask类的一个构造方法是以Callable为参数的,然后FutureTask类是Runnable的子类,所以FutureTask类可以作为Thread类的参数。这样的话我们就可以创建一个Thread线程并调用Callable接口中的call方法。
public class ThreadCallableTest implements Callable {
@Override
public Object call() throws Exception {
//计算1 ~ 10000之间的累加和并打印返回
int sum = 0;
for (int i = 0; i <= 10000; i ++) {
sum += i;
}
System.out.println("call方法中的结果:" + sum);
return sum;
}
public static void main(String[] args) {
ThreadCallableTest tct = new ThreadCallableTest();
FutureTask ft = new FutureTask(tct);
Thread t1 = new Thread(ft);
t1.start();
Object ob = null;
try {
ob = ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("main方法中的结果:" + ob);
}
}
比如说服务器编程中,如果为每一个客户都分配一个新的工作线程,并且当工作线程与客户通信结束时,这个线程被销毁,这就需要频繁的创建和销毁工作线程。如果访问服务器的客户端过多,那么会严重影响服务器的性能。
首先创建一些线程,他们的集合称为线程池,当服务器接收到一个客户请求后,就从线程池中取出一个空余的线程为之服务,服务完后不关闭线程,而是将线程放回到线程池中。
线程池的创建方法总共有 7 种,但总体来说可分为 2 类:
第一类:Executors是一个工具类和线程池的工厂类,用于创建并返回不同类型的线程池,常用的方法如下:查看JDK文档
第二类:HreadPoolExecutor通过构造方法创建线程池,最多可以设置7个参数,创建线程池的构造方法如下:查看JDK文档
通过上面两类方法创建完线程池后都可以用ExecutorService接口进行接收,它是真正的线程池接口,主要实现类是ThreadPoolExecutor,常用方法如下:
1、使用newFixedThreadPool方法创建线程池:
public class FixedThreadPool {
public static void main(String[] args) {
// 创建含有两个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// 创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务!");
}
};
// 线程池执行任务
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
threadPool.execute(runnable);
}
}
输出结果:
线程:pool-1-thread-2执行了任务!
线程:pool-1-thread-1执行了任务!
线程:pool-1-thread-2执行了任务!
线程:pool-1-thread-1执行了任务!
从结果上可以看出,这四个任务分别被线程池中的固定的两个线程所执行,线程池也不会创建新的线程来执行任务。
2、使用newCachedThreadPool方法创建线程池
public class cachedThreadPool {
public static void main(String[] args) {
//1.创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//2.设置任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程:" + Thread.currentThread().getName() + "执行了任务!");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
};
//3.执行任务
for (int i = 0; i < 100; i ++) {
executorService.execute(runnable);
}
}
}
输出结果:
线程:pool-1-thread-1执行了任务!
线程:pool-1-thread-4执行了任务!
线程:pool-1-thread-3执行了任务!
线程:pool-1-thread-2执行了任务!
线程:pool-1-thread-5执行了任务!
线程:pool-1-thread-7执行了任务!
线程:pool-1-thread-6执行了任务!
线程:pool-1-thread-8执行了任务!
线程:pool-1-thread-9执行了任务!
线程:pool-1-thread-10执行了任务!
从结果上可以看出,线程池根据任务的数量来创建对应的线程数量。
3、使用newSingleThreadExecutor的方法创建线程池
public class singleThreadExecutor {
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//执行任务
for (int i = 0; i < 10; i ++) {
final int task = i + 1;
executorService.execute(()->{
System.out.println("线程:" + Thread.currentThread().getName() + "执行了第" + task +"任务!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
输出结果:
线程:pool-1-thread-1执行了第1任务!
线程:pool-1-thread-1执行了第2任务!
线程:pool-1-thread-1执行了第3任务!
线程:pool-1-thread-1执行了第4任务!
线程:pool-1-thread-1执行了第5任务!
线程:pool-1-thread-1执行了第6任务!
线程:pool-1-thread-1执行了第7任务!
线程:pool-1-thread-1执行了第8任务!
线程:pool-1-thread-1执行了第9任务!
线程:pool-1-thread-1执行了第10任务!
从结果可以看出,该方法创建的线程可以保证任务执行的顺序。一般保证任务能够顺序执行的方法为同一时刻只有一个线程来执行任务,和消息队列的顺序消费原理一致。
4、使用newScheduledThreadPool的方法创建线程池
public class ScheduledThreadPool {
public static void main(String[] args) {
//创建包含2个线程的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//记录创建任务时的当前时间
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date startTime = new Date();
String start = formatter.format(startTime);
System.out.println("创建任务时的时间:" + start);
//创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
Date endTime = new Date();
String end = formatter.format(endTime);
System.out.println("线程:" + Thread.currentThread().getName() + "任务执行的时间为:" + end);
}
};
//执行任务(参数:runnable-要执行的任务,2-从现在开始延迟执行的时间,TimeUnit.SECONDS-延迟参数的时间单位)
for(int i = 0; i < 2; i ++) {
scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS);
}
}
}
输出结果:
创建任务的时间:2021-04-19 19:26:18
线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:26:20
线程:pool-1-thread-2任务执行的时间为:2021-04-19 19:26:20
从结果可以看出,该方法创建的线程池可以分配已有的线程执行一些需要延迟的任务。
5、使用newSingleThreadScheduledExecutor方法创建线程池
public class SingleThreadScheduledExecutor {
public static void main(String[] args) {
//创建线程池
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
//创建任务
Date startTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String start = formatter.format(startTime);
System.out.println("创建任务的时间:" + start);
Runnable runnable = new Runnable() {
@Override
public void run() {
Date endTime = new Date();
String end = formatter.format(endTime);
System.out.println("线程:" + Thread.currentThread().getName() + "任务执行的时间为:" + end);
}
};
//执行任务
for(int i = 0; i < 2; i ++) {
scheduledExecutorService.schedule(runnable,2, TimeUnit.SECONDS);
}
}
}
输出结果:
创建任务的时间:2021-04-19 19:51:58
线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:52:00
线程:pool-1-thread-1任务执行的时间为:2021-04-19 19:52:00
从结果可以看出,该方法创建的线程池只有一个线程,该线程去执行一些需要延迟的任务。
6、使用newWorkStealingPool方法创建线程池
public class newWorkStealingPool {
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newWorkStealingPool();
//执行任务
for (int i = 0; i < 4; i ++) {
final int task = i + 1;
executorService.execute(()->{
System.out.println("线程:" + Thread.currentThread().getName() + "执行了第" + task +"任务!");
});
}
//确保任务被执行
while (!executorService.isTerminated()) {
}
}
}
输出结果:
线程:ForkJoinPool-1-worker-9执行了第1任务!
线程:ForkJoinPool-1-worker-4执行了第4任务!
线程:ForkJoinPool-1-worker-11执行了第3任务!
线程:ForkJoinPool-1-worker-2执行了第2任务!
从结果可以看出,该方法会创建一个含有足够多线程的线程池,来维持相应的并行级别,任务会被抢占式执行。(任务执行顺序不确定)
7、使用ThreadPoolExecutor创建线程池
ThreadPoolExecutor构造方法,该构造方法最多可以设置7个参数:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心线程数,在线程池中一直存在的线程(对应银行办理业务模型:一开始就开放的窗口)
maximumPoolSize:最大线程数,线程池中能创建最多的线程数,除了核心线程数以外的几个线程会在线程池的任务队列满了之后创建(对应银行办理业务模型:所有窗口)
keepAliveTime:最大线程数的存活时间,当长时间没有任务时,线程池会销毁一部分线程,保留核心线程
unit:时间单位,是第三个参数的单位,这两个参数组合成最大线程数的存活时间
TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微秒
TimeUnit.NANOSECONDS:纳秒
workQueue:等待队列,用于保存在线程池等待的任务(对应银行办理业务模型:等待区)
ArrayBlockingQueue:一个由数组支持的有界阻塞队列。
LinkedBlockingQueue:一个由链表组成的有界阻塞队列。
SynchronousQueue:该阻塞队列不储存任务,直接提交给线程,这样就会形成对于提交的任务,如果有空闲线程,则使用空闲线程来处理,否则新建一个线程来处理任务。
PriorityBlockingQueue:一个带优先级的无界阻塞队列,每次出队都返回优先级最高或者最低的元素
DelayQueue:一个使用优先级队列实现支持延时获取元素的无界阻塞队列,只有在延迟期满时才能从中提取元素,现实中的使用: 淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
threadFactory:线程工厂,用于创建线程。
handler:拒绝策略,任务超出线程池可接受范围时,拒绝处理任务时的策略。
ThreadPoolExecutor.AbortPolicy:当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常(默认使用该策略)
ThreadPoolExecutor.CallerRunsPolicy:当任务添加到线程池中被拒绝时,会调用当前线程池的所在的线程去执行被拒绝的任务
ThreadPoolExecutor.DiscardOldestPolicy:当任务添加到线程池中被拒绝时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去
ThreadPoolExecutor.DiscardPolicy:如果该任务被拒绝,这直接忽略或者抛弃该任务
当任务数小于等于核心线程数+等待队列数量的总和时:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "==>执行任务");
}
};
// 执行任务
for (int i = 0; i < 5; i++) {
threadPool.execute(runnable);
}
//关闭线程池
threadPool.shutdown();
}
}
输出结果:
pool-1-thread-2==>执行任务
pool-1-thread-1==>执行任务
pool-1-thread-2==>执行任务
pool-1-thread-1==>执行任务
pool-1-thread-2==>执行任务
从结果中可以看出,只有两个核心线程在执行任务。
当任务数大于核心线程数+等待队列数量的总和,但是小于等于最大线程数时:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "==>执行任务");
}
};
// 执行任务
for (int i = 0; i < 7; i++) {
threadPool.execute(runnable);
}
//关闭线程池
threadPool.shutdown();
}
}
输出结果:
pool-1-thread-1==>执行任务
pool-1-thread-4==>执行任务
pool-1-thread-2==>执行任务
pool-1-thread-2==>执行任务
pool-1-thread-3==>执行任务
pool-1-thread-4==>执行任务
pool-1-thread-1==>执行任务
从结果中可以看出,启动了最大线程来执行任务。
当任务数大于最大线程数时:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "==>执行任务");
}
};
// 执行任务
for (int i = 0; i < 8; i++) {
threadPool.execute(runnable);
}
//关闭线程池
threadPool.shutdown();
}
}
输出结果:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.zck.task18.ThreadPool.ThreadPoolExecutorTest$1@7f31245a rejected from java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.zck.task18.ThreadPool.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:25)
pool-1-thread-1==>执行任务
pool-1-thread-4==>执行任务
pool-1-thread-4==>执行任务
pool-1-thread-4==>执行任务
pool-1-thread-2==>执行任务
pool-1-thread-3==>执行任务
pool-1-thread-1==>执行任务
从结果中可以看出,任务大于最大线程数,使用拒绝策略直接抛出异常。
原文链接:https://blog.csdn.net/weixin_44044929/article/details/117231319
作者:天花灯
链接:http://www.javaheidong.com/blog/article/207569/4612b17591a9721acb58/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!