1. 程序、进程、线程基本概念
1.1 程序与进程
(1)程序 (Program) :某种语言编写完成特定任务的一组指令的集合。即指一段静态的代码,静态对象。
(2)进程 (Process):程序的一次执行过程,或是正在运行的一个程序。进程是一个动态的过程:有产生、存在和消亡的过程(生命周期)
(3)进程作为资源分配的基本单位,系统在运行时会为每个进程分配执行资源(CPU,内存,硬盘,网络,显卡,电源)。
(4)程序是静态的代码,进程是动态的执行程序。
────────────────────────────────────────────────────────────
1.2 线程 ( Thread )
(1)进程可进一步细化为线程,指一个程序内部的一条执行路径。
(2)若一个进程在同一时间并行执行多个线程,就是支持多线程的。
(3)线程作为调度和执行的单位,进程在运行时会为每个线程分配执行资源。每个线程拥有独立的运行栈和程序计数器( pc ),线程切换的开销更小
(4)一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患(多线程安全问题)
(5)例如:一个 Java 应用程序 java.exe,至少有三个线程:main( ) 主线程,gc( ) 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
────────────────────────────────────────────────────────────
1.3 单核CPU和多核CPU的理解
(1)单核CPU,一种假的多线程,因为在一个时间单元内,只能执行一个线程的任务。然而操作系统在很短的时间内在各个程序 ( 进程 ) 之间进行切换,看起来像同时执行多个任务。
(2)多核 CPU,真的多线程,多个 CPU 在相同时间执行多个线程,多核 CPU 才能更好的发挥多线程的效率。
────────────────────────────────────────────────────────────
1.4 并行与并发
(1)并行:多个 CPU 同时执行多个任务。比如:多个人同时做不同的事。
(2)并发:一个 CPU ( 采用时间片 ) 同时执行多个任务。比如:秒杀、多人同做一件事。
并发惯用的解决方式: 扩容服务器;服务器资源排队 |
────────────────────────────────────────────────────────────
1.5 多线程的优缺点
优点 | (1)提高应用程序的响应。对图形化界面更有意义,可增强用户体验。 (2)提高计算机系统 CPU 的利用率 (3)改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改 |
缺点 | (1)降低其他线程的执行概率 (2)用户会感受到软件的卡顿问题 (3)增加系统压力,资源压力 (4)多线程情况下有可能会导致共享资源问题,线程冲突,线程安全,线程死锁。 |
────────────────────────────────────────────────────────────
1.6 何时需要多线程
(1)程序需要同时执行两个或多个任务。
(2)程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等。
(3)需要一些后台运行的程序时。
────────────────────────────────────────────────────────────
2. 线程类 Thread
2.1 Thread 类的特性
(1)Java 的 JVM 允许程序运行多个线程,通过 java.lang.Thread 类来体现。
(2)Thread 类是 Java 中线程的基类,有一部分线操作相关方法。
(3)每个线程都是通过某个特定 Thread 对象或其子类对象的 run( ) 方法来完成操作的,run 方法内容是线程目标任务代码,称为线程体。
(4)Thread 对象通过 start( ) 方法来启动本线程,而非直接调用 run( ),否则不是多线程。
(5)注意:不能重复启动已经 start( ) 的线程,如需多次调用,创建多个线程对象分别调用,如果重复调用则将抛出 IllegalThreadStateException (非法线程状态)异常
────────────────────────────────────────────────────────────
2.2 Thread 类的构造器
Thread( ) | 创建新的 Thread 对象 |
Thread( String threadname ) | 创建线程并指定线程实例名 |
Thread( Runnable target ) | 指定创建线程的目标对象,它实现了 Runnable 接中的 run 方法 |
Thread( Runnable target, String name ) | 指定创建线程的目标对象并指定线程实例名 |
────────────────────────────────────────────────────────────
2.3 Thread 类的常用方法
static Thread currentThread( ) | 返回当前线程。在 Thread 子类中就是 this,通常用于主线程和 Runnable 实现类。 |
void start( ) | 启动线程,并执行对象的 run( ) 方法 |
void run( ) | 线程在被调度时执行的操作 |
String getName() | 返回线程的名称 |
void setName( String name ) | 设置该线程名称 |
static void yield( ) | 线程让步:暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 ,若队列中没有同优先级的线程,忽略此方法 |
join( ) | 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止,低优先级的线程也可以获得执行 |
static void sleep( long millis ) | (指定时间:毫秒),令当前活动线程在指定时间段内放弃对 CPU 控制,使其他线程有机会被执行,millis 毫秒后重排队。可能会有 InterruptedException 异常 |
stop( ) | 强制线程生命期结束,不推荐使用 |
boolean isAlive( ) | 返回 boolean,判断线程是否还活着 |
boolean isDaemon() | 判断当前线程是否为守护线程/后台线程。 |
void setDaemon(boolean on) | 设置当前线程是否为守护线程,on 为 true 当前线程为守护线程, |
────────────────────────────────────────────────────────────
3. 线程的创建(JDK1.5前)
3.1 方式一:继承 Thread 类(不推荐)
(1)创建一个继承于 Thread 类的子类。
(2)重写 Thread 类的 run( ) 方法。
(3)创建 Thread 类的子类的对象。
(4)通过此对象调用 start( ) 方法: 启动当前线程,调用当前对象的 run( ) 方法。
(5)代码实现
public class CreateThreadTest01 {
public static void main(String[] args) {
// 3. 创建 Thread 类的子类的对象
MyThread my = new MyThread();
// 4. 通过此对象调用 start( ) 方法
// my.run(); 不能通过对象.run( ) 调用 run( ) 方法,否则不是多线程
// my.start(); 不能重复调用已经 start( ) 的线程,否则报 IllegalThreadStateException
my.start();
for (int i=1; i<101; i++) {
if (!(i % 2 == 0)) {
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
// 重新创建一个线程对象 , 调用 start( ) 方法
MyThread my1 = new MyThread();
my1.start();
}
}
// 1.创建一个继承于 Thread 类的子类
class MyThread extends Thread{
// 2.重写 Thread 类的 run 方法,输出 1-100 之间的偶数
@Override
public void run() {
for (int i=1; i<101; i++){
if (i%2 == 0){
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}
}
────────────────────────────────────────────────────────────
3.2 方式二:实现 Runnable 接口
(1)创建一个实现 Runnable 接口的类
(2)实现 Runnable 接口中的 run( ) 方法
(3)创建实现类的对象
(4)将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
(5)通过 Thread 类对象调用 start( ) 方法 ①启动当前线程 ②调用当前对象的 run( ) 方法
(6)代码实现
public class CreateThreadTest02 {
public static void main(String[] args) {
// 3. 创建实现类的对象
MThread mt01 = new MThread();
// 4. 将此对象作为参数传递到Thread类的构造器中,创建 Thread 类的对象调用 start
Thread t1 = new Thread(mt01);
// 5. 通过创建 Thread 类的对象调用 start 方法:启动线程、调用当前线程的run方法
t1.start();
}
}
// 1.创建一个实现 Runnable 接口的类
class MThread implements Runnable{
// 2.实现 Runnable 接口中的 run 方法
@Override
public void run() {
System.out.println("malong");
}
}
────────────────────────────────────────────────────────────
3.3 创建线程的两种方式比较(JDK1.5前)
(1)优先选择:实现 Runnable 接口的方式
(2)原因:实现 Runnable 接口的方式没有类的单继承性的局限性;通过 Thread( Runnable target ) 可以创建多个线程,多个线程共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
(3)联系:public class Thread implements Runnable,Thread 类本身也继承了 Runnable 接口。
(4)相同点:都需重写 run 方法
────────────────────────────────────────────────────────────
3.4 创建 Thread 类的匿名子类的方式
new Thread(){
@Override
public void run(){
//实现代码 ...
}
}.start();
────────────────────────────────────────────────────────────
4 线程的创建(JDK1.5后)
4.1. 新增方式一:实现 Callable 接口
4.1.1 Callable 接口
(1)Callable 功能与 Runnable 相比更加丰富和强大。
(2)相比 run() 方法,可以有返回值。
(3)方法可以抛出异常,被外面的操作捕获,获取异常的信息。
(4)支持泛型的返回值。
(5)需要借助 FutureTask 类,比如获取返回结果。
4.1.2 Future 接口
(1)对具体 Runnable、Callable 任务的执行结果进行取消、查询是否完成、获取结果等。
(2)FutrueTask 是 Futrue 接口的唯一的实现类。
(3)FutureTask 同时实现了 Runnable,Future 接口。它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值 。
4.1.3 实现方式
//创建一个实现 Callable 接口的类
class NumThread implements Callable{
//重写 Callable 接口的 call 方法
@Override
public Object call() throws Exception {
}
}
public class ThreadNew01 {
public static void main(String[] args) {
//实例化实现类
NumThread numThread = new NumThread();
//将此 Callable 接口实现类的对象作为传递到 FutureTask 构造器中, 创建 FutureTask 的对象
FutureTask futureTask = new FutureTask(numThread);
//将 FutureTask 的对象作为参数传递到 Thread 类的构造器中, 创建 Thread 对象
Thread thread = new Thread(futureTask);
//调用 start 方法
thread.start();
try {
// get() 返回值即为 FutureTask 构造器参数 CaLlable 实现类重写的 call() 的返回值。
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
────────────────────────────────────────────────────────────
4.2 新增方式二:使用线程池
4.2.1 线程池
(1)背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
(2)思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
(3)好处:
提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 便于线程管理 |
(4)线程池的相关参数:
corePoolSize:核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长时间后会终止 |
4.2.2 实现方式
public class ThreadNew02{
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor s1 = (ThreadPoolExecutor)service;
//设置线程池的属性
System.out.println(service.getClass());
s1.setCorePoolSize(15);
//2.执行指定的线程的操作,需要提供实现 Runnable 接口或 Callable 接口的实现类对象
service.execute(new ThreadTool01());//适合使用于 Runnable
service.execute(new ThreadTool02());
//service.submit(Callable callable);//适合使用于 Callable 和 Runnable 接口
//3.关闭连接池
service.shutdown();//关闭连接池
}
}
4.2.3 线程池相关API
(1)JDK 5.0 提供了线程池相关 API:ExecutorService 和 Executors
ExecutorService 真正的线程池接口。 常见子类ThreadPoolExecutor |
| ||||||||
Executors 工具类、线程池的工厂类,用于创建并返回不同类型的线程池 |
|