20220806 第六组 张嘉源 学习笔记一
多线程
线程是进程的一个执行单位,一个进程包含一个或者多个线程,一个线程不能独立的存在,它必须是进程的一部分
并发:指多个事件在同一时间段内一起发生
并行:指多个事件在同一时刻同时发生
线程的生命周期
从产生到死亡的过程
NEW:这个状态主要是线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
BLOCKED:阻塞。因为某些原因不能立即执行需要挂起等待。
WAITING:无限期等待。Object类。如果没有唤醒,则一直等待。
TIMED_WAITING:有限期等待,线程等待一个指定的时间
TERMINATED:终止线程的状态,线程已经执行完毕。
线程的优先级
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
创建线程
1.继承Thread类
创建一个新的类,该类继承Thread类,然后创建一个该类的实例
继承类必须重写run()方法,必须调用start()方法执行
class MyThread extends Thread {
@Override
public void run() {
System.out.println(2);
}
}
public class Ch01 {
public static void main(String[] args) {
System.out.println(1);
MyThread myThread = new MyThread();
myThread.start();
System.out.println(3);
System.out.println(4);
}
}
2.实现Runnable接口
class MyThread implements Runnable{
// 重写run方法
@Override
public void run() {
System.out.println(2);
}
}
public class Ch01 {
public static void main(String[] args) {
System.out.println(1);
MyThread myThread=new MyThread();
// 多态
Thread t=new Thread(myThread);
t.start();
System.out.println(3);
System.out.println(4);
// 输出顺序为:1342
}
}
3.通过Callable和Future创建线程
创建Callable接口的实现类,并实现call()方法,该call()方法有返回值
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 实现Callable接口
class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(2);
return "call方法的返回值";
}
}
public class Ch04 {
public static void main(String[] args) {
System.out.println(1);
// Callable-->FutureTask-->RunnableFuture-->Runnable-->Thread
FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
new Thread(futureTask).start();
System.out.println(3);
System.out.println(4);
}
}
区别
Runnable和Callable的异同:
相同:都是接口,都可以写多线程,都调用start()方法
区别:
1.实现Callable接口能返回结果,实现Runnable接口的线程不能返回结果
2.Callable接口的call方法允许抛异常,Runnable的run方法不能抛异常
3.实现Callable接口的线程可调用Future.cancel()取消执行,而Runnable接口线程不能
常用方法
Object类
wait()方法:使当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
notify()方法:唤醒正在等待对象监视器的单个线程
notifyAll()方法:唤醒正在等待对象监视器的所有线程
sleep()方法:使当前线程睡眠一会,让其他线程有机会继续执行,让线程从运行状态变为阻塞状态,方法调用结束后,线程从阻塞状态变为可执行状态,以毫秒为单位,需要捕捉异常
join()方法:把指定的线程加入到当前线程,可以将两个交替执行线程合并为顺序执行的线程,比如在主线程中调用了线程A的join()方法,线程A执行完毕后,才会继续执行线程B
Thread类
start():启动当前线程,执行run方法
run():创建线程后重写run方法
currentThread:静态方法,获取当前正在执行的线程
getId():返回此线程的唯一标识
setName(String):设置当前线程的name
getName():获取当前线程的name
getPriority():获取当前线程的优先级
setPriority(int):设置当前线程的优先级
getState():获取当前线程的声明周期
interrupt():中断线程的执行
interrupted():查看当前线程是否中断
yield():释放当前CPU资源
区别
sleep()和wait()区别:
1.sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用
2.sleep不会释放锁,也不需要占用锁,wait会释放锁,单调用它的前提是当前线程占有锁
3.都可以被interrupted方法中断
线程调度
常量:
常量名 | 备注 |
---|---|
static int MAX_PRIORITY | 最高优先级(10) |
static int MIN_PRIORITY | 最低优先级(1) |
static int NORM_PRIORITY | 默认优先级(5) |
方法:
方法名 | 作用 |
---|---|
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
- main线程的默认优先级是:5
- 优先级较高的,只是抢到的CPU时间片相对多一些。大概率方向更偏向于优先级比较高的。
加锁
三种加锁方式:
修饰实例方法,作用与当前实例加锁,进入同步代码前要或得当前实例的锁
修饰静态方法,作用于当前类加锁,进入同步代码前要获得的当前类对象的锁
代码块,指定加锁对象,进入同步代码块之前要获得给定对象的锁
实例方法:调用该方法的实例
静态方法:类对象
this:调用该方法的实例对象
类对象:类对象
线程同步
java允许多线程并发控制,当锁哥线程同时操作一个可共享的资源变量时,会导致数据不准确,相互之间产生冲突,因此加锁避免在该线程没有完成操作之前被其他线程调用。
共享数据
多个线程共同操作的变量,可以充当锁
volatile实现线程同步
(1)volatile关键字为域变量的访问提供了一种免锁机制;
(2)使用volatile修饰域,相当于告诉JVM,该域可能会被其它线程更新,因此每次使用该域时就要重新计算,而不是使用寄存器中的值;
(3)volatile不会提供任何原子操作,也不能用来修饰final类型的变量。
注:关于Lock对象和synchronized关键字的选择
(1)如果synchronized能满足用户需求,就用synchronized,因为它能简化代码;
(2)如果需要更高级的功能,就用ReentrantLock,此时需要注意及时释放锁,否则会出现死锁,通常在finally中释放锁。
线程安全
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
1.数据不可变:java中一切不可变的对象一定是线程安全的
对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施
比如:final关键字修饰的基本数据类型,字符串
只要一个不可变的对象被正确的创建出来,那外部的可见状态永远都不会改变
2.互斥同步:加锁,悲观锁
3.非阻塞同步:无锁编程,自旋
4.无同步方案:多个线程需要共享数据,但是这些数据又可以在单独的线程中计算,得出结果
可以把共享数据的可见范围限制在一个线程之内,这样就无需同步,把共享的数据拿过来
你用你的,我用我的,从而保证线程安全
解决方法
1.同步代码块
Synchronized关键字
控制线程同步,确保数据的完整性,一般加在方法上
public synchronized void save(){}
synchronized(object) {
//需要同步的代码块
}
当使用同步方法时,synchronized锁的东西是this(默认的)(谁调的锁谁,锁对象调用的方法)
1.选好同步监视器(锁)推荐使用类对象,第三方对象,this
2.在实现接口创建的线程类中,同步代码块不可以用this来充当同步锁
2.同步方法
有synchronized关键字修饰的方法
public synchronize void method{
//要写的方法的代码
}
由于Java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法之前,需要获取内置锁,否则就处于阻塞状态。
3.同步锁
Lock锁:一个接口,Lock接口的实现类ReentrantLock可重入锁
方法:
public void lock(): 加同步锁
public void unlock():释放同步锁
区别
Lock和synchronize的区别:
1.synchronize是关键字,Lock是一个Java类
2.synchronize无法判断是否获取锁的状态,Lock可以判断是否获取到锁
3.synchronize可以自动释放锁,Lock需要在finally中手动释放锁(因此最佳实践是执行 lock() 后,首先在 try{} 中操作同步资源,如果有必要就用 catch{} 块捕获异常,然后在 finally{} 中释放锁,以保证发生异常时锁一定被释放)
4.synchronize如果修饰线程1和线程2 ,如果当前是线程1获得锁,线程2等待,如果线程1 阻塞状态,线程2会一直等待下去。而Lock锁不一定会等待下去,如果尝试获取不到锁,线 程可以不同一直等待下去
5.Lock锁适合大量同步代码的同步问题,synchronize锁适合代码少量的同步问题
阻塞线程
LockSupport :线程阻塞的工具类,其中所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有唤醒的办法
方法:
park():阻塞
unpark():释放
区别:
1.park不需要获取某个对象的锁(不释放锁)
2.因为中断park不会抛出InterruptedException异常,需要在park之后自行判断中断状态,然后做额外的处理。
总结:
1.park和unpark可以实现wait和notify的功能,但是并不和wait和notify交叉使用。
2.park和unpark不会出现死锁。
3.blocker的作用看到阻塞信息的对象
守护线程
java中提供两种类型的线程:
用户线程:我们平常创建的普通线程
守护线程:用来服务用户线程,我么也可以手动创建一个守护线程
当存在任意一个用户线程的时候,JVM就不会退出
在调用start()方法前,调用setDaemon(true)
把该线程标记为守护线程。
如何检查一个线程是守护线程还是用户线程:使用isDaemon()
方法。
- thread.setDaemon(true) 必须在 thread.start() 之前设置,否则会抛出
IllegalThreadStateException
异常。 - 在Daemon线程中产生的新线程也是Daemon的。
JVM 中的垃圾回收线程就是典型的守护线程
终止线程
1.强行终止:stop()方法,已过时,不推荐使用
2.打一个布尔标记:
class MyThread extends Thread {
volatile boolean flag = true;
@Override
public void run() {
while(flag) {
try {
System.out.println("线程一直在运行...");
int i = 10 / 0;
} catch (Exception e) {
this.stopThread();
}
}
}
public void stopThread() {
System.out.println("线程停止运行...");
this.flag = false;
}
}
public class Ch03 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
3.interrupt方法
调用interrupt方法会抛出InterruptedException异常,捕获后再做停止线程的逻辑即可。
如果线程while(true)运行的状态,interrupt方法无法中断线程。
class MyThread02 extends Thread {
private boolean flag = true;
@Override
public void run() {
while(flag) {
synchronized (this){
// try {
// wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
try {
sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
this.stopThread();
}
}
}
}
public void stopThread() {
System.out.println("线程停止运行...");
this.flag = false;
}
}
public class Ch04 {
// public void show() {
// try {
// wait();
// // 线程异常终止 异常
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
public static void main(String[] args) {
MyThread02 myThread02 = new MyThread02();
myThread02.start();
System.out.println("线程开始...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程的执行
myThread02.interrupt();
}
}
死锁
死锁条件
1.互斥:当资源被一个线程使用(占有),别的线程不能使用,如果另一资源申请,那么申请进程应等到该资源释放为止
2.不可抢占:资源不能被抢占,资源只能被进程在完成任务后释放
3.请求和保持:一个进程占有一个资源,并同时申请另一个资源,而该资源为其他进程所占有
4.循环等待:存在一个循环等待队列,p1占有p2的资源,p2占有p3的资源,p3占有p1的资源,形成了一个等待环路
线程重入
任意线程在拿到锁之后,再次获取该锁不会被该锁所阻碍,线程是不会被自己锁死的,synchronized可重入锁。
线程池
好处:
1.降低资源消耗,通过重复利用已创建的线程来降低创建和销毁造成的资源消耗
2.提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性,线程比较稀缺的资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
JDK自带的四种线程池通过Executors提供的。
1.newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
若无可回收,创建新线程。
2.newFixedThreadPool:创建一个定长的线程池,可以控制线程最大并发数,超出的线程会在队列中等待。
3.newScheduledThreadPool:创建一个定长的线程池,支持定时及周期性任务执行
4.newSinaleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证
所有的任务按照指定顺字执行
这四种线程池的初始化都调用了同一个构造器
ThreadPoolExecutor(int corePoolsize,
int maximumPoolsize long keepAliveTime, TimeUnit unit,
BlockingQueue
RejectedExecutionHandler handler)
参数的意义(重要):
corePoolsize:线程池里线程的数量,核心线程池大小
maximumPoolsize:指定了线程池里的最大线程数量
keepAliveTime:当线程池线程数量大于corePoolsize,多出来的空闲线程,多长时间被销毁
unit:时间单位
workQuene:任务队列,用于存放提交但是尚未被执行的任务
threadFactory:线程工厂,用来创建线程,线程工厂就是我们new线程的
handler:拒绝策略,是将任务添加到线程池中时,线程池拒绝该任务多采取的相应的措施。
常见的工作队列
ArrayBlockingQueue:基于数组的有界阻塞队列。FIFO。
LinkedBlockingQueue:基于链表的有界阻塞队列。FIFO
线程池提供了四种拒绝策略:
AbortPolicy:直接抛出异常,默认的策略。
CallerRunPolicy:用调用者所在的线程来执行任务
DiscardoldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务
Discardpolicy:直接丢弃任务
创建线程的4种方式
线程同步(synchronized,ReentrantLock,ReentrantReadwriteLock)
线程之间的通信(wait,notify,notifyAlL)
线程类的常用方法