Day 21 线程 & 锁

Thread

java能够直接开启多线程吗

Jdk提供了Thread类吗? 它不是就是可以吗…
Java不能直接开启多线程,线程是依赖于进程存在的,必须有进程才能线程!
而创建进程—需要创建系统资源 ,Java语言不能够创建系统资源的,只有c语言底层语
言能够操作系统资源,最底层的还是非Java语言实现;只是jdk为了使用方便将一些操作
线程的东西放在了Thread类中,但是里面的部分能也都是非java语言实现
start()---->开启线程的的方法,底层依赖于start0()—被native修饰:非Java语言实现(本地方法—跟系统相关的)

线程的状态

Thread类的内部枚举类型:
public enum State{
规定了6种状态
NEW ,新建
RUNNABLE,运行
BLOCKED,阻塞状态
WAITTING,等待(一直等待)
TIMED_WAITTING,超时等待
TERMINATED 终止死亡状态
}

/**
 * @author liutao
 * @date 2022/8/28 11:25
 *
 * 实现线程的创建方式第一种:继承关系
 * 实现步骤
 *  1)自定义一个类,继承自Thread类 (jdk提供的线程类),
 *  这个类就是线程类
 *  2)在自定义的类中重写Thread类的run方法
 *  3)在main线程(主线程,或者用户线程)
 *  创建当前自定义类对象--->就是线程对象,启动线程即可
 *
 *
 *  线程不知道哪一个线程?Thread类提供了一些方法,设置线程名称,获取线程名字
 *  public final void setName(String name):给线程设置名字
 *  public final String getName():获取线程名称
 *
 */

//自定义一个类继承Thread
public class MyThread  extends Thread{

    //重写Thread类的run方法
    @Override
    public void run() {
        //写代码:耗时的操作:循环/以后io流读写文件...
        for(int x = 0 ;x <200;x++){
            //public final String getName():获取线程名称
            System.out.println(getName()+":"+x) ;
        }
    }
}

public final void join() 线程礼让! throws InterruptedException

public static void yield():暂停当前正在执行的线程,并执行其他线程

让多个线程执行更和谐,

但是不能保证在抢占CPU的执行权的时候,A/B线程,A一次,B一次,也就是说明线程的执行随机性大

Thread类常量表:

下面自定义常量

public static final int MAX_PRIORITY 10 最大优先级

public static final int MIN_PRIORITY 1 最小优先级

public static final int NORM_PRIORITY 5 默认优先级

线程的优先级越大,只能说明它抢占到CPU的执行权的几率越大,但是不一定,因为线程的执行具有随机性!

public final void setPriority(int newPriority)设置优先级

/**
 * @author liutao
 * @date 2022/8/23 15:43
 * 创建线程的另一种方式:是实现一个种接口 方式 (推荐使用的,体现数据共享的概念)
 * 谁实现了Runable接口的run方法,谁就是资源类!(多个线程必须同时操作同一个资源)
 *
 * 实现步骤:
 *      1)自定义一个类实现Runnable接口,实现里面的run方法---"线程的具体执行的逻辑"
 *      2)在主线程中(main)
 *      创建实现Runable接口实现类---就是资源类对象
 *     创建 Thread类对象
 *      使用它完成public Thread(Runnable target,String name)
 *      在Thread构造方法中,将资源类对象作为参数传递!(多个线程必须同时操作同一个资源)
 *      3)启动线程 start()
 *
 */
//自定义一个类实现Runnable即可
public class MyRunnable  implements Runnable{
    @Override
    public void run() {
        for(int x = 0 ; x <100;x++){
            //如何获取线程名字?
            //Thread类提供静态功能:哪个线程进来,获取当前正在执行的线程,就获取线程名称
            //public static Thread currentThread()
            //public String getName():获取线程名字
//            System.out.println(getName()+":"+x);//getName()继承Thread类才能用,现在不是继承关系
            System.out.println(Thread.currentThread().getName()+":"+x);
        }
    }
}

线程操作的时候:目前现在两种方式,继承关系,实现Runnable接口关系

为什么接口的方式要好一些?

1)java面向接口编程,接口的提供的抽象方法,实现类必须实现这个方法

2)实现Runable接口的方式,体现数据共享的概念

SellTicket st = new SellTicket() ;//栈内存–指向堆内存地址

//创建三个线程

Thread t1 = new Thread(st,“窗口1”) ; //三个线程 都在操作同一个资源类st对象

Thread t2 = new Thread(st,“窗口2”) ;

Thread t3 = new Thread(st,“窗口3”) ;

3)第二种方式用到了"代理设计模式"

虽然方式2优于方式1,问题:刚才加入了网络延迟睡眠150毫秒,出现负票,也会出现同票(线程不安全!)

需要解决------------------>“Java的同步机制 synchronized 同步锁”,使用同步代码块解决

静态代理

/**
 * @author liutao
 * @date 2022/8/24 9:56
 * 设计模式:Java中23种 ---不是一种技术,它是一种"思想"(java编程语言中,根深蒂固的东西)
 * 分为三大类
 *      创建型设计模式(对象的创建):单例模式(重点),静态工厂方法模式(重点),工厂方法模式(重点)
 *      结构型设计模式(整个结构组成:抽象类,接口,..具体实现类...):代理模式(重点)
 *      行为型设计模式(功能型设计模式:使用具体的应用场景):适配器模式,装饰者设计模式(io流中就会用到)
 *
 *
 *      静态代理属于代理设计模式
 *
 *      代理模式      :核心思想:代理角色帮助真实角色完成一些事情  比如:过年回家排队买火车票
 *              静态代理: 代理角色和真实角色都需要实现同一个接口
 *                      线程的创建方式2:就使用到了静态代理
 *                      class MyRunnable implements Runnable{--->真实角色
 *                              //重写run
 *
 *                              //专注于run里面的真实内容
 *
 *
 *                      }
 *
 *                      class Thread  implements Runnable{//代理角色:帮助我们MyRunnable完成事情
 *                          private Runnable target;
 *
 *                          public Thread(Runnable target, String name) {
 *
 *                              //这里面完成 需要target赋值--->MyRunnable实现了Runnable接口
 *                          }
 *
 *
 *                          //重写run  public void run() {
                             *         if (target != null) {
                             *             target.run();//接口变量.run()
                             *         }
                             *     }
 *                      }
 *
 *
 *
 *              动态代理
 *                      jdk动态代理(jdk提供的Proxy类)  反射中就会用到
 *                      cglib动态代理(第三方jar包)
 *
 *
 *                      举例:
 *                          结婚这件事情,每个人都需要结婚
 *
 *
 *
 *                          线程创建的方式2优于方式1的最大好处:使用到了静态代理以及数据共享概念
 */

同步锁synchronized

/**
 * @author 刘涛
 * @date 2022/8/24 10:51
 *
 * 在实际开发中,有的时候同步代码块很少用,如果一个方法中的第一句话就是
 * synchronzied(锁对象){
 *     多条语句对共享数据的操作;
 * }
 * 将这个synchronized提到方法声明上,形成了同步方法(一般说的非静态的)
 *
 * 面试题
 *      1)同步方法的锁对象是谁呢? (一般说的都是非静态的)
 *      非静态同步方法的锁对象是 this:代表当前类对象的地址引用
 *
 *      2)静态的同步方法的锁对象是谁呢?--->静态的东西和类相关
 *      跟反射有关系,是当前类的字节码文件对象
 *      之前讲Object类---->Class getClass():获取字节码文件对象 方式1
 *      任意java类型的class属性--->第二种方式获取字节码文件对象
 */

生产者和消费者

/**
 * @author 刘涛
 * @date 2022/8/24 15:01
 * 模拟 生成者和消费思想模式,让线程之间的进行互相通信
 *
 * Student类--->学生数据(看成--->包子)
 *      name/age 姓名和年龄
 * SetThread类---->生产者线程操作的资源类--->给学生的信息赋值
 * GetThrad类----->消费者线程操作的资源类---->输出学生的信息
 * ThreadDemo类--->主线程main(用户线程)
 *
 * 需求1:先在SetThread类产生学生对象数据,在GetThrad类输出学生数据
 *
 * 问题: 发现数据是null 0 ,为什么呢?不同资源类对数据的操作应该同一个数据
 * 在SetThread里面new 一个学生对象
 * 在GetThread里面new 一个学生对象 ,不是同一个学生对象,没有数据!
 * 解决方案,通过构造方法进行数据传递;必须使用同一个学生对象!
 *
 * 需求2:生产者不断的产生数据,消费者不断的使用数据!---加入循环操作
 * 问题:数据紊乱,部分数据的名字和年龄不对应
 * 数据一打印一大片,是因为线程的执行的时候,cpu的一点点时间片足够线程执行很多次,数据可能一打印一大片
 *  数据紊乱,线程的执行具有随机性
 *
 *  说明程序不安全------>上午讲了---->同步代码块 (多线程的同步机制 synchronized)
 *              校验多线程安全问题的标准:
 *                      是否是多线程环境  是
 *                      是否存在共享数据   是
 *
 *                      是否有多条语句对共享数据的操作 有---->使用同步代码块将它包起来
 *
 *  问题:数据不会紊乱,但是一打印一大片,       ,cpu的一点点时间片足够线程执行很多次
 *  解决方案: 依次打印------------>使用Java的等待唤醒机制(重点)(等会马上讲)
 *              高圆圆 43
 *              张杨   30
 *              高圆圆 43
 *              张杨  30
 *              ...
 *              ...
 *              ...
 * wait()和notify()也是同步机制的一种,这两个必须一    块使用的 (解决线程通信)
 * 同步机制:synchonzied(锁对象){或者是同步方法
 *
 * }
 *
 */

引入等待唤醒机制(wait() + notify())

使用多线程加入了synchronized,解决安全问题,但是可能出现死锁现象
生产者和消费者模式思想:
生产者不断产数据,消费者不断使用数据
当生产者没有数据,等待产生数据,有数据了,唤醒(通知)消费者线程,来消费
当消费者有数据,等待先使用,没有数据了,唤醒(通知)生产者线程,来产生数据
wait()+notify()本身两个结合使用 ----都在synchronzied代码块中使用,就
属于"同步机制"
代码—昨天生产者SetThread产生学生数据,消费者GetThread使用学生数据
(标准写法)-------->“等待唤醒机制–信号灯法”

代码举例(模板代码)

Student
/**
 * @author 刘涛
 * @date 2022/8/24 15:04
 *
 * 学生类
 */
public class Student {
    //不加入私有修饰
    String name ; //姓名
    int age ; //年龄
    boolean flag ; //标记:是否有数据,false没有数据;true有数据
    //默认值就是false


}
SetThread
/**
 * @author 刘涛
 * @date 2022/8/24 15:05
 *
 *
 */
//生产者资源类
public class SetThread  implements Runnable {
    //声明学生类型变量
    private Student s;
    private int x = 0 ; //统计变量
    public SetThread(Student s){
        this.s = s ;
    }

    @Override
    public void run() {
        //生产者线程先抢占到了, x = 0
        //不断的产生数据
        while(true){

            //调用学生 类的同步方法:产生数据

            synchronized (s){ //锁对象可以是任意Java类对象
                //判断
                if(s.flag){ //生产者没有数据,等待产生数据
                    //使用Object提供了一个方法wait()线程等待

                    try {
                        s.wait();//锁对象调用
                        //wait()特点:当被锁对象调用,会立即释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if(x%2==0){
                    //产生学生数据
                    // Student s = new Student() ;
                    s.name ="刘涛" ;
                    s.age = 43 ;//有数据了 ,消费者所在的线程就输出(使用)数据

                }else{

                    s.name = "胡志兰" ;
                    s.age = 30 ;
                }
            x++ ;//不断++   x=1

                //修改标记 信号灯法
                s.flag = true ;//有数据了
                //唤醒(通知)消费者线程,赶紧来使用
                //锁对象调用者这些方法
                s.notify();
            }


            记录  张杨 ,准备使用年龄赋值30,这个时候,第二个线程消费者抢占到了
            //线程的执行随机性,就会直接输出  s.name = "张三" ,s.age = 43 上一次年龄的值

        }


    }
}
GetThread
/**
 * @author 高圆圆
 * @date 2022/8/24 15:05
 */
//消费者资源类
public class GetThread  implements  Runnable{
    private Student s ;
    public GetThread(Student s){
        this.s = s ;
    }
    @Override
    public void run() {
        //不断的使用数据(消费数据)
        while(true){
            //调用学生类的同步方法:产生学生数据

            synchronized (s){

                //如果有数据,等待消费者先使用数据
                if(!s.flag){
                    try {
                        s.wait();
                        //wait()特点:当被锁对象调用,会立即释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //Student s  = new Student();
                //输出学生数据
                System.out.print(s.name+" ");//刘涛
                System.out.print(s.age); //43
                System.out.println();//换行


                //消费完了,没有数据了,修改标记
                s.flag = false ;
                //唤醒(通知)生成者线程,赶紧来产生数据
                s.notify();
            }

        }



    }
}
ThreadDemo
public class ThreadDemo {
    public static void main(String[] args) {

        //创建一个学生对象
        Student s  = new Student();//同一个学生对象
        //创建生产者资源类对象
        SetThread st = new SetThread(s) ;
        //创建消费者子资源类对象
        GetThread gt = new GetThread(s) ;

        //创建线程对象
        Thread t1 = new Thread(st) ;
        Thread t2 = new Thread(gt) ;

        //启动线程
        t1.start();
        t2.start();

     }
}

//ABCDEFGHJK 第一个线程  输出这些字母
//0123456789 第二个线程  输出数字
//展示结果---A0B1C2D3.......----考的就等待唤醒机制

线程池

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author 刘涛
 * @date 2022/8/24 16:54
 *
 * 创建线程的方式有前两种了
 *      1)继承关系
 *      2)实现Runnable接口的方式,使用到了静态代理
 *      为什么要有第三种方式呢?线程池
 *
 *      使用步骤
 *         1)创建线程池 Executors:工厂类(提供一些静态的方法) 专门创建线程池
 *              public static ExecutorService newFixedThreadPool(int nThreads)创建
 *              一个固定的可重用的线程数的线程池
 *              返回值是一个接口---->底层原码---肯定返回的接口的子实现类对象
 *
 *         2)提交异步任务 ExecutorService:线程池---接口里面的方法都要被ThreadPoolExecutor子实现类实现
 *
 *              <T> Future<T> submit(Callable<T> task):提交异步任务,并返回结果处理
 *              这个方法的返回值Future接口---代表的具体的处理结果,
 *              如果仅仅是看一下多个线程互相抢占CPU执行权的效果,返回值可以不写;
 *              如果要进行线程计算结果,必须有返回结果
 *                   参数:Callable: 接口,需要接口的子实现类对象(执行的任务---类似于Runnable接口)
 *                     allable里面的方法call---->类似于Runnable的run 方法---耗时的操作,线程执行的业务代码
 *          3)关闭线程池
 *          void shutdown():关闭之后,不会接受任何新任务
 *
 */
public class ThreadDemo {
    public static void main(String[] args) {
        //创建线程池
        // public static ExecutorService newFixedThreadPool(int nThreads)
        ExecutorService es = Executors.newFixedThreadPool(2);//创建2条线程

        //提交异步任务结果
        /**
         * <T> Future<T> submit(Callable<T> task):提交异步任务,并返回结果处理
         *                这个方法的返回值Future接口---代表的具体的处理结果,
         *              如果仅仅是看一下多个线程互相抢占CPU执行权的效果,返回值可以不写;
         *               如果要进行线程计算结果,必须有返回结果
         */
        Callable c = new MyCallable() ;//接口多态
        es.submit(c) ; //第一次提交异步任务----->相当于---线程对象.start()
        es.submit(c) ; //第二此提交异步任务----
        
        //线程池会默认给线程一个name
        //关闭线程池
        es.shutdown();

    }
}

递归

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值