01、JAVAEE--多线程(一)

Javaee相当于是实现并发编程的基本功 😊~


一、进程与线程

1、具体关系

(进程没有跑起来之前叫做"可执行文件",也就是.exe文件)

每个进程的创建意味着一个房间和任务的诞生(资源容器+任务)

每个线程相当于完成任务的人

在一定范围内线程加多可以提高执行任务的效率

因此可以认为进程包含线程

2、局限性

不是线程越多越好,过多线程会让时间花费在cpu的调度上(上下文切换)和资源竞争(同时看上同一个资源)加剧,从而影响总体效率

3、二者特性对比

|------------|------------------|-------------------|
| | ### 进程 | ### 线程 |
| #### 资源占用 | #### 拥有独立资源,内存空间 | #### 共享进程资源 |
| #### 创建开销 | #### 大(分配独立资源) | #### 小(共享进程资源) |
| #### 稳定性 | #### 进程间相互独立互不影响 | #### 一个线程崩溃影响整个进程 |
| #### 上下文切换 | #### 代价大 | #### 代价小 |

4、上下文切换的理解

CPU从一个任务切换到另一个任务时,需要保存当前任务状态并恢复下一个任务状态的过程

5、总结

进程是操作系统资源分配的基本单位,拥有 独立的内存空间。而线程是CPU调度执行的基本单位,共享进程资源。多线程是通过并行处理和减少等待时间来提高效率的,但过多线程会让时间花费在cpu的调度上(上下文切换)和资源竞争加剧,从而影响总体效率


二、Thread(线程)

1、有什么需要注意的?🤨

一个进程至少存在一个线程(比如一个类中只有一个main方法)

2、创建线程

①继承Thread类,重写run

(不用导入包因为Thread也在java.lang包里面)

java 复制代码
class MyThread1 extends Thread{
//run方法是线程执行的入口,跟"main"方法效果差不多
    @Override
    public void run() {
        while (true) {
            System.out.println("hello run");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {

        MyThread1 t = new MyThread1();

        t.start();

        while (true) {
            System.out.println("hello world");
            Thread.sleep(1000);
        }
    }
}
run()方法定义了线程的任务逻辑,main()方法是Java程序进入的入口(主线程)
start()方法会创建一个新的线程,然后在新线程中执行run()方法,从而实现并发编程(新创建的线程跟main代表的主线程)。
如果直接调用run()方法,他只是在当前进程中作为一个普通方法执行,不会创建新线程

②实现Runnable(接口),重写run

java 复制代码
class MyRunnable1 implements Runnable{
    @Override
    public void run() {

        while (true) {
            System.out.println("hello run");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
}
}

public class Demo2 {

    public static void main(String[] args) throws InterruptedException {

        MyRunnable1 myRunnable = new MyRunnable1();

        Thread t = new Thread(myRunnable);

        t.start();

        while (true) {
            System.out.println("hello world");
            Thread.sleep(1000);
        }
    }

}
相当于将Runnable的实例作为Thread的构造参数,其它步骤一样

③继承Thread类,重写run,使用匿名内部类

对比一下第一种写法,这里可以理解成省略了以下内容:
java 复制代码
class MyThread1 extends Thread{
    //可以理解成自己写了Thread的"plus版本",加入了Thread原本没有的方法
    @Override
    public void run() {

        }
    }
}

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
   //创建你这个"plus版本"的实例
        MyThread1 t = new MyThread1();       
}
这样一来,相当于不用起名字,直接在里边写你想要"plus版"里的内容😎:
java 复制代码
public class Demo3 {
    public static void main(String[] args) {
        //这里的t不是指向父类Thread,指向的是Thread的一个匿名子类
        Thread t = new Thread(){
            public void run(){
                while (true){
                    System.out.println("hello Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t.start();
        //主线程
        while (true){
        System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
t大概就是这行代码的角色,只不过写到匿名内部类里边了
java 复制代码
MyThread1 t = new MyThread1();

④实现Runnable(接口),重写run,使用匿名内部类

这里可以回去对比第二种写法,并跟第三种道理一样,只是省略的内容不同罢了😁
java 复制代码
public class Demo3 {
    public static void main(String[] args) {
   
        Thread t = new Thread(new Runnable(){
            public void run(){
                while (true){
                    System.out.println("hello Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t.start();
        //主线程
        while (true){
        System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

⑤基于lambda表达式(最推荐)😆

这个按住Ctrl底层是Runnable接口和run方法,属于语法糖,非常简洁好用
java 复制代码
public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()-> {
            while (true){
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();

        while (true){
            System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
小结:以上五种写法都需要看懂,自己写可以挑一种自己喜欢的

三、Thread的其它用法

1、比较重要的两个方法:

①setDaemon(true/false)守护线程(叫后台线程贴切些)

java 复制代码
package JavaEE;

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
            while (true) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //在线程创建前设置(将它设置成后台线程)
        t.setDaemon(true);
        //线程创建
        t.start();
        //让main(唯一的前台线程)等会儿再结束
        // 因为前台线程结束整个进程就会结束
        Thread.sleep(3000);
    }
}
前台线程一旦结束,整个进程就结束了,后台线程当然也要结束,可以理解成主角跟配角
另外要注意静态类方法不用创建实例,非静态类需要创建实例调用,所以我用的是t来调用而不是Thread🙂

②isAlive()检查线程存活状态

一个线程 未执行/执行结束---------false
正在执行---------true
java 复制代码
package JavaEE;

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
            for (int i = 0;i < 3;i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("hello Thread");
            }
        });


//        线程还未创建,因此是false
        System.out.println(t.isAlive());

//        创建线程
        t.start();

//        这个等待是让main这个前台线程执行的慢一些,
//        这样一来方便获取到正在执行的那个线程的存活状态
        Thread.sleep(2000);

//        线程正在执行,因此是true
        System.out.println(t.isAlive());

//        一个线程创建出来之后默认是前台线程
//        这个等待是为了让我们创建的那个先结束再观察存活状态
        Thread.sleep(4000);

//        线程执行结束了,因此是false
        System.out.println(t.isAlive());
    }
}

2、Thread类的基本用法

①线程休眠

这里涉及到一个lambda变量捕获的点,涉及到lambda访问外部变量为什么需要"final/effectively final",必要的话可以具体搜一下
java 复制代码
package JavaEE;

import java.util.Scanner;

public class Demo8 {
    private static boolean a = true;

    public static void main(String[] args) {
    //创建一个局部变量a
    //boolean a = true;

        Thread t = new Thread(()-> {
            while (a) {
                System.out.println("hello world");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread结束");
        });

        t.start();

        //作为局部变量它不能被修改,修改了就不满足effectively final了
        //反过来说,如果局部变量没有被修改,idea会判定它是effectively final

        //修改这个变量
        //a = false;

        Scanner scanner = new Scanner(System.in);
        System.out.println("请随便输入一些东西来终止线程");
        scanner.next();

        //修改成功了!!!
        //因为他是一个成员变量/(静态变量也可以)
        a = false;
    }
}
这里想要停下来时,如果sleep()中等待的秒数还没完,则不得不将秒数读完,这就引出了下面的interrupt

另外发现的点:假如在lambda内部尝试修改会出现什么结果?😯

情况一、假如你是局部变量的情况下,写成了a = false那会出现---effectively final不满足的报错

情况二、如果你不小心写成了boolen a = false🙄,那是又创建了一个新变量

出现另一个---与main方法里创建的a重名了的报错


②线程中断

.cyrrentThread是获取当前线程的对象

.isInterrupted()是检查是否设置了标志位interrupt

java 复制代码
package JavaEE;

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(()-> {

            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
//InterruptedException是一种通信机制,不是错误,使用RuntimeException会丢失这个信息
                  //throw new RuntimeException(e);

                    //直接终止
                    break;
                }
            }
            System.out.println("线程结束");
        });

        t.start();

        System.out.println("hello main");

        Thread.sleep(1000);

        //不仅起到设置标志位的作用,同时也起到中断阻塞的作用(这里是lambda里的sleep)
        t.interrupt();
    }
}
interrupt的出现,实现了长时间运行或者阻塞的线程能够响应它并停下来
(另外有一个使用interrupt会清空标志位然后重新设置的奇怪设定...)

③线程等待

线程都可以互相等待,这个没有什么优先级
实际开发中更倾向于在join()中加个上限参数避免死等
java 复制代码
package JavaEE;

public class Demo10 {
    private static int result = 0;

    public static void main(String[] args) {

        Thread t = new Thread(()->{
            int sum = 0;
            for (int i = 1; i <= 100 ; i++) {
                sum += i;
            }
            result = sum;
        });

        t.start();

//可能会多余出来很多时间,时间不好把握
//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }

//这种处理看似可以,但不断进入循环也有开销
//        while (result == 0) {
//            try {
//                Thread.sleep(1);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//        }

        try {
            t.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        //在main方法中打印结果
        System.out.println(result);
    }
    
}
这个是获取引用的写法,顺便展示了一下新线程等待主线程
java 复制代码
package JavaEE;

public class Demo11 {
    private static int result = 0;
    public static void main(String[] args) {

        //获取线程名称
        Thread maintained = Thread.currentThread();
        System.out.println("线程名称: "+ maintained.getName());

        Thread t = new Thread(()->{
            try {
                maintained.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("计算结果: "+ result);
        });

        t.start();

        int sum = 0;
        for (int i = 1; i <= 100 ; i++) {
            sum += i;
        }
        result = sum;
    }
}
记得要获取引用再调用join()

相关推荐
孞㐑¥3 小时前
算法—链表
开发语言·c++·经验分享·笔记·算法
枷锁—sha3 小时前
【CTFshow-pwn系列】06_前置基础【pwn 035】详解:利用 SIGSEGV 信号处理机制
java·开发语言·安全·网络安全·信号处理
学嵌入式的小杨同学3 小时前
【Linux 封神之路】文件操作 + 时间编程实战:从缓冲区到时间格式化全解析
linux·c语言·开发语言·前端·数据库·算法·ux
xqqxqxxq3 小时前
结构体(Java 类)实战题解笔记(持续更新)
java·笔记·算法
林shir3 小时前
3-14-后端Web进阶(SpringBoot原理)
java·spring boot·后端
毕设源码-邱学长3 小时前
【开题答辩全过程】以 疫苗接种预约平台为例,包含答辩的问题和答案
java
虾说羊4 小时前
公平锁与非公平锁的区别与不同的使用场景
java·开发语言·spring
heartbeat..4 小时前
Redis常见问题及对应解决方案(基础+性能+持久化+高可用全场景)
java·数据库·redis·缓存
瑞雪兆丰年兮4 小时前
[从0开始学Java|第五天]Java数组
java·开发语言