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;
}
}

