Java —— 线程
1.基础概念
-
程序(program):为完成特定的任务,选用某一种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
-
进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的、进程是动态的。
-
进程是操作系统调度和分配资源的最小单位。
-
注意:不同的进程之间是不共享内存的、进程之间的数据交换和通信的成本很高。
-
-
线程(thread):进程可以进一步细化为线程,是运行中进程的一条或多条执行路径。
- 线程作为CPU调度和执行的最小单位。
-
线程调度策略
- 分时调度:所有线程轮流使用CPU的使用权,并且平均分配每个线程占用CPU的时间
- 抢占式调度:让优先级高的线程以较大的概率优先使用CPU。如果线程的优先级相同,那么会随机选择一个(线程随机性)。
- Java使用的抢占式调度
-
单核CPU与多核CPU
- 单核CPU:在一个时间单元内,只能执行一个线程的任务。
- 多核CPU:多加几个单核CPU,即多核的CPU。
- 多核的两方面损耗:
- 一个是多个核心的其他共用资源限制。
- 另一个是多核CPU之间的协调管理损耗。
- 多核的两方面损耗:
-
并行与并发
- 并行:指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个CPU上同时执行。
比如:多个人同时做不同的事。 - 并发:指两个或多个事件在同一个时间段内发生。即在一段时间内,有多条指令在单个CPU上快速轮换、交替执行,使得在宏观上具有多个进程同时执行的效果。
- 并行:指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个CPU上同时执行。
2.如何创建多线程(重点理解)
- 方式1:继承Thread类
- 方式2:实现Runnable接口
- 方式3:实现Callable接口(jdk5.0新增)
- 方式4:使用线程池(jdk5.0新增)
3.Thread类的常用方法、线程的生命周期
-
线程中的构造器
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新的线程对象。
- public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
-
线程中的常用方法:
- start():①启动线程、②调用线程的run()
- run():将线程要执行的操作,声明在run()中
- currentThread():获取当前执行代码对应的线程
- getName():获取线程名
- setName():设置线程名
- sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
- yield():一旦执行此方法,就释放CPU的执行权
- join():在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
- isAlive():判断当前线程是否存活
- stop():强制结束一个线程的执行,直接进入死亡状态,导致run()有未执行结束的代码。stop()会导致释放同步监视器,导致线程安全问题。(过时方法:不建议使用)
- void suspend() / void resume() :可能造成死锁,(过时方法:不建议使用)
-
线程的优先级:
- getPriority():获取线程的优先级
- setPriority():设置线程的优先级
- Thread类内部声明的三个常量
- MAX_PRIORITY(10):最高优先级(CPU执行概率较大)
- MIN_PRIORITY(1):最低优先级
- NORM_PRIORITY(5):普通优先级,默认情况下main线程具有普通优先级
-
线程的生命周期
- New(新建) – Runnable(运行,分为准备+运行) – Terminated(死亡)
- 运行阶段:Blocked(锁阻塞) + Waiting(无限等待) + Timed_Waiting(计时等待)
4.如何解决线程安全问题(同步机制)
-
什么是线程安全问题:多个线程操作共享数据,就有可能出现安全问题
-
解决安全问题的几种方式
-
同步机制(synchronized):①同步代码块 ②同步方法
-
在实现Runnable接口的方式中,同步监视器可以考虑使用this。
-
在继承Thread类的方式中,同步监视器要慎用this(可能存在多个对象),建议使用:当前类.class。
-
非静态的同步方法,默认同步监视器是this
-
静态的同步方法,默认同步监视器是当前类
-
-
jdk5.0新增:Lock接口及其实现类
-
5.同步机制相关的问题
-
懒汉式的线程安全的写法
public class BankTest { static Bank b1 = null; static Bank b2 = null; public static void main(String[] args) { Thread t1 = new Thread(){ @Override public void run() { b1 = Bank.getInstance(); } }; Thread t2 = new Thread(){ @Override public void run() { b2 = Bank.getInstance(); } }; t1.start(); t2.start(); try { t1.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } try { t2.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(b1); System.out.println(b2); System.out.println(b1 == b2); } } class Bank{ private Bank(){} private static volatile Bank instance = null; // volatile避免指令重排 //实现线程安全的方式1 // public static synchronized Bank getInstance() { //同步监视器默认为Bank.class // if (instance == null){ // // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // throw new RuntimeException(e); // } // // instance = new Bank(); // } // return instance; // } // //实现线程安全的方式2 // public static Bank getInstance() { //同步监视器默认为Bank.class // synchronized (Bank.class) { // if (instance == null){ // // try { // Thread.sleep(1000); // } catch (InterruptedException e) { // throw new RuntimeException(e); // } // // instance = new Bank(); // } // } // return instance; // } //实现线程安全的方式3:相较于1和2,优化,效率更高。为了避免出现指令重排,需要将instance声明为volatile public static Bank getInstance() { //同步监视器默认为Bank.class if (instance == null) { //instance为null,直接return,效率更高 synchronized (Bank.class) { if (instance == null) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } instance = new Bank(); } } } return instance; } }
-
同步机制会带来的问题:死锁
-
如何看待死锁?
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了现成的死锁。(双方需要的资源都被占用,也不打算释放,就形成了死锁),编写程序时,要避免死锁。
-
诱发死锁的原因 及 如何避免死锁?
- 互斥条件 :互斥条件基本上无法被破坏。因为线程需要通过互斥解决线程安全问题。
- 占用且等待 :可以考虑一次性申请所有的资源,这样就不存在等待的问题。
- 不可抢夺(或不可抢占):占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放已经占用的资源
- 循环等待 :可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。
-
6.线程间的通信
-
线程间通信的理解
- 当需要多个线程来共同完成一件任务时,我们希望其有规律的执行,那么多谢线程之间需要一些同行机制,可以协调它们的工作,以此实现多线程共同操作同一份数据
-
涉及到三个方法的使用:
- wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用
- notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的一个线程(若多个线程的优先级相同,则随机唤醒一个)。
被唤醒的线程从当初被wait()的位置继续执行。 - notifyAll():一旦执行此方法,就会唤醒所有被wait()的线程。
-
注意点:
- 上述三个方法必须在(synchronized)同步代码块或同步方法中使用。
(超纲:Lock需要配合Condition实现线程间的通信) - 此三个方法的调用者,必须是同步监视器。否则会报IllegalMonitorStateException异常
- 此三个方法声明在Object类中。
- 上述三个方法必须在(synchronized)同步代码块或同步方法中使用。
-
wait() 和 sleep()的区别?(高频面试题)
- 相同点:一旦执行,当前线程都会进入阻塞状态
- 不同点:
- 声明的位置:
wait():声明在Object中
sleep():声明在Thread类中,是静态的 - 使用的场景不同:
wait():只能使用在同步代码块中或同步方法中
sleep():可以在任何需要使用的场景 - 使用在同步代码块或同步方法中:
wait():一旦执行,会释放同步监视器
sleep():一旦执行,不会释放同步监视器 - 结束阻塞的方式:
wait():到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞
sleep():到达指定时间自动结束阻塞
- 声明的位置: