多线程相关知识
目录
前言
本文只是涉及到多线程的基本知识,未对线程池,并发做研究。
相关参博客和视频:
https://www.jianshu.com/p/512ab59ee584
https://www.cnblogs.com/hongmoshui/p/11109982.html
https://www.cnblogs.com/yangyongjie/p/11024712.html
https://blog.csdn.net/qq_36711757/article/details/82384356
https://www.bilibili.com/video/BV1V4411p7EF?from=search&seid=14826241534626431382
一. 进程与线程的简单理解
一个程序就是一个进程,系统运行一个程序即使一个进程从创建,运行到消亡的过程。而一个程序中的多个任务则被称为线程。
进程表示资源分配的基本单位,线程是进程执行运算的最小单位,也是调度运行的基本单位。
二. 线程调度
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:优先级高的线程先使用CPU,如果优先级相同则随机选择一个。
三. 创建线程的方式
继承Thread类创建线程类
通过Runnable接口创建线程类
通过Callable和Future创建线程(不常用,少见)。
一般就是通过继承Thread类或者调用Runnable接口来重写run()方法实现线程,调用start()方法启动线程。
四. 用Runnable还是Thread?
一般建议使用Runnable接口,因为java是单继承的,实现Runnable接口可以避免这个单继承的局限性
五. start()方法和run()方法的区别
这也是面试常见问题,涉及到对java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法时,只会在原来的线程中调用,没有开启新的线程,只有shart()方法才会启动新线程。
六. Thread的实现涉及到哪种设计模式
源代码中Thread的实现是静态代理。静态代理就是比如,你结婚,你会和新娘宣读誓言,但是除了这,还有布置现场,主持流程这些都是由婚庆公司来做,婚庆公司就是代理,来帮你处理结婚的事情。
而线程也是类似的,比如我们自己写个抢票的线程,抢票这个类会实现Runnable接口,同时Thread类也是实现Runnable接口的,然后回调用Thread.start()来帮忙抢票,这个Thread就是代理,相当于婚庆公司。具体理解可以在以后写线程代码时细细体会。
七. 线程状态
1,New初始状态:线程对象被创建后,就进入了新建状。例如,Thread thread=new Thread()。
2,Runnable就绪状态:线程对象被创建后,该对象的start()方法被调用了,该线程启动,处于就绪状态,随时可被CPU调度执行。并不是调用start()方法后马上线程就运行了,要等cpu来调用了,才会开始运行。
3,Running运行状态:先后获取CPU权限进行执行,需要注意的是,线程只能从就绪状态进入到与性能状态。
4,阻塞状态:阻塞状态时线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到阻塞状态。阻塞的情况分三种:
1) 等待阻塞—通过调用线程wait()方法,让线程等待某工作的完成。
2)同步阻塞—线程在获取synchronized同步锁失败(因为锁被其他线程所占用),它会进入阻塞状态。
3)其他阻塞—通过调用线程sleep()或者join()或发出了I/O请求时,线程会进入阻塞状态。当sleep()状态超时,join()等待线程中止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5,Dead死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
八. 线程相关方法
8.1 相关方法
sleep()方法:在指定的毫秒数内让当前正在执行的线程休眠。线程休眠不会释放锁。抱着锁睡觉
join()方法:简单理解就是插队,vip,先完成这个线程,再执行其他线程,其他线程阻塞。
setPriority:设置线程优先级,优先级高的,被cpu调用的可能性增大(只是可能性大)。
yield()方法:线程礼让,让当先正在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态,让cpu重新调用。礼让也是不一定成功的,看cpu心情。
线程停止方法:不推荐使用JDK提供的stop()、destory()方法,推荐让线程自己停止下来。一般使用一个标志位来帮忙中止线程(while(flag)…)。
wait()方法,notify()方法:wait()方法的作用是让需要锁的线程等待,直到其他线程调用notify()或着notifyAll()唤醒方法。与sleep()方法的不同是,sleep()方法睡眠时,保持对象锁,仍然占有该锁,而wait()睡眠时,释放对象锁。
8.2 例子
/*
经典面试题:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。
这个问题用Object的wait(),notify(),sleep()就可以很方便的解决
*/
class Thread1 implements Runnable {
private String name;
private Object prev;
private Object self;
private Thread1(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
Thread1 pa = new Thread1("A", c, a);
Thread1 pb = new Thread1("B", a, b);
Thread1 pc = new Thread1("C", b, c);
new Thread(pa).start();
Thread.sleep(100); //确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
/*结果:ABCABCABCABCABCABCABCABCABCABC*/
九. 线程安全与线程同步
当我们使用多个线程访问同一资源的时候,且多个线程对资源有写的操作,那么就容易出现线程安全问题。在Java中为解决线程提供了同步机制。
线程安全的核心理念是“要么只读,要么加锁”
计算机的线程同步,是指线程之间按照某种机制协调先后执行次序,即当有一个线程再对内存操作时,其他线程都不可以对这个内存操作,一直等待直到该线程完成操作,其他线程才能对该内存进行操作。
实现线程同步的方式有很多,比如同步方法,锁,阻塞队列等。
十. 线程同步的方法
10.1 同步代码块
synchronized(['sɪŋkrənaɪzd] 同步)关键字可以用于方法中的某个区块,表示只对这个区块的资源实行互斥访问。
synchronized(Obj){
需要同步操作的代码
}
//这个Obj可以是任何对象,但是推荐共享资源作为Obj
可以理解为给对象上了个锁。多个线程对象,要使用同一把锁,任何时候,最多允许一个线程拥有同步锁,谁拿到锁谁就进入代码块,其他的线程只能在外等待。
在多线称竞争情况下,加锁,释放锁会导致比较多的上下文切换和调度演示等问题,性能下降。
10.2 同步方法
使用synchronized修饰的方法就叫做同步方法,保证A线程在执行的时候,其他线程只能在外面等着。
方法里面又需要修改的内容才需要锁,锁的太多也容易浪费资源。
10.3 Lock锁
JDK5.0后,提供了更加强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
10.3 synchronized与Lock锁对比
Lock是显式锁,需要手动的开启和关闭,synchronized是隐式锁,出了作用域就自动释放。
Lock只有代码块锁,synchronized有代码块和方法锁。
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性。
优先使用顺序:Lock> 同步代码块 >同步方法。
十一. 死锁
多个线程同时各自占有一些资源,并且相互等待着其他线程占有的资源才能允许,而导致两个或者多个线程都在等待着对方释放资源,都停止执行的情况。
那么如何避免死锁呢:死锁的产生有四个必要条件:互斥条件,请求与保持条件,不剥夺条件,循环等待条件。只要破环任何一个就可以。比如我们可以指定获取锁的顺序。
十二. 线程通信
具体的没搞太明白,涉及到了线程池,生产者消费者模式(这不属于设计模式的一种,只是一种问题类型的描述)