线程的基本介绍
1.什么是进程
进程是指在系统中正在运行的一个应用程序
每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
![](https://img.haomeiwen.com/i12058546/7d415f73eeceacc5.png)
2.什么是线程
1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)
线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行
比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行
![](https://img.haomeiwen.com/i12058546/0593f283cab0f859.png)
3.线程的串行
1个线程中任务的执行是串行的
如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
也就是说,在同一时间内,1个线程只能执行1个任务
比如在1个线程中下载3个文件(分别是文件A、文件B、文件C)
![](https://img.haomeiwen.com/i12058546/fc8b4556add3b0ee.png)
4.线程的生命周期
![](https://img.haomeiwen.com/i12058546/0e93e87518d9a214.png)
1.新建
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,
此时仅由 JVM 为其分配 内存,并初始化其成员变量的值.
2.就绪
当线程对象调用了 start()方法之后,该线程处于就绪状态。
Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
3.运行
如果处于就绪状态的线程被调度获得CPU执行权,就会执行run()方法的逻辑,此时处于运行状态。
4.阻塞
阻塞状态是指线程因为某种原因放弃CPU使用权,暂时停止运行.
需要等到线程进入就绪状态才有机会获得cpu时间片从而执行.
这个状态分下面三种情况
1.等待阻塞(obj.wait()->进入wait):即运行中的线程执行wait方法,JVM会将该线程放入等待队列中
2.同步阻塞(lock.lock()/synchronized->锁池):即运行中的线程获取对象的
同步锁(指jvm提供的内置锁synchronized)或者显示锁lock失败,会将线程阻塞挂起
3.其他方式阻塞(sleep/join):运行中的线程执行Thread.sleep()或者thread.join()方法,
或者发出I/O请求待处理的时候,jvm会将线程置为阻塞状态。
当sleep()状态超时、join()等待线程运行结束或者超时、或者处理I/O完毕,会重新进入就绪状态
5.死亡
线程结束任务之后自己结束,或者产生了异常而结束。
5.创建线程
线程的创建一共有四种方式:
1.继承于Thread类,重写run()方法。
2.实现Runable接口,实现里面的run()方法。
3.使用 Future Task 实现有返回结果的线程。
4.使用线程池。
//继承于Thread类,重写run()方法
class MyThread extends Thread{
//重写run方法
@Override
public void run() {
//任务内容....
System.out.println("当前线程是:"+Thread.currentThread().getName());
}
}
Thread thread = new MyThread();
//线程启动
thread.start();
//当前线程是:Thread-0
使用匿名内部类
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("当前线程是:"+Thread.currentThread().getName());
}
};
因为java是单继承结构,一旦继承了Thread类,就无法继承其他类了。所以建议使用 实现Runable接口 的方法
//实现Runable接口,实现里面的run()方法:
class MyTask implements Runnable{
//重写run方法
public void run() {
//任务内容....
System.out.println("当前线程是:"+Thread.currentThread().getName());
}
}
Thread thread = new Thread(new MyTask());
//线程启动
thread.start();
使用匿名内部类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("当前线程是:"+Thread.currentThread().getName());
}
});
FutureTask
是一个可取消的异步计算任务,是一个独立的类,实现了 Future
、Runnable
接口。FutureTask
的出现是为了弥补 Thread
的不足而设计的,可以让程序员跟踪、获取任务的执行情况、计算结果 。
因为FutureTask
实现了 Runnable
,所以FutureTask
可以作为参数来创建一个新的线程来执行,也可以提交给 Executor
执行。FutureTask
一旦计算完成,就不能再重新开始或取消计算。
FutureTask的构造方法
可以接受 Runnable,Callable 的子类实例。
//创建一个 FutureTask,一旦运行就执行给定的 Callable。
public FutureTask(Callable<V> callable);
//创建一个 FutureTask,一旦运行就执行给定的 Runnable,并安排成功完成时 get 返回给定的结果 。
public FutureTask(Runnable runnable, V result)
//FutureTask 的简单例子
MyCallable.java
public class MyCallable implements Callable<Double>{
@Override
public Double call() throws Exception {
double d = 0;
try {
System.out.println("异步计算开始.......");
d = Math.random()*10;
d += 1000;
Thread.sleep(2000);
System.out.println("异步计算结束.......");
} catch (InterruptedException e) {
e.printStackTrace();
}
return d;
}
}
Test.java
public class Test {
public static void main(String[] args) {
FutureTask<Double> task = new FutureTask<>(new MyCallable());
//创建一个线程,异步计算结果
Thread thread = new Thread(task);
thread.start();
//主线程继续工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程等待计算结果...");
//当需要用到异步计算的结果时,阻塞获取这个结果
Double d;
try {
d = task.get();
System.out.println("计算结果是:"+d);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//用同一个 FutureTask 再起一个线程
Thread thread2 = new Thread(task);
thread2.start();
}
}
![](https://img.haomeiwen.com/i12058546/27ba1c955dc0adf9.png)
第四种先简单给个例子,具体看下面
JDK中提供了工具类
Executors
,提供了几个创建常用的线程池的工厂方法
package wgzyx;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyRunable implements Runnable{
private String taskName;
public MyRunable(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("线程池完成任务:"+taskName);
}
public static void main(String[] args) {
//创建一个只有一个线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建任务,并提交任务到线程池中
executorService.execute(new MyRunable("任务1"));
executorService.execute(new MyRunable("任务2"));
executorService.execute(new MyRunable("任务3"));
}
}
/*
线程池完成任务:任务1
线程池完成任务:任务2
线程池完成任务:任务3
*/
多线程
1.什么是多线程
1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
多线程技术可以提高程序的执行效率
比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C)
![](https://img.haomeiwen.com/i12058546/49359d2751223ffe.png)
2.多线程的原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
如果线程非常非常多
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低线程的执行效率降低
3.多线程的优缺点
- 线程的优点
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率) - 多线程的缺点
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享
4. 何时建议使用多线程
①. 当主线程试图执行冗长的操作,但系统会卡界面,体验非常不好,这时候可以开辟一个新线程,来处理这项冗长的工作。
②. 当请求别的数据库服务器、业务服务器等,可以开辟一个新线程,让主线程继续干别的事。
③. 利用多线程拆分复杂运算,提高计算速度
线程池
1.为什么使用线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
2.线程池的作用
使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。
3.线程池的好处
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
创建线程池
阿里的 Java开发手册,上面有线程池的一个建议:
![](https://img.haomeiwen.com/i12058546/cf81bf12c93c4792.png)
主要是底层的阻塞队列
LinkedBlockingQueue
是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
通过创建
ThreadPoolExecutor
对象来创建1.ThreadPoolExecutor参数介绍
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1.corePoolSize
:表示核心线程池的大小。当提交一个任务时,如果当前核心线程池的线程个数没有达到corePoolSize,则会创建新的线程来执行所提交的任务,即使当前核心线程池有空闲的线程。如果当前核心线程池的线程个数已经达到了corePoolSize
,则不再重新创建线程。如果调用了prestartCoreThread()
或者 prestartAllCoreThreads()
,线程池创建的时候所有的核心线程都会被创建并且启动。*
2.maximumPoolSize
:表示线程池能创建线程的最大个数。如果当阻塞队列已满时,并且当前线程池线程个数没有超过maximumPoolSize
的话,就会创建新的线程来执行任务。
3.keepAliveTime
:空闲线程存活时间。如果当前线程池的线程个数已经超过了corePoolSize
,并且线程空闲时间超过了keepAliveTime
的话,就会将这些空闲线程销毁,这样可以尽可能降低系统资源消耗。
4.unit
:时间单位。为keepAliveTime
指定时间单位。
-
workQueue
:阻塞队列。用于保存任务的阻塞队列,可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。 -
threadFactory
:创建线程的工程类。可以通过指定线程工厂为每个创建出来的线程设置更有意义的名字,如果出现并发问题,也方便查找问题原因。
7.handler
:饱和策略。当线程池的阻塞队列已满和指定的线程都已经开启,说明当前线程池已经处于饱和状态了,那么就需要采用一种策略来处理这种情况。采用的策略有这几种:
1. AbortPolicy: 直接拒绝所提交的任务,并抛出RejectedExecutionException异常;
2. CallerRunsPolicy:只用调用者所在的线程来执行任务;
3. DiscardPolicy:不处理直接丢弃掉任务;
4. DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务.
适当的阻塞队列
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek()
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
![](https://img.haomeiwen.com/i12058546/70882cd4737eef1c.png)
2.ThreadPoolExecutor例子
package wgzyx;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool {
//newFixedThreadPool创建固定大小的线程池。
//每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
// 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
//线程池接口是ExecutorService
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(
nThreads,// corePoolSize
nThreads,// maximumPoolSize == corePoolSize
0L,// 空闲时间限制是 0
TimeUnit.MILLISECONDS,
//TimeUnit.DAYS天 TimeUnit.HOURS小时 TimeUnit.MINUTES分钟 TimeUnit.SECONDS秒 TimeUnit.MILLISECONDS毫秒
new LinkedBlockingQueue<Runnable>(nThreads)//一个由链表结构组成的有界阻塞队列
);
}
// newCachedThreadPool创建一个可缓存的线程池。
//如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,
//当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
//此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(
0, // corePoolSoze == 0
Integer.MAX_VALUE, // maximumPoolSize 非常大
60L, // 空闲判定是60 秒
TimeUnit.SECONDS,//分钟
// 神奇的无存储空间阻塞队列,每个 put 必须要等待一个 take
new SynchronousQueue<Runnable>()
);
}
//newSingleThreadExecutor创建一个单线程的线程池。
//这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
//如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
//实际上FinalizableDelegatedExecutorService这个类就是对ExecutorService进行了一个包装,防止暴露出不该被暴露的方法
//,然后加上了finalize方法保证线程池的关闭
public static ExecutorService newSingleThreadExecutor() {
return
new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1));
}
public static void main(String[] args) {
System.out.println("使用固定大小的线程池");
ExecutorService newCachedThreadPool = newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
newCachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "----" + index);
}
});
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("使用可缓存的线程池");
newCachedThreadPool = newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
newCachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "----" + index);
}
});
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("使用单线程的线程池");
newCachedThreadPool = newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
newCachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "----" + index);
}
});
}
}
}
![](https://img.haomeiwen.com/i12058546/ff7227403a43d14b.png)
文章参考
http://www.cnblogs.com/yxt9322yxt/p/4804026.html
https://www.cnblogs.com/jinggod/p/8485106.html
网友评论