20.多线程(一)

本文详细介绍了Java中的进程与线程概念,包括线程的两种存在形式,以及并行与并发的区别。通过实例展示了多线程的三种开启方式,并探讨了线程的生命周期、同步机制和线程间通信。此外,还涉及到了线程的休眠、优先级设置以及线程对象的命名和操作。最后,通过包子案例展示了线程等待与唤醒的使用。
摘要由CSDN通过智能技术生成

进程和线程

进程 : 正在运行的应用程序
线程 : 一个正在运行的应用程序中的一条执行流程
    1. 线程存在于进程中
    2. 一个进程中至少有一个线程
    3. 市面上绝大部分的程序,都是多线程程序

并行和并发

//角度 : 同一时刻,同一时间段
并行 : 在同一时刻有多件事情发生
并发 : 在同一时间段有多件事情发生

并发不一定并行,并行一定并发

多线程的程序 : 多线程程序是一个并行的程序

CPU处理程序的逻辑

//站在CPU的角度
1. CPU收集所有进程中的线程并打乱
2. 随机执行其中一个线程 (CPU在多个线程之间高速切换)
    
//站在线程的角度
1. 线程抢夺CPU的执行权
2. 抢到了就执行,没抢到阻塞

结论 : 多线程的程序的随机性很强

多线程的体系结构

多线程的开启方式

线程的开启方式一 : 继承的方式

步骤 :
            1. 准备一个类,继承Thread -> MyThread
            2. 主动重写 run方法
            3. 在run方法内,编写线程对象需要执行的任务
            4. 在需要使用线程的地方,创建MyThread对象
            5. MyThread对象调用start() 启动线程

线程类:

public class MyThread extends Thread {//线程类

    //主动生成带有String name参数的构造方法
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    //主动重写run
    @Override
    public void run() {
        //线程任务是 打印10次HelloWorld
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "HelloWorld" + i);
        }
    }
}

主线程:

public class Demo {//进程
    public static void main(String[] args) {//主线程

        //创建自己的线程类对象
        MyThread mt = new MyThread("线程1 : ");

        //mt.setName("线程1 : ");

        //启动线程
        mt.start();//新开了一个线程

        //主线程的任务
        for (int i = 1; i <= 10; i++) {
            System.out.println("Java" + i);
        }

    }
}

线程的开启方式二 : 实现的方式

步骤 : 
            1. 准备一个类,实现Runnable --> 任务类: MyTarget
            2. MyTarget强制重写run方法,在run方法内编写线程的任务
            3. 在需要使用线程的地方,创建Thread对象和MyTarget对象,并把MyTarget传入到Thread对象类 --> 任务分配
            4. Thread对象 启动线程 -> 调用start()方法

任务类:

public class MyTarget implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println( Thread.currentThread().getName() + "HelloWorld" + i);
        }
    }
}

主线程:

public class Demo {
    public static void main(String[] args) {
        //创建任务对象
        MyTarget target = new MyTarget();
        //创建线程对象
        Thread thread = new Thread(target,"线程2 : ");

        //thread.setName("线程2 : ");
        //启动线程

        //匿名内部类的方式实现线程对象的开启
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "Python" + i);
                }
            }
        },"啦啦啦");

        thread.start();
        thread1.start();

        //main线程的任务
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName() + "Java" + i);
        }
    }
}

线程的开启方式三 : 有任务结果的线程开启方式

Callable<V> : 带有任务结果的任务接口 -> 线程的任务接口
    抽象方法 : V call()
        
    承上启下的对象 : FutureTask<V>
        构造方法 :
             FutureTask(Callable<V> callable)
             FutureTask(Runnable runnable, V result)

     FutureTask<V> 类是Runnable的实现类
      
    Thread类中的构造:
            Thread(Runnable target)

    看线程任务的结果:
        FutureTask<V> 类中有 V get() -> 获取线程对象的结果并返回
            注意 : 它一定是等到线程执行完毕后才能获取结果        
            
步骤 : 
    //创建Callable的实现类,重写call方法
    1. 创建有结果的任务实现类对象 -> Callable<V>接口的实现类对象 (task)
    2. 创建FutureTask对象,把Callable<V>接口的实现类对象 (task)传入给FutureTask对象
    3. 创建Thread对象,把FutureTask对象传递给Thread对象
    4. Thread对象调用start方法,启动线程

Callable:

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        for (int i = 1; i <= 100; i++) {
            System.out.println("舔狗 : 女神,睡了吗 ?");
        }
        return "陌生男性 : 你哪位 ?";
    }
}

主线程:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建任务对象
        MyCallable call = new MyCallable();

        //创建承接对象
        FutureTask<String> fk = new FutureTask<String>(call);

        //创建线程对象
        //把任务对象分配给线程对象
        Thread thread = new Thread(fk,"线程3: ");

        //thread.setName("线程3: ");

        //启动线程
        thread.start();


        //main线程
        for (int i = 0; i < 100; i++) {
            System.out.println("PHP");
        }

        //获取线程的结果
        String str = fk.get();
        System.out.println(str);
    }
}

多线程程序的内存图

线程对象名称的操作

设置名称 : 
    Thread 构造方法 : 
        Thread(String name) : 在创建线程对象的同时给线程对象取名字;
        Thread(Runnable target, String name) : 传入线程任务对象,再给线程对象取名字
        
    Thread 成员方法    :
        void setName(String name) : 对线程对象取名字

获取名称 :
    Thread 成员方法    : 
        String getName()
            
Thread类中的静态方法 : 获取当前线程对象
    static Thread currentThread()
    
非Thread类的子类中获取线程名称 : Thread.currentThread().getName()   

线程休眠的方法

Thread类中 : static void sleep(long millis) : 让当前线程对象休眠millis毫秒
    休眠 : 线程放弃抢夺CPU的执行权,不释放锁资源的
    时间到了 : 自己醒来,抢夺CPU执行权
        1. 立刻抢到,立刻执行
        2. 没有抢到,阻塞状态

线程的优先级方法

优先级 : 优先级越高,抢到CPU执行权的 概率 越大
    默认优先级 : 5
    最低优先级 : 1
    最高优先级 : 10
        
设置优先级的方法 :
     void setPriority(int newPriority) 
获取优先级的方法
     int getPriority() :

设置守护线程

void setDaemon(boolean on) : 传入true,那么调用此方法的线程对象就被设置为守护线程

同步代码块

同步代码块的格式 :
    synchronized(锁对象){
        需要被保护的代码; //当一个线程在执行此段代码时,不让其他线程访问
    }

锁对象 : 
    1. 可以是Java中任意引用类型的对象
    2. 锁对象要被所有的线程对象共享 (共享的锁控制所有线程)
        
当线程代码被上锁   : 程序会执行的慢一些
    1. 先获取锁
    2. 执行代码
    3. 释放锁
    
线程对象执行,以前只需要抢夺CPU的执行权,现在上锁的代码需要线程抢夺CPU执行权和锁资源,线程才可以执行    

休眠 : 线程放弃抢夺CPU的执行权,不释放锁资源的

同步方法

同步方法的格式
    权限修饰符 状态修饰符 synchronized 返回值类型 方法名(形参列表){
        方法体;//当一个线程在执行此段代码时,不让其他线程访问
        //return 值;
    }
    //非静态同步方法的锁对象是谁 ? this
    //静态同步方法的锁对象是谁 ? 本类的字节码对象

Lock接口

Lock接口 :
    lock() : 上锁
    unlock(): 解锁
    
实现类对象 :   ReentrantLock    

线程的生命周期

新建 -> NEW : 线程对象被创建,但是没有调用start时的状态

阻塞 -> BLOCKED : 线程对象没有获取CPU执行权或者锁资源,线程对象处于阻塞状态(时刻抢夺资源的路上) //清醒

限时等待 -> TIMED_WAITING : 线程对象被调用了sleep(long millis)或者wait(long timeout)方法
    sleep(long millis) : 仅仅只释放CPU执行权,不释放锁资源
    wait(long timeout) : 释放CPU执行权和锁资源
    
无限等待 -> WAITING : 线程对象被调用wait()方法   
    wait() : 释放CPU执行权和锁资源
    
运行 -> RUNNABLE : 线程对象调用start之后,并抢到了CPU执行权和锁资源时,正在运行的状态

死亡 -> TERMINATED : 线程对象执行完任务,被移除内存

 等待和唤醒的方法

等待 : 同时释放锁资源和CPU的执行权
    void wait() : 无限等待
    void wait(long timeout) : 限时等待
    
唤醒 : 
    void notify() : 随机唤醒一个正在等待的线程对象
    void notifyAll() : 把所有正在等待的线程对象唤醒
    
一般使用锁对象调用等待和唤醒的方法 : //锁对象被所有的线程对象共享    
    所以等待唤醒的方法来自于 : Object 类 //锁对象可以是任意类型的引用数据类型对象

等待唤醒案例(线程之间的通讯)

包子案例:

BaoZi.java

import java.io.Serializable;
import java.util.Objects;

/*
    1. 事物描述类
    2. 属性 : 状态 -> 包子有无  -> 控制多个线程
    3. 锁对象的类型
    4. 增强了程序的趣味性  : 属性 -> 馅
 */
public class BaoZi implements Serializable {
    private static final long serialVersionUID = -1927890984971954740L;
    //包子馅
    private String xianer;
    //包子状态
    private boolean flag;//false

    public BaoZi() {
    }

    public BaoZi(String xianer, boolean flag) {
        this.xianer = xianer;
        this.flag = flag;
    }

    public String getXianer() {
        return xianer;
    }

    public void setXianer(String xianer) {
        this.xianer = xianer;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BaoZi baoZi = (BaoZi) o;
        return flag == baoZi.flag &&
                Objects.equals(xianer, baoZi.xianer);
    }

    @Override
    public int hashCode() {
        return Objects.hash(xianer, flag);
    }

    @Override
    public String toString() {
        return "BaoZi{" +
                "xianer='" + xianer + '\'' +
                ", flag=" + flag +
                '}';
    }
}

Customer.java

import java.io.Serializable;
import java.util.Objects;

/*
    顾客线程类
        1. 线程类 -> extends Thread
        2. 事物描述类
                姓名 : 继承Thread
                包子
 */
public class Customer extends Thread implements Serializable {
    private static final long serialVersionUID = -1260624229038066665L;
    //属性 : 包子属性
    private BaoZi bz;

    public Customer() {

    }

    public Customer(String name, BaoZi bz) {
        super(name);
        this.bz = bz;
    }

    public BaoZi getBz() {
        return bz;
    }

    public void setBz(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Customer customer = (Customer) o;
        return Objects.equals(bz, customer.bz);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bz);
    }

    @Override
    public String toString() {
        return "Customer{" +
                "bz=" + bz +
                '}';
    }

    //线程类
    @Override
    public void run() {
        //死循环
        while(true){
            //上锁
            synchronized (bz){

                //什么时候顾客线程需要等待
                if (!bz.isFlag()){//bz.isFlag() == false
                    //顾客线程需要等待
                    try {
                        bz.wait();//释放锁和CPU执行权
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //能从上面下来,说明包子的状态是 true
                System.out.println("姓名是 : " + getName() + " 的顾客正在吃 " + bz.getXianer() + " 的包子~~");
                //吃包子花时间
                try {
                    Thread.sleep(2000);//不释放锁资源 释放CPU执行权
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName() + " : 包子吃完了, 老板再来一份~");
                System.out.println("-------------------------------");
                //真正吃包子的代码
                bz.setFlag(false);
                //唤醒厨师线程
                bz.notify();

            }
        }
    }
}

Cook.java

import java.io.Serializable;
import java.util.Objects;

/*
    1. 事务描述类 : 厨师
    2. 线程类 : run
 */
public class Cook extends Thread implements Serializable {
    private static final long serialVersionUID = -6881458292011075477L;
    //属性 : 包子
    private BaoZi bz;

    public Cook() {

    }

    public Cook(String name, BaoZi bz) {
        super(name);
        this.bz = bz;
    }

    public BaoZi getBz() {
        return bz;
    }

    public void setBz(BaoZi bz) {
        this.bz = bz;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Cook cook = (Cook) o;
        return Objects.equals(bz, cook.bz);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bz);
    }

    @Override
    public String toString() {
        return "Cook{" +
                "bz=" + bz +
                '}';
    }

    int count = 0;

    @Override
    public void run() {
        while(true){
            //给代码上锁
            synchronized (bz){

                //什么时候厨师休息
                if (bz.isFlag()){//bz.isFlag() == true
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //能下来说明 没有包子 包子的状态是false
                //奇数次和偶数次做的包子馅不一样
                if (count % 2 == 0){
                    bz.setXianer("茴香肉末馅");
                }else{
                    bz.setXianer("西葫芦鸡蛋馅");
                }

                System.out.println("姓名是 : " + getName() + " 的厨师正在做 " + bz.getXianer() + " 的包子~~");

                //吃包子花时间
                try {
                    Thread.sleep(4000);//不释放锁资源 释放CPU执行权
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName() + " : 香喷喷的 "+bz.getXianer()+" 包子出炉啦, 快来抢购吧~");

                //真正做包子的代码
                bz.setFlag(true);
                //唤醒顾客
                bz.notify();
                //统计变量
                count++;
            }
        }
    }
}

测试类Demo:

public class Demo {
    public static void main(String[] args) {
        //创建包子对象
        BaoZi bz = new BaoZi();
        //创建线程对象
        Customer customer = new Customer("猪八戒",bz);
        Cook cook = new Cook("中华小当家",bz);

        //启动线程
        customer.start();
        cook.start();

    }
}

运行结果:

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值