进程
-
基本概念
程序 - 数据结构 + 算法,主要指存放在硬盘上的可执行文件。
进程 - 主要指运行在内存中的程序。
目前主流的操作系统都支持多进程,为了使得操作系统能够同时执行多个任务,但进程是重量级的,新建进程对系统的资源消耗比较大,因此进程的数量比较局限。
线程是进程内部的程序流,也就是操作系统中支持多进程,而每个进程的内部又可以支持多线程,线程是轻量级的,新建线程会共享所在进程的系统资源,因此以后的开发中都采用多线程技术。
多线程技术采用时间片轮转法实现并发执行,所谓并发就是指宏观并行微观串行。
线程的创建
-
创建和启动的方式
java.lang.Thread类用于描述线程,Java 虚拟机允许应用程序并发地运行多个执行线程,而线程的创建和启动方式如下:
- 自定义类继承Thread类并重写run方法,创建该类的实例调用start方法。
- 自定义类实现Runnable接口并重写run方法,创建该类的实例作为实参来构造 Thread类型的对象,然后使用Thread类型的对象调用start方法。
-
相关方法的解析
Thread() - 使用无参方式构造对象。
Thread(String name) - 根据参数指定的名称来构造对象。
Thread(Runnable target) - 根据参数指定的接口引用来构造对象。
Thread(Runnable target, String name) - 根据参数指定的引用和名称构造对象。void run() - 若使用Runnable类型的引用构造出来的对象调用该方法,则最终调用引用所指向对象的run方法,否则调用该方法啥也不做。
void start() - 用于启动线程,Java虚拟机会自动调用该线程的run方法。 -
原理分析
- 执行main方法的线程叫做主线程,执行run方法的线程叫做子线程。
- main方法是程序的入口,最开始只有主线程来依次执行main方法中的代码,当start方法调用成功后,线程的个数瞬间由1个变成了2个,其中子线程去执行run方法,主线程继续执行main方法的代码,两个线程各自独立运行互不影响。
- 当run方法执行完毕后子线程结束,当main方法执行完毕后主线程结束,但两个线程谁先执行没有明确的规定,取决于操作系统的调度算法。
注意:
线程创建和启动的方式一相对来说代码简单,但Java语言中只支持单继承,若该类继承Thread类后则无法继承其它类;而方式二相对来说代码复杂,但不影响该类继承其它类以及实现其它接口,因此以后开发中推荐方式二。
线程的同步机制
-
基本概念
当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要进行线程之间的通信和协调,该机制就叫线程的同步机制。
例:2003年 存折 和 银行卡 对应同一个账户
-
解决方案
由程序结果可知:当两个线程同时进行取款时,导致最终的账户余额不合理。
引发原因:线程一还没有执行完毕取款操作,此时线程二已经开始取款操作。
解决方案:让两个线程的并发操作改为串行操作即可,也就是依次执行取款操作。
方案的缺点:若依次启动多个线程则导致多线程的意义不复存在。 -
实现方式
在Java语言中使用synchronized关键字来实现同步/对象锁机制,来保证线程执行该段代码时的原子性(要么不执行,要么就执行完整),具体方式如下:
-
使用同步语句块的方式来锁定部分代码;
synchronized(任意类型的引用){ 编写需要锁定的代码; }
-
使用同步方法的方式来锁定所有代码;
-
-
原理分析
当多个线程调用start方法后同时去抢占共享资源,由于同步锁的存在导致只有一个线程能够抢到共享资源并进行加锁处理,其它没有抢到共享资源的线程进入阻塞状态,当该线程执行完毕所有锁定的代码后自动释放同步锁,此时阻塞状态的所有线程继续抢占共享资源,抢不到的线程再次回到阻塞状态。
-
死锁的概念
线程一执行的代码:
public void run() { synchronized(a) { //持有同步锁a,等待同步锁b synchronized(b) { ... } } }
线程二执行的代码:
public void run() { synchronized(b) { //持有同步锁b,等待同步锁a synchronized(a) { ... } } }
经验分享:在以后的开发中尽量少使用同步代码块的嵌套结构!