1. 概念
(1)程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
(2)进程(process)是程序的一次执行过程,或是正在运行的一个程序。
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
(3)线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 线程作为调度和执行的单位,每个线程拥有独立的虚拟机栈栈、程序计数器(pc),线程切换的开销小。因为方法区(Method Area)和堆(Heap)是一个进程分配一套,所以一个进程中的多个线程共享相同的方法区和堆。
- 一个Java应用程序java.exe,其实至少有三个线程:main主线程,gc垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
2. 线程的创建
2.1 继承Thread类
- 创建一个继承Thread类的子类
- 重写Thread类的run( )方法,将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start()
//1. 创建一个继承Thread类的子类
class MyThread extends Thread {
//2. 重写Thread类的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
// t1.run();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().
getName() + ":" + i + "***main()***");
}
}
}
}
public static void main(String[] args) {
//创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().
getName() + ":" + i);
}
}
}
}.start();
}
注:
start()
方法作用:①启动当前线程 ② 调用当前线程的run()
。仅调用run()
方法无法开启新线程。此外,不可以让已经start()的线程去执行。会报错:IllegalThreadStateException
。start()
方法不等于立刻启动线程,还需等CPU调度。
2.2 实现Runnable接口
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的
run
方法。 - 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法或采用匿名子类的方法。
public class ThreadTest1 {
@Test
public void test(){
MThread mThread = new MThread();
new Thread(mThread).start();
}
}
class MThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++){
if(i%2==0){
System.out.println(i);
}
}
}
}
比较创建线程的两种方式。
开发中优先选择:实现Runnable接口的方式。原因如下:
1. 实现的方式没有类的单继承性的局限性
2. 实现的方式更适合来处理多个线程有共享数据的情况。
两者之间的联系:public class Thread implements Runnable
,即Thread 类也是 Runnable 接口的子类
两者之间的相同点:两种方式都需要重写run()
,将线程要执行的逻辑声明在run()中。
2.3 实现Callable接口
与使用Runnable相比, Callable功能更强大:
- 相比run()方法,call()可以有返回值
- call()方法可以抛出异常
- Callable是支持泛型的
- 需要借助FutureTask类,比如获取返回结果
创建步骤:
- 创建一个实现Callable的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable接口实现类的对象
- 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
- 获取Callable中call方法的返回值(可选);get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
其中,Future接口:
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。
- FutrueTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值(可选)
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
2.4 线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用(类似生活中的公共交通工具)
优点:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理:
corePoolSize——核心池的大小
maximumPoolSize——最大线程数
keepAliveTime——线程没有任务时最多保持多长时间后会终止
创建步骤:
- 提供指定线程数量的线程池
- 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
- 关闭连接池
/**
* 创建线程的方式四:使用线程池
* 面试题:创建多线程有几种方式?四种!
*/
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池(ExecutorService类是一个接口)
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性(用于调度和管理)
System.out.println(service.getClass());
service1.setCorePoolSize(15);
service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
FutureTask futureTask = (FutureTask) service.submit(new NumThread3());//适合使用于Callable
//获取线程返回值
try {
System.out.println("100以内奇数的和是:"+ futureTask.get());
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//3.关闭连接池
service.shutdown();
}
}
class NumThread1 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumThread2 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumThread3 implements Callable {
private int num = 0;
@Override
public Object call() throws Exception{
// TODO Auto-generated method stub
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
num+=i;
}
}
return num;
}
}
3. Thread中的常用方法
start()
:启动当前线程;调用当前线程的run()方法run()
:通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中currentThread()
:静态方法,返回执行当前代码的线程getName()
:获取当前线程名setName()
:设置当前线程名(也可通过继承Thread的构造方法设置线程名)yield()
:释放当前cpu的执行权join()
:在线程a中调用线程b的join()
,此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态sleep(long millitime)
:让当前线程“睡眠”指定的 millitime 毫秒。在指定的 millitime 毫秒时间内,当前线程是阻塞状态。isAlive()
:判断当前线程是否存活
4. 线程的生命周期
JDK中用Thread.State
类定义了线程的几种状态(内部类)
java中线程的一个完整生命周期中通常要经历如下的五 种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
- 就绪:处于新建状态的线程被
start()
后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 - 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,
run()
方法定义了线程的操作和功能 - 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束