多线程入门
一、线程和进程
-
进程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间和系统资源,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。进程是系统进行资源分配和调度的独立单位。
单cpu同一时间点只能执行一件事情,CPU高效的切换让我们觉得是同时进行的
我们在同一个进程内可以执行多个任务,每个任务就可以看成一个线程
进程就是正在运行的程序
进程是系统进行资源分配和调度的独立单位,每一个进程都有它自己的内存空间和系统资源。案例:
百度云盘(一个应用程序:进程)
下载功能(可以同时下载多个文件)
-
线程
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
在同一个进程内可以执行多个任务,而这个每个任务就是看成线程
**线程,**是程序执行的单元,执行路径,是程序使用cpu最基本单位。cpu 只有一个, 每次只能执行一个(线程)
一个进程有多个线程
单线程 :如果程序只有只有一条执行路径 多线程:如果程序有多条执行路径
二、并行与并发
- 并发:逻辑上同时发生,指再某一个时间内同时运行的程序
- 并行:物理上同时发生,指在某一个时间点同时运行的程序
三、多线程的意义嘛?
多线程存在的意义:不是提高程序的执行速率,其实是为了提高程序的使用率。
程序的执行其实都是在抢cpu的资源,cpu的执行权。
多个线程抢夺到cpu执行权的概率更大 线程抢夺执行权具有随机性
四、java程序的运行原理
由java命令启动jvm,启动jvm相当于启动了一个进程
接着该进程创建主线程(main)去调用main方法
jvm虚拟机的启动是单线程的还是多线程的?多线程
原因是垃圾回收线程也要启动,不然很容易就内存溢出
五、Thread的基本使用
1、创建线程的步骤
- 自定义一个类MyThread 继承Thread类
- 重写run方法
- 创建一个MyThread对象
- MyThread对象.start()
注: run 与 start 的区别
- 直接调用run方法,还是在main线程中执行
- start() 方法, jvm会创建一个新线程,然后jvm会自动运行新线程的run方法
2、创建多个线程的方法
注:new 多个MyThread对象即可。不是要理解成调用多次start了
MyThread myThread = new MyThread("张三"); //这就是一个线程
MyThread myThread2 = new MyThread("李四");
myThread.start();
myThread2.start();
3、获得线程的名字
-
String getName()
返回该线程的名称。可以在Thread的子类中直接使用
-
Thread.currentThread() : 得到当前线程对象
System.out.println(Thread.currentThread().getName());
4、多线程独立栈空间
5、多线程的打印具有随机性
6、线程的调度
**分时调度:**所有线程轮流使用CPU 的使用权,平均分配每个线程占用 CPU 的时间。
**抢占式调度:**优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),
Java使用的是抢占式调度。
7、设置线程的优先级:
抢占式调度详解:大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我 们上课一边使用idea编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是 在同时运行,”感觉这些软件好像在同一时刻运行着“。 实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
线程优化级高,并不能让线程先执行。它还是随机的,只是概率高点
六、多线程好处:
-
充分利用CPU的资源(多进程), 多线程(为当前程序抢占CPU使用权)
-
简化编程模型
-
带来良好的用户体验
-
多个线程之间互不干扰
七、线程的控制
1、Thread类API
属性:
NORM_PRIORITY : 值为 5
MAX_PRIORITY : 值为 10
MIN_PRIORITY : 值为 1
构造方法:
-
Thread():分配一个新的 Thread对象。。
-
Thread(String name):分配一个指定名字的新的线程对象。
-
Thread(Runnable target):分配一个带有指定目标新的线程对象。
-
Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
常用方法:
- String getName():获取当前线程名称。
static Thread currentThread()
:返回对当前正在执行的线程对象的引用。- void setName(String name):将此线程的名称更改为等于参数 name 。
void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。- void run():此线程要执行的任务在此处定义代码。
static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。- int getPriority() :返回此线程的优先级。
- void setPriority(int newPriority) :更改此线程的优先级。
void join()
:等待这个线程死亡。- static void yield():对调度程序的一个暗示,即当前线程愿意让出当前使用的处理器。
- void interrupt():中断这个线程。
boolean isAlive()
:测试这个线程是否活着。Thread.State getState()
:返回此线程的状态。
2、线程的休眠
public void run() {
// 放让线程执行代码块
for(int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName()+"我是最棒的:"+i);
}
}
3、线程的加入
void join()
:等待这个线程死亡。
MyThread myThread = new MyThread("张三"); //这就是一个线程
MyThread myThread2 = new MyThread("李四");
myThread.start();
try {
myThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
myThread2.start();
4、线程的礼让
static void yield()
:对调度程序的一个暗示,即当前线程愿意让出当前使用的处理器。
public void run() {
// 放让线程执行代码块
for(int i=0;i<10;i++){
Thread.yield(); //让出cpu 使用权
System.out.println(getName()+"我是最棒的:"+i);
}
}
5、线程的中断
- public final void stop (): 让线程停止,过时了,但是还可以使用。
- **public void interrupt ( )**∶中断线程。把线程的状态终止,并抛出一个InterruptedException.
6、线程的守护
public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
线程启动前必须调用此方法。
MyThread myThread = new MyThread("张三"); //这就是一个线程
MyThread myThread2 = new MyThread("李四");
myThread.setDaemon(true);
myThread2.setDaemon(true);
myThread.start();
myThread2.start();
//myThread.interrupt();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"我是最棒的:"+i);
}
八、Runnable创建多线程
runable方式创建线程
- 创建一个Runable接口的实现类MyRunable
- 重写run方法
- 创建实现类MyRunable对象, myrunable
- 创建Thread类的对象,把 myrunable对象作为构造参数传过去
九、创建多线程方式总结
实现多线程方式:两种
方法1:Thread类
- 自定义一个MyThread类继承Thread类
- 在MyThread类中重写run方法
- 创建MyThread类的对象
- 启动线程对象。start()方法
方法2:Runable 接口
- 定义一个Runable接口的实现类,MyRunable类
- 在MyRunable类中重写run方法
- 创建MyRunable类的对象myRunable
- 创建Thread类的对象,且将myRunable对象作构造方法的参数传递
- 启动线程 . start方法
问题:为什么有了方法1,还需要方法2
- 为了避免 java 中由于单继承带来的局限性
- runable接口实现的线程,适合多个相同的代码去处理同一个资源的情况,把线程的同程序的代码,数据进行有效分享(资源共享),体现了面向对象思想
线程安全问题
案例:卖票????
出现同票原因
// 分析同票的原因
// 线程抢夺cpu 执行权时,执行的代码具有原子性
// 原子性是指最基本的代码(最小的语句)
出现负票的原因???
如何解决
什么情况会出现线程安全问题
- 是否多线程环境
- 是否有共享资源
- 是否有多条语句操作共享资源
同步锁
同步代码块
锁对象:任意对象
语法:
synchronized (obj){
//操作共享数据的代码
}
同步方法
同步方法的锁对象是this
public synchronized void sellTicket(){
if(ticket>0){ // c1 c2 c3 ticket = 1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"第"+ticket+"张票");
ticket--;// 0
}
}
同步静态方法
同步方法的锁对象是当前类的字节码对象(反射会学习)
public synchronized static void sellTicket(){
if(ticket>0){ // c1 c2 c3 ticket = 1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"第"+ticket+"张票");
ticket--;// 0
}
}
Lock锁
public void run() {
while (true){
lock.lock(); //上锁
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"第"+ticket+"张票");
ticket--;// 0
}
lock.unlock(); //释放锁
}
}
synchronized与lock的区别
- synchronized:jvm级别的锁,jvm自动上锁和解锁
- lock锁:java代码的锁,需要手动的加锁和释放锁
死锁
多线程产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 保持和请求条件:一个进程因请求资源而阻塞时,对已获得资源保持不妨。
- 不可剥夺调用:进程已获得资源,在未使用完成前,不能被剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
public class MyThread extends Thread{
private boolean flag;
public MyThread(Boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (LockObject.objA) { //
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("我有锁A,需要锁B");
synchronized (LockObject.objB) {
System.out.println(Thread.currentThread().getName() + ":我有锁B");
}
}
}else{
synchronized (LockObject.objB) { //
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("我有锁A,需要锁B");
synchronized (LockObject.objA) {
System.out.println(Thread.currentThread().getName() + ":我有锁B");
}
}
}
}
}
public class LockObject {
public static Object objA = new Object();
public static Object objB = new Object();
}
public class Example02 {
public static void main(String[] args) {
MyThread thread = new MyThread(true);
MyThread thread2 = new MyThread(false);
thread.start();
thread2.start();
}
}
十、线程的生命周期
线程的生命周期(状态):
- 新建:创建一个线程对象
- 就绪:对象调用start()方法,有执行资格,但是没有抢到CPU资源
- 运行:抢到了CPU资源,有执行权
- 阻塞:通过sleep(),wait()等方法让线程中断运行,没有了执行资格
- 死亡:线程执行完毕,等待垃圾回收
十一、线程之间的通信
生产者:
先查看是否有资源,有就等待,没有就生产,生产后通知消费者来消费资源
消费者:
先查看是否有资源,有就消费资源,没有就通知生产者生产资源并等待
public class BaoZi { // 公共资源包子类
public String name;
public boolean flag=false; //是否有资源
public BaoZi(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BaoZi(String name){
this.name = name;
}
}
// 生产者,包包子的
public class SetBaoZi extends Thread{
private BaoZi bz;
private int x = 0;
public SetBaoZi(BaoZi bz){
this.bz = bz;
}
@Override
public void run() {
while (true){
synchronized (bz){
if (bz.flag){ // 有资源,不需要包包子,等待资源消耗完
try{
bz.wait();
} catch (InterruptedException e){
throw new RuntimeException(e);
}
}
if (x%2==0){
bz.name = "小笼包";
}else {
bz.name = "酱肉包";
}
bz.notify(); // 唤醒消费者线程
bz.flag = true;
x++;
}
}
}
}
// 消费者
public class GetBaoZi extends Thread{
private BaoZi bz;
public GetBaoZi(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
while (true){
synchronized (bz){
if (!bz.flag){
try {
bz.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("消费了包子:"+bz.name);
bz.flag = false;
bz.notify(); // 包子被消费,唤醒生产者线程包包子
}
}
}
}
// 测试
public class Test {
public static void main(String[] args) {
BaoZi baoZi = new BaoZi();
SetBaoZi setBaoZi = new SetBaoZi(baoZi);
GetBaoZi getBaoZi = new GetBaoZi(baoZi);
setBaoZi.start();
getBaoZi.start();
}
}
// 加锁,解决了消费资源出错的问题
// 使用wait和notify解决了多次消费同一资源的问题
线程间通信的内存图
常见情况:
- 新建 – 就绪 – 运行 – 死亡
- 新建 – 就绪 – 运行 – 就绪 – 运行 – 死亡
- 新建 – 就绪 – 运行 – 等待阻塞 – 同步阻塞 – 就绪 – 运行–死亡
- 新建 – 就绪 – 运行 – 其它阻塞 – 就绪 – 运行–死亡
- 新建 – 就绪 – 运行 – 同步阻塞 – 就绪 – 运行–死亡
十二、线程池
使用线程池的思想,池化思想可以提高重用性和效率(Executors)。
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("我是callable方法");
return null;
}
}
public class Example01 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
MyCallable myCallable1 = new MyCallable();
MyCallable myCallable2 = new MyCallable();
service.submit(myCallable1);
service.submit(myCallable2);
}
}
十二、线程池
使用线程池的思想,池化思想可以提高重用性和效率(Executors)。
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("我是callable方法");
return null;
}
}
public class Example01 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
MyCallable myCallable1 = new MyCallable();
MyCallable myCallable2 = new MyCallable();
service.submit(myCallable1);
service.submit(myCallable2);
}
}