什么是多线程
要说清楚什么是线程,首先要分清程序,进程线程的区别
-
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一
段静态的代码,静态对象。 -
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态
的过程:有它自身的产生、存在和消亡的过程。 ——生命周期- 如:运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域
-
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
多线程的优点
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和
修改
Java实现多线程
实现方法一:继承Thread类
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:启动线程,调用run方法。
注意点:
- 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU
调度决定。- 想要启动多线程,必须调用start方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上
的异常“IllegalThreadStateException” 。
实现类
class CreateThread1 extends Thread{
@Override
public void run() {
for (int i =0;i<100;i++){
System.out.println("子进程" + i);
}
}
}
测试代码
@Test
public void TheadTest1() {
Thread thread = new CreateThread1();
//调用start方法启动线程,但是无法控制JVM什么时候执行
thread.start();
System.out.println("主线程");
}
输出
主线程
//这里虽然先启动子线程,但是主线程却先运行
子进程0
子进程1
子进程2
子进程3
子进程4
...
实现方法二:实现Runnable接口
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程, 调用Runnable子类接口的run方法。
实现类
class CreateThread2 implements Runnable{
@Override
public void run() {
for (int i =0;i<100;i++){
try {
//让程序休眠0.1秒,避免JVM来不及切换线程
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i%2==0)
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}
测试
@Test
public void TheadTest2() throws InterruptedException {
//创建子类
CreateThread2 cthread = new CreateThread2();
//通过Thread的含参构造器创造线程
Thread thread1 = new Thread(cthread);
Thread thread2 = new Thread(cthread);
//设置线程名称
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
//在测试中避免因为主线程结束导致其他线程还没执行就结束
Thread.sleep(1000);
}
输出
线程1:0
线程2:0
线程1:2
线程2:2
线程1:4
...
实现方法三:实现Callable接口
- Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式: Callable 接口
- Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
- Callable 需要依赖FutureTask , FutureTask 也可以用作闭
锁。
class CreateThread3 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=0;i<=100;i++){
sum+=i;
}
return sum;
}
}
@Test
public void TheadTest3(){
CreateThread3 cthread = new CreateThread3();
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(cthread);
new Thread(result).start();
try {
//获得返回的结果
int sum = result.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
输出
5050
实现方法四:使用线程池
一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
二、线程池的体系结构:
java.util.concurrent.Executor : 负责线程的使用与调度的根接口
-
ExecutorService 子接口: 线程池的主要接口
- ThreadPoolExecutor 线程池的实现类
- ScheduledExecutorService 子接口:负责线程的调度
- ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现ScheduledExecutorService
三、工具类 : Executors
ExecutorService newFixedThreadPool() : 创建固定大小的线程池
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
class CreateThread4 implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
@Test
public void TheadTest4() throws InterruptedException {
// 创建一个固定5个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
CreateThread4 cthread = new CreateThread4();
for(int i=0;i<10;i++){
// 向线程池提交线程
pool.submit(cthread);
}
Thread.sleep(5000);
pool.shutdown();
}
输出
pool-1-thread-3 : 1
pool-1-thread-1 : 4
pool-1-thread-5 : 0
pool-1-thread-2 : 3
pool-1-thread-4 : 2
pool-1-thread-5 : 6
...
可以向线程池提交实现Callable的方法吗?
@Test
public void TheadTest5() throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(5);
CreateThread3 cthread = new CreateThread3();
//创建一个Future<Integer>类型的列表,存放future
List<Future<Integer>> list = new ArrayList<>();
for(int i=0;i<10;i++){
//获取返回值
Future<Integer> future = pool.submit(cthread);
list.add(future);
}
Thread.sleep(5000);
pool.shutdown();
for(Future<Integer> future:list){
System.out.println(future.get());
}
}
输出
5050
5050
5050
5050
5050
5050
5050
5050
5050
5050
继承方式和实现方式的联系与区别
public class Thread extends Object implements Runnable
区别
- 继承Thread:线程代码存放Thread子类run方法中。
- 实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线
程来处理同一份资源。
Thread类的有关方法
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程名称
- static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
- static void yield(): 线程让步
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
- join() : 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
- 低优先级的线程也可以获得执行
- static void sleep(long millis): (指定时间:毫秒)
- 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
- 抛出InterruptedException异常
- stop(): 强制线程生命期结束,不推荐使用
- boolean isAlive(): 返回boolean,判断线程是否还活着
线程的优先级
线程的优先级等级
- MAX_PRIORITY: 10
- MIN _PRIORITY: 1
- NORM_PRIORITY: 5
涉及的方法
-
getPriority() : 返回线程优先值
-
setPriority(int newPriority) : 改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
线程的生命周期
Java语言使用Thread类
及其子类的对象来表示线程, 在它的一个完整的生命周期中通常要经历如下的五
种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束