Java基础 | Multithread

1.多线程概述

程序需要多个任务时候
程序需要实现一些需等待的任务[用户输入][文件读写]等等
需要一些后台运行的程序时
start方法作用,启动当前线程,调用当前线程的run()方法

优点

提高应用程序响应,增强用户体验,提高电脑CPU的利用率

改善程序结构,把复杂的进程改为多个线程,独立运行便于理解和修改

2.程序|进程|线程

程序
是用某种语言编写的一组指令集合
即一段静态的代码,静态对象

进程
程序的一次执行过程/一个正在运行的程序即是一个动态的过程
有它自身产生、存在和消亡的过程[生命周期]
电脑上的任务管理器中的一个程序就代表一个进程

线程
进程再进行细化就是线程,即一个程序内一条执行路径
若一个进程在同一时间并行执行多个线程[多线程]
例:360卫士中的每一个清理选项,就是一个线程
多个清理选项同时进行,就是多线程了

3.并发并行

并发concurrency
一个CPU同时处理多个任务,每个任务相当一个线程
给每一个线程分配一点时间,时间一到到切换另外一个线程

并行parallelism
多个CPU同时处理多个任务

4.线程声明周期

5.创建线程方式

5.1继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1:继承Thread类
class ThreadBasic extends Thread{
public static void main(String...args){
ThreadBasic tbc = new ThreadBasic();
//3:开启此线程
tbc.start();
}

//2:重写Thread方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Test extends Thread!");
}
}
}

5.2实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Demo2Thread {
public static void main(String[] args) {
//3.创建实例类对象
MyTD mt = new MyTD();
//4.因为Runnable是实现的不是继承所以要写一个Thread对象用来开启线程
Thread tr = new Thread(mt);
//5.开启线程
tr.start();
for (int j = 0; j < 40; j++) {
System.out.println("PHP not liked everything");
}
}
}
//1.实现Runnable
class MyTD implements Runnable{
//2.重写run方法
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println("Java first at world");
}
}
}

5.3实现Callable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//1.实现Callable接口
public class CallableBasic implements Callable {
@Override//2.重写call方法
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100 ;i++) {
if (i % 2 == 0){
System.out.println(i);
//1-100总和
sum += i;
}
}
//sum为int类型 自动封箱成为integer integer又是object的子类 故不会报错
return sum;
}
}
public static void normalCallable(){
//3.实例化Callable对象
CallableBasic callableBasic = new CallableBasic();

//4.实例化FutureTask对象把callable对象传入FutureTask构造器
FutureTask futureTask = new FutureTask(callableBasic);

//5.传入futureTask对象,并调用start方法开启线程
new Thread(futureTask).start();

//6/get方法用于获取call方法的返回值
try {
//回调方法
Object sum = futureTask.get();
System.out.println("总和为:"+sum);
} catch (Exception e) {
e.printStackTrace();
}
}

5.4使用Excutor线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
//存放指定数量线程
ExecutorService poor = Executors.newFixedThreadPool(2);

//存放单条线程
ExecutorService poor2 = Executors.newSingleThreadExecutor();

//execute适用于runnable submit适用于callable
poor.execute(new BubbleThread());
//FutureTask Sum = new FutureTask(new SumThread());
//匿名写法
poor.submit(new FutureTask(new SumThread()));
//关闭池子
poor.shutdown();
}

6.线程安全问题

同时满足两个条件时
1.多个线程执行的不确定性引起执行结果的不稳定性
2.多个线程在操作共享数据,会造成系统操作的不完整性,会破坏数据
3.解决方案使用synchronized关键词或lock接口

6.1同步代码块

1
2
3
4
sychronized(同步监视器){
需同步代码(即操作共享数据的代码)
包多了包少了都会出现一定的问题
}
  1. 共享数据:多个线程都要操作的变量
  2. 同步监视器:就是锁,任意对象都可充当锁
  3. 多个线程必须使用同一把锁
  4. 实现Runnable接口
    Sychronized(this)或Sychronized(new的对象名)
  5. 继承Thread类
    Sychronized(当前类名.class)或Sychronized(new的对象名)
    Sychronized(this)慎用充当同步监视器

好处
解决了线程安全的问题
弊端
操作同步代码时,只能一个线程执行,其余等待
就相当于单线程了,效率变低了(jdk1.5以后lock接口解决了此问题)

6.2同步方法

前提条件
共享数据:多个线程都要操作的变量

同步监视器:就是锁,任意对象都可充当锁

如果操作共享数据的代码刚好在一个方法里面,把此方法声明为同步的即可

  1. 同步方法仍然涉及到同步监视器,不需要我们声明
  2. 非静态同步方法默认为this
  3. 静态同步方法默认为当前类本事

6.3Lock接口

Jdk1.5开始java提供了锁对象来同步问题
同步锁使用Lock对象充当
Lock锁本身是一个接口,无法进行实例化
通常使用reentrantlock进行实例化

6.4Synchronized和Lock的区别

Synchronized

是关键字,在jvm层面,无法判断是否获取锁的状态

会自动释放锁
a 线程执行完同步代码会释放锁
b 线程执行过程中发生异常会释放锁

线程1和线程2
如果当前线程1获得锁,线程2等待
如果线程1阻塞
线程2则会一直等待下去

锁可重入、不可中断、非公平,适合代码少量的同步问题

Lock

是接口由ReentrantLock类实现,可以判断是否获取到锁

Lock需在finally中手工释放锁(unlock方法)
否则容易造成线程死锁

线程1和线程2
Lock锁就不一定会等待下去
如果尝试获取不到锁
线程可以不用一直等待就结束了

锁可重入、可中断、可公平,适合大量同步的代码的同步问题

7.线程的通讯

即线程在运行中遇到的一些状态,需要通过一些方法去解决

notify()
唤醒正在等待的单个线程(优先级高的优先)
notifyAll()
唤醒正在等待的所有线程
wait()
使得当前线程等待,直到另一个线程调用该对象notify或者notifyAll方法

sleep与wait的区别

  • sleep方法必须传入参数,参数是时间,时间到了自动醒来
  • 可以在任何需要场景下去调用
  • sleep定义在thread类,不释放锁


  • wait方法可以传入参数,也可以不传入参数
  • 只能在同步方法或者同步代码块中
  • wait定义在Object类,释放锁

8.线程的调度

Sleep

1
2
3
4
让当前线程"睡眠"指定毫秒
指定时间内当前线程是阻塞的
过了这个时间之后,线程重新执行
CPU再进行资源的分配

Yield

1
2
3
释放当前线程对CPU的执行权
暂停当前正在执行的线程对象,
把执行机会让给相同或者更高优先级的线程

Daemon

1
2
3
4
5
6
7
8
自定义对象调用setDaemon方法传入ture参数就意味着设置为守护线程
线程是程序中执行的线程,JVM允许应用程序同时执行多个执行线程。
每个线程都有优先权,优先权高的线程先执行。
每个线程可能也可能不会被标记为守护程序。

当在某个线程中运行的代码创建一个新的Thread对象时
新线程的优先级最初设置为等于创建线程的优先级
并且当且仅当创建线程是守护进程时才是守护线程。

Join

1
2
3
4
5
6
线程a在线程b中调用Join方法,此时线程b就进入堵塞状态
直至线程a加载完,线程b再进行加载

注:匿名内部在使用他所在方法中局部变量的时候必须用final修饰
join中可以写时间参数 指定的时间
时间过后 两条线程交替执行

Priority

1
2
3
4
5
6
7
8
9
一个线程即使优先级为MAX_PRIORITY
并不代表等它执行完了,才会执行另外一个线程
在运行的中间还是会出现交互的情况
setPriority方法用于设置优先继
getPriority方法用于获取优先继
设置优先级常量
MAX_PRIORITY = 10;
MIN_PRIORITY = 1;
NORM_PRIORITY = 5;

9.四种方式对比

Thread类

  1. Thread类创建线程不适合资源共享
  2. Thread类本事自己也实现了Runnable接口

Runnable接口

  1. 避免了Java中单继承的局限性
  2. 适合多条线程去处理同个资源
  3. 增强程序的健壮性,代码可被多条线程共享
  4. 代码和数据之间彼此独立
  5. 线程池中只能放入runnable或callable类线程

Callable接口(JDK1.5)

  1. 可以使用泛型
  2. 可以使用带泛型的返回值
  3. 可以抛出异常
  4. 具体实现需要借助FutureTask类

Future接口

  1. 可以对具体Runnable、Callable任务的执行结果
  2. 进行取消、查询是否、获取结果等等
  3. Futuretask是Future接口的唯一实现类
  4. FutureTask同时实现Runnable和future接口
  5. 可作为runnable被线程执行
  6. 也可作为future来获取callable的返回值使用get()方法

Excutor线程池(JDK1.5)

  1. 开发中常用,多条线程存放在Excutor中
  2. 使用时候通过方法调用,不用时候释放
  3. 提高响应速度,降低资源消耗,便于线程管理

Java基础 | Multithread
http://example.com/2022/05/01/Java初级部分/SEImprove/Multithread/
Author
John Doe
Posted on
May 1, 2022
Licensed under