1. 进程、线程概念
- 首先要理解进程(Processor)和线程(Thread)的区别:
-
进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
-
线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
-
一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。
-
线程没有地址空间,线程包含在进程的地址空间中。线程上下文只包含一个堆栈、一个寄存器、一个优先权,线程文本包含在他的进程 的文本片段中,进程拥有的所有资源都属于线程。所有的线程共享进程的内存和资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段, 寄存器的内容,栈段又叫运行时段,用来存放所有局部变量和临时变量。
2. 创建任务和线程
可以在程序中 创建附加的线程
以执行并发任务。在Java 中,每个任务都是 Runnable
接口的一个实例,也称为可运行对象(runnable object) 。线程本质上讲就是便于任务执行的对象。
-
定义一个实现Runnable 接口的类
任务就是对象。为了创建任务,必须首先为任务定义一个实现Runnable 接口的类。Runnable 接口非常简单,它只包含一个run 方法。需要实现这个方法来告诉系统线程将如何运行。 -
创建一个任务
一旦定义了一个TaskClass ,就可以用它的构造方法创建一个任务。 -
创建任务的线程:
任务必须在线程中执行。Thread 类包括创建线程的构造方法以及控制线程的很多有用的方法。使用下面的语句创建任务的线程:
Thread thread = new Thread(task); -
调用start()方法
然后调用start()方法告诉Java 虚拟机该线程准备运行。 thread.start(); -
JVM调用run()方法执行任务
package Thread;
public class test {
public static void main(String[] args) {
Runnable printA = new PrintChar('a',100); // 第 2 步
Runnable printB = new PrintChar('b',100);
Runnable print100 = new PrintNum(100);
Thread thread1 = new Thread(printA); // 第 3 步
Thread thread2 = new Thread(printB);
Thread thread3 = new Thread(print100);
thread1.start(); // 第 4 步
thread2.start();
thread3.start();
}
}
class PrintChar implements Runnable{ // 第 1 步
private char charToPrint;
private int times;
public PrintChar(char c,int t) {
charToPrint = c;
times = t;
}
@Override
public void run() {
for(int i = 0; i < times; i++)
System.out.print(charToPrint);
}
}
class PrintNum implements Runnable{
private int lastNum;
public PrintNum(int n) {
lastNum = n;
}
@Override
public void run() {
for(int i = 1; i <= lastNum; i++) {
System.out.print(" " + i);
}
}
}
3. Thread类
Thread.sleep(1000);
表示当前线程暂停1000毫秒 ,其他线程不受影响
Thread.sleep(1000); 会抛出InterruptedException 中断异常,因为当前线程sleep的时候,有可能被停止,这时就会抛出 InterruptedException
Thread.yield();
临时暂停,使得其他线程可以占用CPU资源
t1.setDaemon(true);
守护线程就相当于那些支持部门,如果一个进程只剩下守护线程,那么进程就会自动结束。守护线程通常会被用来做日志,性能统计等工作。
4. 优先级
Java 给每个线程指定一个优先级。默认情况下,线程继承生成它的线程的优先级。可以用 setPriority
方法提高、降低线程的优先级,还能用 getPriority
方法获取线程的优先级。
优先级是从1 到10 的数字。
Thread 类有int 型常量 MIN_PRIORITY
、NORM_PRIORITY
和 MAX_PRIORITY
,分别代表1 、5 和10 。
主线程的优先级是 Thread.NORM_PRIORITY。
例如在上节的历程里修改一下程序:
thread1.start();
thread2.start();
thread3.start();
thread3.setPriority(Thread.MAX_PRIORITY);
}
则任务print100 的线程首先结束。
5. 线程池
顾名思义线程池就是线程的容器。在没有接触线程池之前,我们使用线程的时候就去创建一个线程,然后startup()就可以(#1里提到的)
但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
假设,你现在有一个while n(n=10000)的循环,每一次循环都要启动一个线程去计算n的因数,这样频繁而且大量的创建线程,系统的效率会大幅下降,系统会很快奔溃。
线程池就可以帮助我们解决这个问题,他使线程可以重复使用,就是执行完一个任务线程不会被销毁,而是可以继续执行其他任务
线程池类ThreadPoolExecutor 在包 java.util.concurrent 下
线程池是管理并发执行任务个数的理想方法。 Java 提供 Executor ( n. 执行者 ) 接口来执行线程池中的任务,提供 ExecutorService 接口来管理和控制任务。ExecutorService 是Executor 的子接口
为了创建一个Executor 对象,可以使用Executors 类中的静态方法,newFixedThreadPool(int)
方法在池中创建固定数目的线程。
ExecutorService executorService= Executors.newFixedThreadPool(5);
接下来向线程池提交一个任务。
executorService.execute(new TestRunnable());
如果线程完成了任务的执行,它可以被重新使用以执行另外一个任务。
如果线程池中所有的线程都不是处于空闲状态,而且有任务在等待执行,那么在关闭之前,如果由于一个错误终止了一个线程,就会创建一个新线程来替代它。如果线程池中所有的线程都不是处于空闲状态,而且有任务在等待执行,那么newCachedThreadPool() 方法就会创建一个新线程。如果缓冲池中的线程在60 秒内都没有被使用就该终止它。对许多小任务而言,一个缓冲池已经足够。
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool——创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool——创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool——创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor——创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(new PrintChar('a',100));
executor.execute(new PrintChar('b',100));
executor.execute(new PrintNum(100));
executor.shutdown();
}
把第2行换成:ExecutorService executor = Executors.newCachedThreadPool();
又会发生什么呢?
将为每个等待的任务创建一个新线程,所以,所有的任务都并发地执行。
最后一行的方法shutdown() 通知执行器关闭。不能接受新的任务,但是现有的任务将继续执行直至完成。
如果仅需要为一个任务创建一个线程,就使用Thread 类。如果需要为多个任务创建线程,最好使用线程池。