并发与并行
并发:多个任务在同一时间段执行,示意可看作是,cpu在执行时,交替执行多个任务
并行:多个任务在同一时刻进行,现代cpu都是多核多线程,即同时执行多个任务
进程与线程
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
一般电脑的内容就存储在ROM永久存储单元。一旦程序执行,就会进入进程,即在RAM临时存储单元之中,
线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
比如当程序要执行某一功能用到cpu,就会建立一条通道到cpu,即线程
例如8线程,cpu就会在8个线程中高速切换,速度快
我们简单总结下:
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
线程调度
- 分时调度
每个线程轮流使用cpu,平均分配每个线程的cpu使用时间
-
抢占式调度
优先让优先级高的程序使用cpu,如果优先级相等就随机分配,Java就是使用抢占式调度
主线程
执行主方法的线程,就是主线程
JVM在执行main方法时,会将main方法入栈,入栈后,JVM会开辟一条通向cpu的路径,也就是主线程
多线程的创建方法
法一:
1.创建一个Thread的子类
2.重写Thread的run方法(多线程执行内容)
3.创建对象
4.调用start方法开启多线程,会执行run方法
注意:不能多次调用线程,否则程序报错,线程结束后不能重新启动
新增线程会开辟新的栈空间
法二:
实现Runnable接口
1.创建实现Runnable接口的对象
2.重写run方法,即实现线程任务
3.创建实现接口的对象
4.创建Thread类对象,构造方法传参传递Runnable接口的实现类对象
使用Runnable接口的好处:
1.让线程更加灵活,Runnable只是一个接口而非父类,因此创建线程还可以有继承
- Runnable使得程序可扩展性强。普通创建线程的方法基础Thread类,线程任务和线程开启都是一个类实现,耦合性高。Runnable接口创建线程,线程任务在接口的实现类中,开启线程由创建新的Thread实现,降低程序耦合性
常用方法
- sleep方法,让线程以指定的毫秒数进入TIMED_WAITING状态
线程安全问题
线程安全问题的前提是线程访问了共享数据
多个线程在访问共享数据时,就会出现安全问题。要防止安全问题的产生,就必须使得一个线程在访问共享数据时,无论是否有cpu控制权,其他线程都不能访问该数据
同步
为了保证每个代码块都能进行原子操作,Java引入了线程同步机制
同步代码块
synchronized (锁对象){
可能出现线程安全的代码(访问了共享数据的代码)
}
tips:
1.代码块中的锁对象可以使用任意对象
2.必须保证多个线程的锁对象统一
3.锁对象作用:让代码块锁住,只让一个线程在代码块中执行
同步方法
同步方法也会将局部的代码块锁住,只让一个线程执行
同步方法的锁对象就是实现类的对象,即this(Runnable接口实现对象)
tips:
对于静态同步方法,锁对象不能再是实现类对象:因为静态方法和对象无关
而是本类的Class属性--->class文件对象
同步技术原理
同步技术使用了锁对象,这个对象叫同步锁,也叫对象锁,对象监视器
举一个小栗子:
t0,t1,t2是三个线程,t0首先抢到了cpu使用权,在执行run方法时,遇到synchronized代码块,然后发现锁对象还存在,于是获取锁对象,进入到同步块执行。
此时,t1又抢到了cpu使用权,在遇到synchronize代码块时,发现锁对象不在,t1就会进入阻塞状态,必须等到t0线程归还锁对象后才能继续。t0线程只有在执行完synchronized代码块后,才会归还锁对象。
优点:程序绝对安全
缺点:不断地获取锁,归还锁,效率低
Lock锁
Lock锁在jdk1.5后出现,是比synchronize更加好用的线程安全方法
方法:
- 创建ReentrantLock对象(对象实现了Lock接口)
- 在可能出现线程不安全的代码前调用lock方法
- 在结束后调用unlock方法
最后的unlock方法最好是放在finally代码块中,使得无论如何锁都会被释放
线程状态
注意Blocked和Timed_WAITING的区别
blocked:具有cpu执行资格,等待cpu空闲时执行,一般都是锁导致的
TIMED_WAITING:不具有cpu执行资格,cpu空闲也不会执行,一般是认为或者系统产生
TIMED_WAITING
例子:等待唤醒的案例:生产者与消费者
consumers: 告诉生产者自己的需求后,进入等待生产者生产
producers:在得知需求后开始生产,生产完毕唤醒消费者
始终保证消费者和生产者只有一个在执行
等待唤醒机制
1.线程通信
多个线程在处理同一资源时,动作可能不同
在多个线程并发处理时,cpu是随机选择线程的,而我们希望多个线程之间能够协调有规律完成同一个任务,此时就需要用到等待唤醒机制
三大方法:
- wait:线程不再活动,不会占用cpu资源,也不会去竞争锁
- notify:选择对象监视器中的一个对象释放,重新进入到调度队列(ready queue)之中
- 释放全部线程
总结: 从wait出来后,如果获得了锁,就能从waiting变为runnable,否则,会变成blocked状态
注意:wait和notify必须由同一个锁对象调用,并且notify和wait方法必须在synchronize代码块或同步方法中调用
原因:使用唤醒机制,一般是用来避免Lost wake up problem,必须在同步代码块或同步方法中调用,并且保证锁对象相同,wait方法调用后会把锁对象归还,而notify方法只是唤醒,不会直接将锁给对象,而是让对象具有竞争cpu的能力
线程池
当并发的线程过多时,并且每个线程都是执行某个任务就会销毁,此时,频繁地创建线程会导致效率过于低下。线程池的出现使得线程可以复用,在结束一个线程后,并不被销毁。
线程池,就是容纳多个线程的容器
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
使用线程池中线程对象的步骤:
-
创建线程池对象。
-
创建Runnable接口子类对象。(task)
-
提交Runnable接口子类对象。(take task)
-
关闭线程池(一般不做)。
并发与并行
并发:多个任务在同一时间段执行,示意可看作是,cpu在执行时,交替执行多个任务
并行:多个任务在同一时刻进行,现代cpu都是多核多线程,即同时执行多个任务
进程与线程
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
一般电脑的内容就存储在ROM永久存储单元。一旦程序执行,就会进入进程,即在RAM临时存储单元之中,
线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
比如当程序要执行某一功能用到cpu,就会建立一条通道到cpu,即线程
例如8线程,cpu就会在8个线程中高速切换,速度快
我们简单总结下:
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
线程调度
- 分时调度
每个线程轮流使用cpu,平均分配每个线程的cpu使用时间
-
抢占式调度
优先让优先级高的程序使用cpu,如果优先级相等就随机分配,Java就是使用抢占式调度
主线程
执行主方法的线程,就是主线程
JVM在执行main方法时,会将main方法入栈,入栈后,JVM会开辟一条通向cpu的路径,也就是主线程
多线程的创建方法
法一:
1.创建一个Thread的子类
2.重写Thread的run方法(多线程执行内容)
3.创建对象
4.调用start方法开启多线程,会执行run方法
注意:不能多次调用线程,否则程序报错,线程结束后不能重新启动
新增线程会开辟新的栈空间
法二:
实现Runnable接口
1.创建实现Runnable接口的对象
2.重写run方法,即实现线程任务
3.创建实现接口的对象
4.创建Thread类对象,构造方法传参传递Runnable接口的实现类对象
使用Runnable接口的好处:
1.让线程更加灵活,Runnable只是一个接口而非父类,因此创建线程还可以有继承
- Runnable使得程序可扩展性强。普通创建线程的方法基础Thread类,线程任务和线程开启都是一个类实现,耦合性高。Runnable接口创建线程,线程任务在接口的实现类中,开启线程由创建新的Thread实现,降低程序耦合性
常用方法
- sleep方法,让线程以指定的毫秒数进入TIMED_WAITING状态
线程安全问题
线程安全问题的前提是线程访问了共享数据
多个线程在访问共享数据时,就会出现安全问题。要防止安全问题的产生,就必须使得一个线程在访问共享数据时,无论是否有cpu控制权,其他线程都不能访问该数据
同步
为了保证每个代码块都能进行原子操作,Java引入了线程同步机制
同步代码块
synchronized (锁对象){
可能出现线程安全的代码(访问了共享数据的代码)
}
tips:
1.代码块中的锁对象可以使用任意对象
2.必须保证多个线程的锁对象统一
3.锁对象作用:让代码块锁住,只让一个线程在代码块中执行
同步方法
同步方法也会将局部的代码块锁住,只让一个线程执行
同步方法的锁对象就是实现类的对象,即this(Runnable接口实现对象)
tips:
对于静态同步方法,锁对象不能再是实现类对象:因为静态方法和对象无关
而是本类的Class属性--->class文件对象
同步技术原理
同步技术使用了锁对象,这个对象叫同步锁,也叫对象锁,对象监视器
举一个小栗子:
t0,t1,t2是三个线程,t0首先抢到了cpu使用权,在执行run方法时,遇到synchronized代码块,然后发现锁对象还存在,于是获取锁对象,进入到同步块执行。
此时,t1又抢到了cpu使用权,在遇到synchronize代码块时,发现锁对象不在,t1就会进入阻塞状态,必须等到t0线程归还锁对象后才能继续。t0线程只有在执行完synchronized代码块后,才会归还锁对象。
优点:程序绝对安全
缺点:不断地获取锁,归还锁,效率低
Lock锁
Lock锁在jdk1.5后出现,是比synchronize更加好用的线程安全方法
方法:
- 创建ReentrantLock对象(对象实现了Lock接口)
- 在可能出现线程不安全的代码前调用lock方法
- 在结束后调用unlock方法
最后的unlock方法最好是放在finally代码块中,使得无论如何锁都会被释放
线程状态
注意Blocked和Timed_WAITING的区别
blocked:具有cpu执行资格,等待cpu空闲时执行,一般都是锁导致的
TIMED_WAITING:不具有cpu执行资格,cpu空闲也不会执行,一般是认为或者系统产生
TIMED_WAITING
例子:等待唤醒的案例:生产者与消费者
consumers: 告诉生产者自己的需求后,进入等待生产者生产
producers:在得知需求后开始生产,生产完毕唤醒消费者
始终保证消费者和生产者只有一个在执行
等待唤醒机制
1.线程通信
多个线程在处理同一资源时,动作可能不同
在多个线程并发处理时,cpu是随机选择线程的,而我们希望多个线程之间能够协调有规律完成同一个任务,此时就需要用到等待唤醒机制
三大方法:
- wait:线程不再活动,不会占用cpu资源,也不会去竞争锁
- notify:选择对象监视器中的一个对象释放,重新进入到调度队列(ready queue)之中
- 释放全部线程
总结: 从wait出来后,如果获得了锁,就能从waiting变为runnable,否则,会变成blocked状态
注意:wait和notify必须由同一个锁对象调用,并且notify和wait方法必须在synchronize代码块或同步方法中调用
原因:使用唤醒机制,一般是用来避免Lost wake up problem,必须在同步代码块或同步方法中调用,并且保证锁对象相同,wait方法调用后会把锁对象归还,而notify方法只是唤醒,不会直接将锁给对象,而是让对象具有竞争cpu的能力
线程池
当并发的线程过多时,并且每个线程都是执行某个任务就会销毁,此时,频繁地创建线程会导致效率过于低下。线程池的出现使得线程可以复用,在结束一个线程后,并不被销毁。
线程池,就是容纳多个线程的容器
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
使用线程池中线程对象的步骤:
-
创建线程池对象。
-
创建Runnable接口子类对象。(task)
-
提交Runnable接口子类对象。(take task)
-
关闭线程池(一般不做)。