JAVA多线程

多线程

JAVA是如何运行的

JAVA系统

在这里插入图片描述

JVM

在这里插入图片描述

Data Structure

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dksoDpHF-1617268044059)(JAVA多线程_files/43746c44-beaa-4782-ae0a-79f2e9f23e80.png)]

区域划分

• Java程序运行时由一到多个线程组成
• 每个线程都有自己的栈
• 所有线程共享同一个堆
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I5q6PjyV-1617268044062)(JAVA多线程_files/b8029d00-6ab6-4f2e-a5a8-42f97b4cc9ce.png)]

• Method内存区域
• 保存class信息(类型定义信息)
• 每个Java应用拥有一个相应区域
• 虚拟机中的所有线程共享
• 一次只能由一个线程访问
• Heap内存区域
• 保存对象或数组
• 每个Java应用拥有一个相应区域
• 为垃圾回收提供支持
• 程序执行过程中动态扩展和收缩
Method 中放的是class,Heap中放的是对象。
如何找到对象?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eundDGd-1617268044064)(JAVA多线程_files/925365b5-fef2-4226-bb03-095efb5eadfc.png)]

方法的分派调用:
• 类提供了一个列出所有方法的列表,分派向量(dispath vector)
• 分派向量给出了类中所有方法入口,存放在Method内存区的class数据中
• 对象中内在有一个指向类数据(分派向量)的指针
• 对象引用类型可以是对象的创建类型本身或者其父类型
• 改变对象引用类型不会改变对象中保存的分派向量指针

思考

• 垃圾回收是Java虚拟机的一个核心功能,它的基本工作原理是扫描堆中的对象,记录对象的引用计数,如果发现一个对象不能被程序访问到,就标记为“垃圾”,从而在合适时机回收其内存。
请思考并讨论:
• 1. 如果堆中某个对象仍然被程序引用,那么可以从JVM的哪个区域找到这些引用?

总可能在栈或者堆区的某个地区找到这些引用

• 2. 一个对象的引用计数如果不为0,是否仍然可能被回收

可能,Java判断的是对象是否能被程序访问到。某个类中几个对象相互引用,但是最终这个类不会再用到,那么程序也就同样不会访问到这几个对象。
同样,也有可能出现引用计数为0,但是不被回收。会出现玄学内存泄漏。

线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqTpVprd-1617268044065)(JAVA多线程_files/d81e04dd-d2dc-4657-bf0d-86a5153e3eff.png)]

执行(控制)流(flow of control)是程序运行时的一个运行流程
• 执行流内的指令顺次运行
• 任意时刻,一个执行控制流中能且只能有一个指令在运行
• 每个执行流都使用栈来追踪函数调用
线程对象封装了对执行(控制)流的管理
• 单线程:程序运行时只有一个执行流只创建一个线程对象
• 多线程:程序运行时可以有多个执行流创建了多个线程对象

JAVA栈内存

每个线程有专属的栈内存,用来追踪方法执行层次
栈由栈帧组成,顶栈帧描述当前线程在执行哪个方法,JVM对栈帧进行push,pop操作
栈帧是什么?

栈帧结构:
• 方法输入参数
• 方法局部变量
• 栈帧数据(Frame Data)
到类定义区(method area)的引用
无异常的方法调用返回
返回值推到上一帧
有异常的方法执行,分派处理

(dispatch) • 操作数栈(Operand Stack)
始终处于栈顶
为方法中的各种计算操作提供了工作空间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NvkPMewK-1617268044066)(JAVA多线程_files/e20f7801-750a-4c6d-b07d-cc0b5d926a10.png)]

线程交互

  • 一个程序任意两个执行流之间通常有一定的相关性。
    时间相关性:在某个特定时间点进行同步
    空间相关性:在某些特定的数据状态上进行同步
  • 线程在执行中进行交互
    控制交互:在合适的实际启动,暂停,终止另一个线程。
    数据交互,把数据传递给另一个线程。
  • 同步对象交互和异步对象交互
    对象保存在堆中,相互独立,内在具有并发性。
    对象调用方式的交互具有同步特性,需等待被调用方法返回才能继续向下执行。
    异步方式:方法调用不必等待执行结束就立即返回:t.start()
    • 基于共享对象的异步交互
      1.使用信使对象(共享对象)通知对方自己“做了什么”或者“状态发生了改变”
      2.并发处理业务,使用传送带交换数据

多线程JAVA程序

JVM采用线程来管理Java应用程序的运行状态
• 运行一个Java程序->创建一个JVM进程->创建一个主线程(入口为main方法)
• 默认情况下,Java程序执行时只有一个主线程
• 从主线程开始,可根据需要来创建、启动和控制其他的线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GBa9bxdz-1617268044067)(JAVA多线程_files/9eb30374-fea9-44e5-abbc-afc849c25ddc.jpg)]

写一个多线程java程序
  • JAVA提供了对象化线程支持
    Tread类和Runnable接口java.lang包中
  • 继承Tread类 重写run方法
  • 实现Runnable接口 重写run方法
  • 匿名内部类创建线程
// 通过继承Thread类 
public class Scanner1 extends Thread {
    public void run() {

// 线程执行入口点,相当于主类的main()                    this.go();
     }
}
// 通过实现Runnable接口 
public class Scanner2 implements Runnable { 
    public void run() { // 线程执行入口点,相当于主类的main() 
        this.go(); 
    }
}

线程模板

入口代码模板

public void run() {
    try{
        while(more work to do){  常规的唤醒(即sleep()退出)从这里继续执行
            dosomework;
            sleep() or wait();// 让其他线程有机会执行
        }
    } catch (InterruptedException e){{ // 如果由interrupt()唤醒则从这里继续执行
     // thread interrupted during sleep or wait
    }
}
启动多线程

run不是给用户代码来调用(main也不是给用户来调用!)
• Thread t=new Scanner1(…); // new Scanner (“1”);
• t.start();
• Thread t = new Thread(new Scanner2(…));
• t.start();

线程调度

• 线程运行中需要交互
• 空转的线程会浪费CPU资源
• 可能会有多个线程在等待某个数据的就绪

线程调度不确定性

public class SimpleThread extends Thread{
    public SimpleTread(String str){
        super(str);
    }
    public void run(){
        for (int i=0;i<10:i++){
            System.out,println(i+" "+getName());
            try{
                sleep((long)(Math.random()*1000));
            }catch(InterruptedException e){}
        }
        System.out.println("DONE!"+getName());
    }
}
public class TwoTreadsTest{
    public static void main(String[]args){
        new SimpleThread("t1").start();
        new SimpleThread("t2").start();
    }
}

共享资源控制

• 多个线程共同访问一个对象(共享对象)
经典场景读写共同的对象
如果不加以控制会导致数据状态混乱—数据竞争、数据不一致
多个线程对变量的访问次序无法预测

public class BankAccount {
    private int balance;
    public void deposit(int amount){
        balance += amount;
        }
}
a.deposit(amount = 50):
    x = this.getBalance()
    x+= amount;
    this.setBalance(x)
b.deposit(amount = 40):
    x = this.getBalance()
    x+= amount;
    this.setBalance(x)

共享数据访问控制

采用互斥控制:任何时刻只允许一个线程获得访问/执行权限
synchronized(obj) {}:任意时刻只允许一个线程对obj进行操作
synchronized method(){}:任意时刻只允许一个线程调用方法method

• 任何线程访问受控的共享数据时
可能有多个其他线程在等待访问该共享资源
执行结束前通过notify/notifyAll来让JVM调度等待队列中的其他线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2bcJY20V-1617268044068)(JAVA多线程_files/c884fa19-3388-4742-a57f-985e0352dce0.jpg)]

synchronized如何发挥作用?

每个synchronized block都关联到一个对象(monitor)
• JVM确保拿到montior.lock的线程才能进入执行
应用:
1.作用于方法声明的同步块
• JVM确保每个对象只有一个lock
• 针对static方法:monitor实际是****.class
• 效果:one thread per class
• 针对instance方法:monitor实际是this
• 效果:one thread per object/instance
2.作用于方法中局部代码的同步块
• 取决于所选择的monitor对象
• 效果:one thread per monit

线程交互

• 一个程序中任意两个执行流通常都有一定的相关性
• 时间相关性:在某些特定的时间点上进行同步
• 数据相关性:在某些特定的数据状态上进行同步
• 线程在执行中需要进行交互
• 控制交互:在合弁时机启动、暂停或停止另一个线程
直接控制交互:start stop sleep yield
间接控制交互:共享对象,wait notify notifyAll
• 数据交互:把数据传递给另一个线程

线程的运行时状态

New // 线程被创建
Runnable//线程start()后的状态
Blocked//阻塞,无法访问公共数据或者临界区执行权限
Waiting//等待被唤醒 wait(),join
Timed_waiting//等待指定时间 sleep(time) wait(time) join(time)
Termingted/dead//run() 结束或者stop()被调用

线程状态变化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r6mrldXs-1617268044069)(JAVA多线程_files/c0370eb5-0b54-468a-849b-aa5be1e0dc53.png)]

线程调度应当与业务结合

多个线程并发运行,交换数据或共同工具是常态
>不知道是否有新数据,工具是否可用
采用循环方式查询

如果一直没有新数据或工具不可用呢?
>空转,处于是否有新数据和工具是否可用的轮询当中
极大浪费CPU资源->把当前线程设置为等待状态sleep,wait

期望:有新数据或工具可用时被唤醒
   >通过notify/notifyAll通知JVM我准备好了数据,我暂时不用某个工具,JVM唤醒等待中相应线程
如果有多个线程在等待?
>JVM唤醒具有不确定性

共享对象去调度线程是合理的

单线程与多线程对比

流程数目只有一个执行流多个执行流
流程控制方法调用、分支控制方法调用,分支控制,创建,启动,等待,唤醒
调试侵入不影响执行效果影响执行效果
对象交互同步同步,异步,并发
线程间共享对象和非线程间共享对象
非线程间共享对象线程间共享对象
遵循对象构造和引用基本规则遵循对象构造和引用基本规则
相互间可以访问,无需额外控制相互间可以访问,需要互斥控制
可访问线程间共享对象,遵循互斥规则不可以访问非线程间共享对象
### 线程设计的基本框架 • 实现一个独立和完整的算法/功能 + run方法 • 通过构造器或专门的方法获得与其他线程共享的对象 + 数据交互窗口:线程间共享对象 + 实现了Runnable接口的对象本身也可以作为共享对象 • 创建和使用专属对象 + 仅供自己这个线程使用:非线程间共享对象 + 这些对象之间仍然可以相互调用方法 #### 通过共享对象与其他线程交互 通过锁来确保任何时候只能有一个线程在共享对象“房间”内工作 • 基于obj的语句段级同步控制: synchronized(obj){} • 基于this的方法级同步控制:synchronized method{} 完成工作即退出房间 • 交出锁(自动) • 通知其他在等待进入共享对象“房间”工作的线程 继续“自己家里”的处理工作 多个线程共享访问一个资源,限制任何时候只能有一

个线程访问 • 基本做法:把共享对象的方法进行synchronized保护

线程交互模式 --生产者消费者模式(发布订阅模式)

在这里插入图片描述

• 生产者向托盘对象存入生产的货物 //synchronized method
• 消费者从托盘里取走相应的货物 //synchronized method
• 货物放置控制 //依赖于托盘状态
• 货物提取控制 //依赖于托盘状态
• 托盘既可以是单一对象,也可以是容器对象

注意同步范围

  1. 问题1:混淆synchronized限定的范围,在外部共享访问资源导致冲突
class CustomerDb { 
    private ArrayList<Customers>customers= new ArrayList();
        public synchronized ArrayList<Customer> getCustomers(){ 
        return customers; 
    }
}
class CustomerUpdater{
    CustomerUpdater (CustomerDb cdb){}
    public void run{
        ArrayList<Customer> customer = cdb.getCustomers();
    ...
        customers.add(customer)
    }
}

synchronized定义的是代码意义上的临界区,依赖相应的monitor;
monitor在临界区外失去作用!
2. 问题2:所有对共享对象的访问都需要进行同步控制,不能遗漏

class CustomerDb2 {
     private final ArrayList<Customer>customers = new ArrayList();
     public addCustomer(Customer c){ 
        synchronized (customers){ 
            customers.add(c);
         } 
    }
     public removerCustomer(Customer c){
         customers.add(c);
     }
 }
//既然customers是共享对象,任意时刻都可能有多个线程在访问它!
  1. 问题3:同步控制施加在上层对象,但忽略了下层对象仍然可能

是共享对象

class CustomerDb3 { 
    private final List<Customer>customers = new ArrayList();
     public double countOrderValue (Customer c){
         ArrayList<Order>orders;
         synchronized (c){ 
            orders = c.getOrders(); 
        }
         double total = 0.0;
         for(Order o : orders) 
            total += o.getValue();
         return total;
     }
 }

mers = new ArrayList();
public double countOrderValue (Customer c){
ArrayListorders;
synchronized ©{
orders = c.getOrders();
}
double total = 0.0;
for(Order o : orders)
total += o.getValue();
return total;
}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值