-
在学习多线程之前需要了解一个重要的概念,什么是进程!?
举个栗子:关键词: 工厂 cpu --> 车间(进程)–> 工人(线程)
假设一个工厂的能源有限,只能供一个车间工作,这个车间工作的时候,其他车间停止生产。一个车间里有很多工人,他们一起协同完成一个任务。车间里的空间是工人可以共享的,每个人都可以使用车间里的资源。有些资源空间很大,大到可以让固定数量的个工人进行使用,有些空间资源很小,小到只能让一个人使用,其他人得等到使用完,才能进入。
- 空间小的资源怎么防止他人进入呢!?
解释:一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫“互斥锁”(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
- 固定数量的工人怎么能确定数量呢!?
解释 就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做“信号量”(Semaphore),用来保证多个线程不会互相冲突。 -
什么是进程!?:是操作系统进行资源分配和调度的一个独立单位,好比正在运行的程序,打开一个软件,就会在任务管理器中增加一个进程。它会占用对应的内存区域,由CPU进行执行与计算。
参考:Windows 中任务管理器(查看相应程序的进程)。
多进程,允许多个任务同时运行。
多线程,允许单个任务分成不同的部分运行。
注:它们之间有协调机制,确保二者不会冲突,又能保证资源共享。到底是依赖一个进程还是多个进程是由软件开发时决定的。
-
并发性:
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响。(例如:如google浏览器,在windows中可以看到有多个进程) -
动态性:
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加 入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的. -
独立性:
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
-
线程 是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
-
一个进程可以开启一个线程去完成任务,也可以开启多个。注意:但其中肯定有一条主线程,执行程序,并调度资源,进行具体操作。如:java中main()方法的入口。用户在次创建的可以称之为子线程。
-
多线程还需要基于 软件和硬件的支持,提升的性能处理。
举个栗子: 关键词 人
宏观上来说:人从出生就开始运行直到人生的尽头。在运行期间人体,需要在这个有限的资源中进行工作,如,呼吸,血液循环,思考,学习… 相当于 “多个线程工作”。
- 单线程,就好像上面工厂中的工人一样,是对代码具体执行的。当分配给这个线程一个任务时,只有当这个线程完成了任务,才会对下一个任务有所响应。 执行期间是对其他任务没有任何响应的。
- 咱们白话解释下区别:
- 单线程,当执行任务的主线程按照执行流程执行任务,等一个动作完成后,才能执行下一个代码,主线程在等待期间,用户界面看起来就是“忙碌状态”。
- 多线程, 将原本的任务分成了很多个子任务去执行。这样防止了“阻塞”,一个线程在焖米饭期间,cpu会分给另一个线程时间片,让去执行下一个任务。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务,提高了程序的执行效率。
1.多线程特性,线程执行有随机性 cpu选择线程随机分配的。
首先要明确一件事,只有cpu发出指令的时候,线程得到cup授权,才能使用资源执行任务。但是注意: 多线程工作,其中并发运行( 指两个或多个事件在同一个时间段内发生),在宏观上看似同时执行,实际上是各个线程轮流获取 CPU 使用权才能执行指令。
- cpu有自己的底层分配机制。java 默认抢占式的调度模型。
- 分时调度模型:是指让所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用的CPU时间片(可以理解为cpu分给的线程的时间段)。但是,Java 中的线程调度不是分时的,同时启动多个线程后,并不能保证各个线程轮流获取均等的时间片。
- 抢占式调度模型:使优先级高的线程获取更多的运行机会来占用cpu更好的资源, 如果可运行池种的线程优先级相同,则随机选中一个线程去使用,当它失去了CPU的使用权后,再随机选择其他线程获取CPU试用权。Java使用的为CPU使用抢占式调度模式在多个线程间进行着高速的切换,让CPU的使用率更高。
- 并发和并行在多线程技术上是绕不开的词汇,首先,都利用多线程技术。其次,目的都是提高CPU的使用率。
- 概念上区别:
并行:强调一个处理器同时处理多个任务,并不是正在同时运行。(容易高并发)
并发:强调多个处理器或者是多核的处理器同时处理多个不同的任务,同时运行多段代码。
- 场景区别:
- io场景: 查询数据库,io的操作等,数量大单是不消耗cpu资源,可以使用并发技术。
- 计算场景: 大量的数据计算,加减乘除,数据加密等,需要大量占用cpu资源的,使用cpu并行技术。
- 也可以当做多线程的生命周期。
线程生命周期,总共有五种状态:
-
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
-
就绪状态(Runnable):也被称为“可执行状态”,当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
-
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中; -
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
4.1 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
4.2 同步阻塞 :线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
4.3 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 -
死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
- Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
- 启动线程的唯一方法就是通过Thread类的 start() 实例方法,start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run()
- 不要用run()方法去启动线程 如果用run()会有什么效果!?
Thread() 分配新的Thread对象 Thread(String name) 分配新的Thread对象 Thread(Runnable target) 分配新的Thread对象 Thread(Runnable target,String name) 分配新的Thread对象5.3 常用方法
| 方法 | 作用 |
|---|---|
| static Thread currentThread( ) | 返回对当前正在执行的线程对象的引用 |
| long getId() | 返回该线程的标识 |
| String getName() | 返回该线程的名称 |
| void run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法 |
| static void sleep(long millions) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
| void start() | 使该线程开始执行:Java虚拟机调用该线程的run() |
- run() 方法启动和start()启动有什么区别!?
public class Test_Thread {
public static void main(String[] args) {
MyThread t = new MyThread();
//t.run(); // 虽然调用run()也能启动但并不是真正的启动方法。
t.start(); // 单个调用只是相当于单线程。
MyThread t1 = new MyThread("张楚岚"); //通过含参构造设置名称
MyThread t2 = new MyThread();
t2.setName("宝儿姐");// 通过方法设置线程名称
t1.start();
t2.start();
}
}
class MyThread extends Thread{
// 空参构造方法,不用写默认存在
public MyThread() {
super();
}
//含参构造方法
public MyThread(String name) {
super(name);
}
//重写run()方法;
@Override
public void run() {
//写一个循环20次的任务
for (int i = 0; i <5 ; i++) {
System.out.println(getName()+" : "+i);//执行线程+输出结果
}
}
}
输出结果: 注意不固定!
张楚岚 : 0 宝儿姐 : 0 Thread-0 : 0 宝儿姐 : 1 张楚岚 : 1 张楚岚 : 2 宝儿姐 : 2 Thread-0 : 1 Thread-0 : 2 Thread-0 : 3 Thread-0 : 4 宝儿姐 : 3 张楚岚 : 3 宝儿姐 : 4 张楚岚 : 46 多线程创建方式二: 实现Runnable接口 6.1 常用方法
| 方法 | 作用 |
|---|---|
| void run() | 当实现接口的对象 Runnable被用来创建一个线程,启动线程使对象的 run在独立执行的线程中调用的方法。 |
- 创建多线程的方式有两种,哪种更好一些!?
public class Test_Runnable {
public static void main(String[] args) {
//1.创建对象
MyRunnable myRunnable = new MyRunnable();
//没有start()方法怎么启动!?看构造方法找实现类Thread
Thread t = new Thread(myRunnable); //通过Thread含参数构造方法。
t.start(); //通过Thread 嗲用start() 简写 new Thread(myRunnable).start();
//模拟多线程
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable,"钢铁侠"); //通过构造方法设置线程名称
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable{
//重写run()方法
@Override
public void run() {
//写一个循环的业务
for (int i = 0; i <5 ; i++) {
//线程名称通过谁来获取,Runnable只有一个run()方法!?拿到Thread即可通过Thread静态方法。
System.out.println(Thread.currentThread().getName()+"="+i);
}
}
}
输出结果:结果不固定
Thread-0=0 钢铁侠=0 Thread-1=0 钢铁侠=1 Thread-0=1 钢铁侠=2 Thread-1=1 钢铁侠=3 Thread-0=2 钢铁侠=4 Thread-1=2 Thread-0=3 Thread-1=3 Thread-0=4 Thread-1=46.3 简述一下Thread和Runnable区别
-
继承Thread类
优点: 创建方式简单,可以直接new Thread(){重写run()},如果需要访问当前线程,getName()即可,直接获取线程名称。
缺点: 只能单继承了,无法在继承其他类。 -
实现Runnable接口 (推荐使用)
优点: 自定义的线程类只是实现了Runnable接口,扩展性好,可以在继承其他类, 在这种方式下,多个线程可以共享同一个target对象, 所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、还有数据分开(解耦),形成清晰的模型,较好地体现了面向对象的思想
缺点: 编程稍微复杂,如想访问当前线程,则需使用Thread.currentThread().getName()方法



