day16_java多线程(入门了解)

2023-12-13 03:31:33

多线程入门

一、线程和进程

  1. 进程

    进程:是指一个内存中运行的应用程序每个进程都有一个独立的内存空间和系统资源,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。进程是系统进行资源分配和调度的独立单位。

    单cpu同一时间点只能执行一件事情,CPU高效的切换让我们觉得是同时进行的

    我们在同一个进程内可以执行多个任务,每个任务就可以看成一个线程

    进程就是正在运行的程序
    进程是系统进行资源分配和调度的独立单位,每一个进程都有它自己的内存空间和系统资源。

    案例:

    百度云盘(一个应用程序:进程)

    下载功能(可以同时下载多个文件)

  2. 线程

    线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

    在同一个进程内可以执行多个任务,而这个每个任务就是看成线程
    **线程,**是程序执行的单元,执行路径,是程序使用cpu最基本单位。

    cpu 只有一个, 每次只能执行一个(线程)

    一个进程有多个线程

    单线程 :如果程序只有只有一条执行路径 多线程:如果程序有多条执行路径

二、并行与并发

  1. 并发:逻辑上同时发生,指再某一个时间内同时运行的程序
  2. 并行:物理上同时发生,指在某一个时间点同时运行的程序

在这里插入图片描述

三、多线程的意义嘛?

多线程存在的意义:不是提高程序的执行速率,其实是为了提高程序的使用率。
程序的执行其实都是在抢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的使用率更高。

在这里插入图片描述

线程优化级高,并不能让线程先执行。它还是随机的,只是概率高点

六、多线程好处:

  1. 充分利用CPU的资源(多进程), 多线程(为当前程序抢占CPU使用权)

  2. 简化编程模型

  3. 带来良好的用户体验

  4. 多个线程之间互不干扰

七、线程的控制

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方式创建线程
  1. 创建一个Runable接口的实现类MyRunable
  2. 重写run方法
  3. 创建实现类MyRunable对象, myrunable
  4. 创建Thread类的对象,把 myrunable对象作为构造参数传过去

九、创建多线程方式总结

实现多线程方式:两种

方法1:Thread类

  1. 自定义一个MyThread类继承Thread类
  2. 在MyThread类中重写run方法
  3. 创建MyThread类的对象
  4. 启动线程对象。start()方法

方法2:Runable 接口

  1. 定义一个Runable接口的实现类,MyRunable类
  2. 在MyRunable类中重写run方法
  3. 创建MyRunable类的对象myRunable
  4. 创建Thread类的对象,且将myRunable对象作构造方法的参数传递
  5. 启动线程 . start方法

问题:为什么有了方法1,还需要方法2

  1. 为了避免 java 中由于单继承带来的局限性
  2. runable接口实现的线程,适合多个相同的代码去处理同一个资源的情况,把线程的同程序的代码,数据进行有效分享(资源共享),体现了面向对象思想

线程安全问题

案例:卖票????

出现同票原因

// 分析同票的原因
// 线程抢夺cpu 执行权时,执行的代码具有原子性
// 原子性是指最基本的代码(最小的语句)

出现负票的原因???

如何解决

什么情况会出现线程安全问题

  1. 是否多线程环境
  2. 是否有共享资源
  3. 是否有多条语句操作共享资源

同步锁

同步代码块

锁对象:任意对象

语法:

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解决了多次消费同一资源的问题

线程间通信的内存图

常见情况:

  1. 新建 – 就绪 – 运行 – 死亡
  2. 新建 – 就绪 – 运行 – 就绪 – 运行 – 死亡
  3. 新建 – 就绪 – 运行 – 等待阻塞 – 同步阻塞 – 就绪 – 运行–死亡
  4. 新建 – 就绪 – 运行 – 其它阻塞 – 就绪 – 运行–死亡
  5. 新建 – 就绪 – 运行 – 同步阻塞 – 就绪 – 运行–死亡

在这里插入图片描述

十二、线程池

使用线程池的思想,池化思想可以提高重用性和效率(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);
    }
}

文章来源:https://blog.csdn.net/qq_64646478/article/details/134933148
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。