程序,进程,线程

- 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 进程则是程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 通常一个进程中可以包含若干个线程,一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
注: 很多多线程都是模拟出来的,真正的多线程是指多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快(CPU根据一定的线程调度算法来切换线程),所以就有同时执行的错局。
同一个进程下的所有线程都只能在CPU同一个核下运行,同一进程下的多个线程在同一个核下轮流使用处理器,因为处理速度快,看起来是并行,实际上同一进程下的多线程是串行。
多核可以同时运行多个进程,
核心概念
- 线程就是独立的执行路径;
- 在程序执行时,即使没有自己创建线程,后天也会有多个线程,如:主线程(main方法),GC线程(垃圾回收);
- main()称之为主线程,为系统的入口,用于执行整个程序。
- 在一个进程中,如果避开了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 线程对同一资源操作时,会存在资源抢夺问题,需要加入并发控制;
- 线程会带来额外的开销,如CPU调度时间,并发控制开销。
- 每个线程在自己的工作内存交换,内存控制不当会造成数据不一致。
线程的创建
线程的创建有三种方式:
- Thread class:继承Thread类(重点)
- Runnable接口:实现Runnable接口(重点)
- Callable接口:实现Callable接口(了解)
Thread
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
测试:
//创建线程方式1:继承Thread类,重写run() 方法;
public class TestThread extends Thread{
@Override
public void run() {
//run() 方法线程体
for (int i = 0; i < 15; i++) {
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
/* main()方法 主线程*/
//创建一个线程对象:
TestThread test= new TestThread();
//调用start()方法开启线程:
test.start();
for (int i = 0; i < 15; i++) {
System.out.println("我在学习多线程---"+i);
}
}
}
执行结果:
我在学习多线程---0
我在学习多线程---1
我在看代码---0
我在学习多线程---2
我在看代码---1
我在学习多线程---3
我在看代码---2
我在学习多线程---4
我在看代码---3
我在学习多线程---5
我在看代码---4
我在学习多线程---6
我在看代码---5
我在学习多线程---7
我在看代码---6
我在学习多线程---8
我在看代码---7
我在学习多线程---9
我在看代码---8
我在学习多线程---10
我在看代码---9
我在看代码---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14
Process finished with exit code 0
如果调用run方法:
//创建一个线程对象:
TestThread test= new TestThread();
//调用start()方法开启线程:
test.run();
执行结果:
我在看代码---0
我在看代码---1
我在看代码---2
我在看代码---3
我在看代码---4
我在看代码---5
我在看代码---6
我在看代码---7
我在看代码---8
我在看代码---9
我在看代码---10
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14
我在学习多线程---0
我在学习多线程---1
我在学习多线程---2
我在学习多线程---3
我在学习多线程---4
我在学习多线程---5
我在学习多线程---6
我在学习多线程---7
我在学习多线程---8
我在学习多线程---9
我在学习多线程---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
从上述测试可以得出结论:

如上图所示:如果自定义线程对象在主线程main()方法中调用的是start()方法,那么此时便有多条执行路径,自定义线程会和主线程(main)会交替执行,而且执行的顺序不定,是由CPU调度来决定。如果自定义线程对象在主线程main()方法中调用的是run()方法,那么,此刻就只有主线程一条执行路径,两个线程的执行就有了先后顺序,如上执行结果,先执行完了自定义线程,再执行主线程。
Runnable
- 自定义线程类实现Runnable接口
- 实现run( )方法,编写线程执行体
- 创建线程对象,调用start() 方法
//创建线程方式2:实现Runnable接口,重写run
//执行线程需要丢入Runnable接口实现类,调用start方法
public class TestThread3 implements Runnable{
@Override
public void run() {
//run() 方法线程体
for (int i = 0; i < 15; i++) {
System.out.println("我在看代码---"+i);
}
}
/* main()方法 主线程*/
public static void main(String[] args) {
//创建一个线程对象:Runnable接口实现类对象
TestThread3 test3= new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程(代理,因为Thread类的底层也是实现了Runnable接口)
Thread thread=new Thread(test3);
thread.start();
for (int i = 0; i < 15; i++) {
System.out.println("我在学习多线程---"+i);
}
}
}
执行结果:
我在看代码---0
我在学习多线程---0
我在学习多线程---1
我在学习多线程---2
我在学习多线程---3
我在学习多线程---4
我在学习多线程---5
我在学习多线程---6
我在学习多线程---7
我在学习多线程---8
我在学习多线程---9
我在学习多线程---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
我在看代码---1
我在看代码---2
我在看代码---3
我在看代码---4
我在看代码---5
我在看代码---6
我在看代码---7
我在看代码---8
我在看代码---9
我在看代码---10
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14
Process finished with exit code 0
小结:
继承Thread类:
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
实现Runnable接口:
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
Thread类的底层也是实现了Runnable接口
Callable(了解)
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
- 提交执行:Future
result1=ser.submit(t1); - 获取结果:boolean r1=result1.get();
- 关闭服务:ser.shutdownNow();
多线程测试
采用多线程来下载网图:
1.下载jar包:commons-io-2.8.0.jar
https://sovzn.lanzous.com/iX6A7mncr2b 密码:bd3z
2.将jar包导入项目 ( idea中可以在项目中新建文件夹lib,将jar包复制到lib中,然后右击lib,Add as Library即可)
3.编写测试类
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread{
private String url; //网络图片地址
private String name; //保存的文件名
public TestThread2(String url,String name){
this.url=url;
this.name=name;
}
@Override
public void run() {
WebDownloader webDownloader=new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名为"+name);
}
public static void main(String[] args) {
TestThread2 t1=new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2771978851,2906984932&fm=26&gp=0.jpg","1.jpg");
TestThread2 t2=new TestThread2("https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1603365312,3218205429&fm=26&gp=0.jpg","2.jpg");
TestThread2 t3=new TestThread2("https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2853553659,1775735885&fm=26&gp=0.jpg","3.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下载器
class WebDownloader{
//自定义一个下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name)); //这个工具类由我们刚才导入的包提供
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}
//下载的图片会保存在项目的根目录下
执行结果:
下载了文件名为1.jpg
下载了文件名为3.jpg
下载了文件名为2.jpg
Process finished with exit code 0
从执行的结果可以看出:自定义子线程的执行顺序并非按照程序的书写顺序,如果再执行一次,可能会产生新的执行顺序,这样证明了结论:线程的运行由CPU调度器安排调度,先后顺序是不能人为干预的。
初始并发问题
//多个线程操作同一个对象
public class TestThread4 implements Runnable{
//票数
private int ticketNums=10;
@Override
public void run() {
while(true){
if(ticketNums<=0){ break;}
try {
//模拟延时
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestThread4 test4=new TestThread4();
//创建多个线程
Thread thread1= new Thread(test4,"线程1");
Thread thread2= new Thread(test4,"线程2");
Thread thread3= new Thread(test4,"线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
执行结果:
线程1--拿到了第10票
线程3--拿到了第8票
线程2--拿到了第9票
线程1--拿到了第7票
线程2--拿到了第7票
线程3--拿到了第6票
线程2--拿到了第5票
线程1--拿到了第5票
线程3--拿到了第5票
线程1--拿到了第4票
线程3--拿到了第3票
线程2--拿到了第3票
线程3--拿到了第2票
线程1--拿到了第0票
线程2--拿到了第1票
Process finished with exit code 0
从上结果可以看出:多个线程操作同一资源的情况下,线程不安全,数据紊乱。
静态代理
实例测试:
//静态代理测试:取快递
public class StaticProxy {
public static void main(String[] args) {
You you =new You();
PostMan postMan=new PostMan(you);
postMan.success();
}
}
interface GetExpress{
//提取快递成功
void success();
}
//真实对象:你去取快递
class You implements GetExpress{
@Override
public void success() {
System.out.println("提取快递成功");
}
}
//代理对象:邮递员帮你去取快递
class PostMan implements GetExpress{
private GetExpress target;//代理目标
public PostMan(GetExpress target) {
this.target = target;
}
@Override
public void success() {
before();
this.target.success();
after();
}
private void before(){
System.out.println("取快递前:付定金");
}
private void after(){
System.out.println("取快递后:付尾款");
}
}
执行结果:
取快递前:付定金
提取快递成功
取快递后:付尾款
Process finished with exit code 0
总结:
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实对象
好处:代理对象可以做很多真实对象做不了的业务(如上面的before和after方法),真实对象专注做自己的业务
Lambda表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
函数式接口定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
如:
public interface Runnable{
public abstract void run();
}
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
实例1:
package com.lamda;
/*
* 推导lambda表达式
* */
public class TestLamda {
public static void main(String[] args) {
//正常执行
ILike like = new Like();
like.lambda();
//用lambda简化,将下列语句传入接口ILike,相当于实现了接口中的lambda()方法
like=()->{
System.out.println("i like lambda2");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
// 2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like Lambda");
}
}
结果:
I like Lambda
i like lambda2
Process finished with exit code 0
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
Lambda 表达式实例
Lambda 表达式的简单例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
实例2:
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
System.out.println("test1---"+addition. operation(3 ,1 ));
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
System.out.println("test2---"+subtraction. operation(3 ,1 ));
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
System.out.println("test3---"+multiplication. operation(3 ,1 ));
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
//函数式接口
interface MathOperation {
int operation(int a, int b);
}
//函数式接口
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
输出结果:
test1---4
test1---2
test1---6
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google
使用 Lambda 表达式需要注意以下两点:
- Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
- Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
线程状态

过程描述:

线程方法:
| 方法 | 说明 |
|---|---|
| setPriority(int newPriority) | 更改线程的优先级 |
| static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
| void join() | 等待该线程终止 |
| static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
| void interrupt() | 中断线程,别用这个方式 |
| boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
- 不推荐使用JDK提供的stop(),destroy()方法【已废弃】。
- 推荐线程自己停止下来,建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
测试:
/*
* 测试停止线程
* 1.建议线程正常停止----》利用次数,不建议死循环
* 2.建议使用标志位----》设置一个标志位
* 3.不要使用stop或者destroy等过时的或JDK不建议使用的方法
* */
public class TestStopThread implements Runnable{
//1.设置一个标志位
private boolean flag=true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println("线程正在执行------"+i++);
}
}
//2.设置一个公开的方法停止线程,转换标志位
public void stopThread(){
this.flag=false;
}
public static void main(String[] args) {
//启动线程
TestStopThread test=new TestStopThread();
new Thread(test).start();
for (int i = 0; i <= 20; i++) {
System.out.println("main线程正在运行"+i);
if (i==12){
test.stopThread();//调用stopThread方法切换标志位,让线程停止
System.out.println("线程停止运行--------------------");
}
}
}
}
执行结果:
main线程正在运行0
线程正在执行------0
main线程正在运行1
线程正在执行------1
main线程正在运行2
线程正在执行------2
线程正在执行------3
线程正在执行------4
线程正在执行------5
main线程正在运行3
main线程正在运行4
线程正在执行------6
main线程正在运行5
main线程正在运行6
main线程正在运行7
main线程正在运行8
main线程正在运行9
main线程正在运行10
main线程正在运行11
main线程正在运行12
线程正在执行------7
线程停止运行--------------------
main线程正在运行13
main线程正在运行14
main线程正在运行15
main线程正在运行16
main线程正在运行17
main线程正在运行18
main线程正在运行19
main线程正在运行20
Process finished with exit code 0
从上执行结果可以看出,当i=12时,子线程停止运行,主线程main一直执行完
线程休眠:sleep
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
测试:
模拟网络延时:
//模拟网络延时
public class TestSleep implements Runnable {
//票数
private int ticketNums=10;
@Override
public void run() {
while(true){
if(ticketNums<=0){ break;}
try {
//模拟延时
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--拿到了第"+ ticketNums-- +"票");
}
}
public static void main(String[] args) {
TestSleep test4=new TestSleep();
new Thread(test4,"线程1").start();
new Thread(test4,"线程2").start();
new Thread(test4,"线程3").start();
}
}
结果:
线程3--拿到了第10票
线程2--拿到了第10票
线程1--拿到了第9票
线程2--拿到了第7票
线程1--拿到了第8票
线程3--拿到了第8票
线程3--拿到了第6票
线程2--拿到了第6票
线程1--拿到了第6票
线程2--拿到了第5票
线程1--拿到了第5票
线程3--拿到了第4票
线程3--拿到了第3票
线程2--拿到了第2票
线程1--拿到了第1票
Process finished with exit code 0
模拟倒计时:
//模拟倒计时
public class TestSleep2 {
public static void tendown(){
int num=10;
while(true){
try {
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
tendown();
}
}
结果:每一秒打印一个数字,打印十个
10
9
8
7
6
5
4
3
2
1
Process finished with exit code 0
线程礼让:yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态,等待CPU再次调度
- CPU重新调度,礼让不一定成功,CPU可能再次优先调度该线程。
测试:
//测试礼让线程
public class TestYield {
public static void main(String[] args) {
Myyield myyield=new Myyield();
new Thread(myyield,"线程1").start();
new Thread(myyield,"线程2").start();
}
}
class Myyield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":线程开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+":线程停止执行");
}
}
执行结果:
线程2:线程开始执行
线程1:线程开始执行
线程1:线程停止执行
线程2:线程停止执行
Process finished with exit code 0
从上执行结果可以看出:初次调度,CPU先执行的线程2,线程2礼让成功,开始执行线程1,线程1礼让失败,再次执行线程1,线程1执行完后,再执行完线程2
线程强制执行:join
- Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
- 可以想象成插队
测试:
//测试join
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("VIP子线程-----"+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动线程
TestJoin test = new TestJoin();
Thread thread=new Thread(test);
thread.start();
//主线程
for (int i = 0; i <= 12; i++) {
if (i==8){
thread.join();//插队
}
System.out.println("main线程--"+i);
}
}
}
结果:
main线程--0
main线程--1
main线程--2
main线程--3
main线程--4
main线程--5
main线程--6
main线程--7
VIP子线程-----0
VIP子线程-----1
VIP子线程-----2
VIP子线程-----3
VIP子线程-----4
VIP子线程-----5
VIP子线程-----6
VIP子线程-----7
VIP子线程-----8
VIP子线程-----9
main线程--8
main线程--9
main线程--10
main线程--11
main线程--12
Process finished with exit code 0
由上运行结果可以看出,当 i= 8时,子线程被强制执行,等到子线程执行完,主线程main才继续执行。
线程状态
Thread.State
- NEW: 尚未启动的线程处于此状态
- RUNNABLE:正在java虚拟机中执行的线程处于此状态
- BLOCKED:被阻塞等待监视器锁定的线程处于此状态
- WAITING:正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING: 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- TERMINATED:已退出的线程处于此状态
//测试观察线程的状态
public class TestState {
public static void main(String[] args) {
Thread thread=new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--------");
}
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动后状态
thread.start();
state= thread.getState();
System.out.println(state);
/*while (state!=Thread.State.TERMINATED){
//只要线程不终止,就一直输出状态
state= thread.getState();
System.out.println(state);
}*/
}
}
结果:
NEW
RUNNABLE
--------
--------
--------
--------
--------
Process finished with exit code 0
- Post link: http://sovzn.github.io/2021/03/03/%E5%A4%9A%E7%BA%BF%E7%A8%8B01-%E7%BA%BF%E7%A8%8B%E6%A6%82%E8%BF%B0/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.



若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues