1、程序、进程、线程
程序是为实现特定功能使用某语言而编写的代码,程序是静态的。例如:为了实现360安全卫生的垃圾清理功能而编写的代码即为程序。
进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这也是进程的生命周期,它有完整的开始及消亡。每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位。
2、线程的创建
在JDK1.5以前线程有两种创建方式,分别为集成Thread类、实现Runnable接口。在JDK1.5以后增加两种线程创建方式:实现Callable接口、使用线程池创建。
以下通过窗口售票示例来进行说明。
2.1 通过继承Thread类创建创建线程
通过继承Thread类,重写run方法,在run方法内实现自己的业务即可。
package com.javaboy.thread.blob;
/**
* 售票窗口线程
*/
public class Window extends Thread {
/**
* 票总数
*/
private int tickets = 100;
@Override
public void run() {
while(true) {
if(tickets > 0) {
System.out.println(this.getName() + "售票,票号:" + tickets);
tickets--;
} else {
// 当票数为0时跳出循环
break;
}
}
}
}
上述代码Window为自定义的线程,通过继承Thread来达到线程的目的,荣国重写run方法来达到线程内售票的业务逻辑。
测试代码:
package com.javaboy.thread.blob;
public class WindowTest {
public static void main(String[] args) {
Window window1 = new Window();
// 设置线程名称
window1.setName("窗口1");
Window window2 = new Window();
window2.setName("窗口2");
Window window3 = new Window();
window3.setName("窗口3");
// 启动线程
window1.start();
window2.start();
window3.start();
}
}
在上述测试代码中创建了三个Window对象,即为三个线程。
通过setName设置线程的名称,setName为Thread的方法,因为Window继承Thread,因此可以直接使用。
通过start方法来启动线程,在启动后会自动调用线程的run方法进行执行。此处需要注意,不能直接调用run方法,否则会变成方法调用,而不是线程启动。
测试结果(部分结果,未包含全部):
窗口2售票,票号:100
窗口1售票,票号:100
窗口1售票,票号:99
窗口1售票,票号:98
窗口1售票,票号:97
窗口1售票,票号:96
窗口1售票,票号:95
窗口1售票,票号:94
窗口1售票,票号:93
窗口3售票,票号:100
窗口1售票,票号:92
窗口1售票,票号:91
窗口1售票,票号:90
窗口2售票,票号:99
窗口1售票,票号:89
窗口3售票,票号:99
窗口1售票,票号:88
窗口2售票,票号:98
窗口1售票,票号:87
窗口3售票,票号:98
窗口1售票,票号:86
窗口2售票,票号:97
窗口1售票,票号:85
窗口3售票,票号:97
窗口1售票,票号:84
窗口2售票,票号:96
窗口1售票,票号:83
窗口3售票,票号:96
窗口1售票,票号:82
窗口2售票,票号:95
窗口1售票,票号:81
窗口3售票,票号:95
窗口1售票,票号:80
窗口2售票,票号:94
窗口1售票,票号:79
窗口3售票,票号:94
窗口1售票,票号:78
窗口2售票,票号:93
窗口1售票,票号:77
窗口3售票,票号:93
窗口1售票,票号:76
窗口2售票,票号:92
窗口1售票,票号:75
窗口3售票,票号:92
窗口1售票,票号:74
窗口2售票,票号:91
窗口1售票,票号:73
窗口1售票,票号:72
窗口1售票,票号:71
窗口3售票,票号:91
窗口3售票,票号:90
窗口3售票,票号:89
窗口3售票,票号:88
窗口3售票,票号:87
窗口3售票,票号:86
窗口1售票,票号:70
窗口1售票,票号:69
窗口1售票,票号:68
窗口2售票,票号:90
窗口1售票,票号:67
窗口1售票,票号:66
窗口3售票,票号:85
窗口1售票,票号:65
窗口2售票,票号:89
窗口1售票,票号:64
窗口3售票,票号:84
窗口1售票,票号:63
窗口2售票,票号:88
窗口1售票,票号:62
窗口3售票,票号:83
窗口3售票,票号:82
窗口3售票,票号:81
窗口3售票,票号:80
线程的执行是由CPU决定的,因此不一定会知道哪个线程先执行,当然可以通过设置线程优先级等方式来提高优先执行的命中率。
在上述的测试结果中已经看到不同的窗口售出的票号出现了重复,这就是线程同步问题,在以后的介绍中进行解决。
2.2 通过实现Runnable接口创建线程
通过实现Runnable接口,重写run方法来实现线程。
创建步骤:
- 创建一个类(A)并实现Runnable接口;
- 重写run方法,将线程所要实现的功能写在run方法中;
- 创建实现Runnable接口类(A)的对象,将对象当做Thread类构造函数的参数参入;
- 使用Thread构造方法创建一个对象,调用start方法运行新线程。
package com.javaboy.thread.my;
/**
* 线程创建方式:实现Runnable接口
*/
class Thread2 implements Runnable {
@Override
public void run() {
for(int i=1; i<=10; i++) {
System.out.println(Thread.currentThread().getName() + "运行的结果。。。" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName() + "正在运行。。。");
Thread2 thread2 = new Thread2();
// 创建新线程
Thread thread = new Thread(thread2);
thread.setName("测试线程");
// 启动线程
thread.start();
System.out.println(Thread.currentThread().getName() + "运行结束。");
}
}
运行结果:
主线程正在运行。。。
主线程运行结束。
测试线程运行的结果。。。1
测试线程运行的结果。。。2
测试线程运行的结果。。。3
测试线程运行的结果。。。4
测试线程运行的结果。。。5
测试线程运行的结果。。。6
测试线程运行的结果。。。7
测试线程运行的结果。。。8
测试线程运行的结果。。。9
测试线程运行的结果。。。10
2.3 通过实现Callable接口创建线程
通过实现Callable接口与实现Runnable接口相比,实现Callable接口时线程可以有返回值,同时可以抛出异常
创建步骤:
- 创建一个类(A)并实现Callable接口,需要指定泛型,泛型表示线程返回的类型;
- 重写Callable的call方法,将线程要实现的功能写在call方法中,同时返回指定的数据,如果有异常,可以抛出异常;
- 创建FutureTask对象,构造函数的参数为实现Callable接口的类(A)的对象;
- 创建Thread类,构造函数参数为创建的FutureTask对象;
- 通过Thread的start方法启动线程;
- 如果需要获取线程返回值和异常,需要调用FutureTask对象的get方法进行获取。
package com.javaboy.thread.my;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 线程创建方式:实现Callable接口
*/
public class CallableThreadTest {
public static void main(String[] args) {
// 创建FutureTask类,构造器参数为线程实例
FutureTask<String> futureTask = new FutureTask<>(new ThreadDemo());
Thread thread = new Thread(futureTask);
thread.setName("测试线程");
// 线程启动
thread.start();
// 获取线程返回值和异常
try {
String result = futureTask.get();
System.out.println(thread.getName() + "返回的结果:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* 线程类实现Callable接口<br>
* 泛型标识线程返回的类型
*/
class ThreadDemo implements Callable<String> {
@Override
public String call() throws Exception {
String threadName = Thread.currentThread().getName();
for(int i=0; i<=10; i++) {
System.out.println(threadName + "执行" + i);
}
return "这里是线程【" + threadName + "】返回的结果";
}
}
运行结果:
测试线程执行0
测试线程执行1
测试线程执行2
测试线程执行3
测试线程执行4
测试线程执行5
测试线程执行6
测试线程执行7
测试线程执行8
测试线程执行9
测试线程执行10
测试线程返回的结果:这里是线程【测试线程】返回的结果
2.4 通过线程池创建线程
创建步骤:
- 使用Executors类的newFixedThreadPool创建指定数量的线程池;
- 调用线程池的execute方法执行实现Runnable接口的线程;通过线程池的submit方法执行实现Callable接口的线程;
- 调用线程池的shutdown方法关闭线程池。
package com.javaboy.thread.my;
import java.util.concurrent.*;
/**
* 线程创建:通过线程池创建
*/
public class ThreadPoolTest {
public static void main(String[] args) {
// 创建线程数量为2的线程池
ExecutorService service = Executors.newFixedThreadPool(1);
// 通过execute执行实现Runnable接口的线程
service.execute(new Demo1());
service.execute(new Demo2());
// 通过submit执行实现Callable接口的线程
Future<String> task = service.submit(new Demo3());
// 获取实现Callable接口的线程返回值和异常
try {
String result = task.get();
System.out.println("返回的结果:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
service.shutdown();
}
}
class Demo1 extends Thread {
@Override
public void run() {
Thread.currentThread().setName("线程1");
for(int i=1; i<=10; i++) {
System.out.println(Thread.currentThread().getName() + "输出的结果:" + i);
}
}
}
class Demo2 implements Runnable {
@Override
public void run() {
Thread.currentThread().setName("线程2");
for(int i=1; i<=10; i++) {
System.out.println(Thread.currentThread().getName() + "输出的结果:" + i);
}
}
}
class Demo3 implements Callable<String> {
@Override
public String call() throws Exception {
Thread.currentThread().setName("线程3");
for(int i=1; i<=10; i++) {
System.out.println(Thread.currentThread().getName() + "输出的结果:" + i);
}
return Thread.currentThread().getName() + "返回";
}
}
执行结果:
线程1输出的结果:1
线程1输出的结果:2
线程1输出的结果:3
线程1输出的结果:4
线程1输出的结果:5
线程1输出的结果:6
线程1输出的结果:7
线程1输出的结果:8
线程1输出的结果:9
线程1输出的结果:10
线程2输出的结果:1
线程2输出的结果:2
线程2输出的结果:3
线程2输出的结果:4
线程2输出的结果:5
线程2输出的结果:6
线程2输出的结果:7
线程2输出的结果:8
线程2输出的结果:9
线程2输出的结果:10
线程3输出的结果:1
线程3输出的结果:2
线程3输出的结果:3
线程3输出的结果:4
线程3输出的结果:5
线程3输出的结果:6
线程3输出的结果:7
线程3输出的结果:8
线程3输出的结果:9
线程3输出的结果:10
返回的结果:线程3返回
线程的创建方式说明完毕,也从中发现一些问题:线程同步问题,在以后的介绍中进行解决。