一、介绍
进程和线程
1.1 进程
进程是操作系统中运行的一个任务(一个应用程序运行在一个进程中)。是一块包含了某些资源的内存区域。操作系统利用进程将内存划分为不同的内存块。每个程序(进程)独享自己的内存空间。
1.2 线程
线程:进程中包含的一个或多个"执行单元"称为线程。线程共享所属进程的空间。
1.3 并发
并发原理:(不是同时做,而是走走停停,只是间隔时间很小),线程调度机制将CPU时间划分为很多时间片段(时间片),"尽可能均匀分配"给正在运行的程序,获取CPU时间片的线程或进程得以被执行,其他则等待。而CPU则在这些进程或线程上来回切换运行。微观上所有进程和线程是走走停停的,宏观上都在运行,这种都运行的现象叫并发,但是不是绝对意义上的同时发生。
使用场合:
为了宏观上是同时在干多件事时使用。 例如:多线程下载。
并行和并发的区别:
并发:把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。(单核)
并行:并行是让不同的任务同时在不同的处理器(CPU)上执行,是真的同时执行。例如:多核CPU就是并行执行。
二、创建线程
2.1 继承Thread类
java.lang.Thread
类是线程类,其每一个实例表示一个可以并发运行的线程。
//1.创建线程
public class TestThread extends Thread{ //TestThread:线程名
public void run() { //需重写run方法,目的是定义该线程要执行的逻辑
for(int i=0;i<100;i++){
System.out.println("我是线程");
}
}
}
//2.实例化线程 :
Thread thread = new TestThread();
//3.启动线程:
thread.start();
此方式的不足:
- 由于java是单继承,这就导致,一旦继承了Thread就无法再继承其他父类。
- 由于run方法的方法体被写死了,不利于重用。
注意:
启动线程时调用线程的start()方法而非直接调用run()方法。这样线程才会被纳入线程调度,才会并发运行。
另外,线程启动后,何时运行?运行多久?都是由系统的线程调度来完成,无法人为控制。
2.2 实现Runnable接口
实现java.lang.Runnable
接口并重写run方法。特点是:无返回值,无法抛出异常。
在创建线程的时候将Runnable的实例传入并启动线程。从而达到:线程只负责并发运行,任务分离,给什么任务就运行什么任务。
//1.实现Runnable接口,定义"任务"
public class TestRunnable implements Runnable{
public void run() {
for(int i=0;i<100;i++){
System.out.println("我是线程");
}
}
}
//2.实例化Runnable
Runnable runnable = new TestRunnable();
//3.实例化线程并传入“任务”
Thread thread = new Thread( runnable );
//4.启动线程:
thread.start();
2.3 实现Callable接口
实现java.util.concurrent.Callable
接口,重写call方法,特点是:有返回值(泛型),可抛出异常。
//1.实现Callable接口
public class CallableTest implements Callable<String>{
@Override
public String call() throws Exception {
//测试返回线程自己的名字
String name = Thread.currentThread().getName();
return name;
}
}
//2.实例化Callable
Callable callable = new CallableTest();
//3.创建FutureTask实例,为的是后期接收返回值
FutureTask<String> task = new FutureTask<String>(callable);
//4.启动线程
Thread thread = new Thread(task);
thread.start();
//5.获取返回值
String str = task.get();
System.out.println(str);
注意:
前面的三种方式,都可以使用匿名内部类来简化。
2.4 线程池
线程池是为了解决线程重用和控制线程数量。
线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。以此,来节约创建线程和销毁的资源消耗。
线程池有以下几种实现策略:
- Executors.newFixedThreadPool(int nThreads) 常用
创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。 - Executors.newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。 - Executors.newScheduledThreadPool(int corePoolSize)
创建一个线程池,它可安排在给定延迟后运行或者定期地执行。 - Executors.newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
2.4.1 newFixedThreadPool
//1.创建具有30个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(30);
//2.创建Runable
Runnable r1 = new Runable(){
public void run(){
//线程体
}
};
//3.将任务交给线程池,其会分配空闲线程来运行这个任务。无需我们自己创建线程和启动线程。
threadPool.execute(r1);
//其他方法:
threadPool.submit(r1); //也可以执行
threadPool.shutdown(); //停止线程池(所有活干完,自动停止线程)
threadPool.shutdownNow(); //直接终止线程,不管任务做完没有
线程池关键参数说明
关于几个线程池的几个常见属性:
-
corePoolSize
线程池的核心池的大小,即在没有任务需要执行的时候线程池的大小。[保证最小工人数]
注意:
① 如果池中的实际线程数小于corePoolSize,无论是否其中有空闲的线程,都会给新的任务产生新的线程
② 如果池中的线程数>corePoolSize and <maximumPoolSize,而又有空闲线程,就给新任务使用空闲线程,如没有空闲线程,则产生新线程。
③ 如果池中的线程数=maximumPoolSize,则有空闲线程使用空闲线程,否则新任务放入workQueue。[线程的空闲只有在workQueue中不再有任务时才成立] -
maximumPoolSize
线程池允许的最大线程数。 -
workQueue
工作队列,即:任务队列,添加任务被放进任务队列依次执行。默认大小是:Integer.MAX_VALUE -
RejectedExecutionHandler
当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
① ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
② ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
③ ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
④ ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务