活动地址:CSDN21天学习挑战赛
目录
一、辨析线程与进程
进程Process | 线程Thread | |
基本单位 | 系统资源分配的基本单位 | CPU调度和执行的基本单位 |
状态 | 动态 | 静态 |
区别 | ①是可并发执行的具有独立功能的应用程序(相当于电脑可同时打开多个浏览器的多个界面) ②拥有运行空间和资源(相当于应用程序中的图像、声音、文字等) |
①只是执行的代码 ② 不具有资源 |
联系 | 一个进程中的每个任务称为一个线程 |
线程的基本概念:
1)线程就是独立的执行路径;
2)在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程(用户线程),gc线程(守护线程);main()称之为主线程,为系统的入口,用于执行整个程序;
3)在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,操作系统紧密相关的,先后顺序是不能人为干预的。
4)对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;线程会带来额外的开销,如cpu调度时间,并发控制开销。
二、单线程运行和多线程运行的区别
单线程:进程由main()函数开始执行,只包含一个主线程
多线程:在main()函数中创建多个线程对象,并调用start()函数启动并发执行
三、线程创建
3.1 继承Thread类
1、步骤:①创建一个线程类(类声明为Thread子类)
class 线程类名称 extends Thread
②在该线程类中重写run()方法
public void run(){
......; //该线程想要执行的操作
}
③在main()函数中创建该线程类对象,并调用start()函数启动
new 线程类().start();//函数 线程类() 是初始化线程类对象
2、 示例:创建两个线程,观察多线程的运行次序的实现机制
//通过继承Thread类创建线程,“线程1”和“线程2”分别执行10次
package Thread;
public class TwoThreadsTest {
public static void main (String[] args) {
new SimpleThread("线程1").start();//虽然线程的定义位于run()方法里,线程的运行调用start(),相当于调用线程管理器
//调用start相当于去线程管理器注册线程运行的方式及准备工作,主线程调用start(),子线程执行run()方法
new SimpleThread("线程2").start();
}
}class SimpleThread extends Thread {
public SimpleThread(String str) { //线程对象的初始化函数定义
super(str);//继承父类Thread属性
}
public void run() {//重写run函数
for (int i = 0; i < 10; i++) {//输出10次,getName:获得线程的名字
System.out.println(i + " " + getName());
try {
sleep((int)(Math.random() * 1000));//暂停随机的将近1s(sleep(1)是暂停1ms)
} catch (InterruptedException e) {}//有异常干扰时,进程结束
}
System.out.println("DONE! " + getName());//获得线程名字
}
}
运行结果:"线程1"和"线程2"交叉输出,说明两个线程是同时执行的;每一次点击运行时,各个线程输出顺序是不同(随机)的。
3.2 实现Runnable接口
1、步骤:①创建一个线程类(该类是实现类Runnable接口)
class 线程类名称 implements Runnable
②在该线程类中重写run()方法
public void run(){
......; //该线程想要执行的操作
}
③在main()函数中创建该线程类对象,并类型转化为Thread类型,调用start()函数启动
线程类 t = new 线程类();
new Thread(t).start();
2、同样的示例如下:
//通过实现Runnable类创建线程,“线程1”和“线程2”分别执行10次
package Thread;
public class ThreadRunnable2 {
public static void main (String[] args) {
SimpleThread t1 = new SimpleThread("线程1");
SimpleThread t2 = new SimpleThread("线程2");
new Thread(t1).start(); //强制类型转化,转化为Thread类型才能启动start()函数
new Thread(t2).start();
}
}class SimpleThread implements Runnable {
String string;
public SimpleThread(String str) {
//由于没有“继承”的关系,不使用super,而是关键字this定义子类变量
this.string = str;
}
public void run() {//重写run函数
for (int i = 0; i < 10; i++) {//输出10次,由于没有“继承”的关系,不能使用Thread内置的getName函数获得变量名称
System.out.println(i + " " + string);
try {
Thread.sleep((int)(Math.random() * 1000));//sleep函数的使用继承Thread类,即Thread.sleep
} catch (InterruptedException e) {}//有异常干扰时,进程结束
}
System.out.println("DONE! " + string);//获得线程名字
}
}
运行结果:
3.3 实现Callable接口
1、步骤:①创建一个线程类,实现Callable接口,需要返回值类型
class 线程类名称 implements Callable<返回值类型>
②在该线程类中重写call()方法,主函数声明时需要抛出异常
public <返回值类型> call(){
......; //该线程想要执行的操作
try {
......;
} catch (InterruptedException e) {}
return r;
}
③在main()函数中创建该线程类对象,通过创建执行服务启动(步骤④~⑦)
线程类 t = new 线程类();
④创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(n); //n为线程池中线程的个数,ser为执行服务类对象
⑤提交执行
Future<返回值类型> result = ser.submit(t);
⑥获取结果
<返回值类型> r = result.get(); //定义返回值变量,由return语句返回
⑦关闭服务
ser.shutdownNow();
2、示例如下:
//通过实现Callable类创建线程,“线程1”和“线程2”分别执行10次
package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class ThreadCallable {
public static void main (String[] args) throws InterruptedException, ExecutionException {
SimpleThread2 t1 = new SimpleThread2("线程1");
SimpleThread2 t2 = new SimpleThread2("线程2");
//创建执行服务(线程池创建2个线程)
ExecutorService ser = Executors.newFixedThreadPool(2);
//提交执行
Future<Boolean> r1 = ser.submit((Callable<Boolean>) t1);
Future<Boolean> r2 = ser.submit((Callable<Boolean>) t2);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();//关闭服务
ser.shutdown();
}
}class SimpleThread2 implements Callable<Boolean> { //这里假设返回的是布尔类型(不影响结果的输出)
String string;
public SimpleThread2(String str) {
//由于没有“继承”的关系,不使用super,而是关键字this定义子类变量
this.string = str;
}
public Boolean call() {//重写call函数
for (int i = 0; i < 10; i++) {//输出10次,由于没有“继承”的关系,不能使用Thread内置的getName函数获得变量名称
System.out.println(i + " " + string);
try {
Thread.sleep((int)(Math.random() * 1000));//sleep函数的使用继承Thread类,即Thread.sleep
} catch (InterruptedException e) {}//有异常干扰时,进程结束
}
System.out.println("DONE! " + string);//获得线程名字
return true;
}
}
3、运行结果:
四、Thread 和Runnable的区别
若一个类继承Thread,则不适合资源共享;但是若一个类实现了Runable接口,容易实现资源共享。
总结:实现Runnable接口比继承Thread类所具有的优势
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。