Day23JavaSE——多线程&实现

Day23JavaSE——多线程&实现

多线程
		进程的概述和多进程的意义
		线程的概述和多线程的意义
		JVM运行原理以及JVM启动的线程探讨
		实现多线程
		线程调度
		线程控制

进程的概述和多进程的意义

//我们要学习线程,先要了解进程
        //线程依赖于进程存在
        //进程:正在运行的应用程序(QQ,微信,IDEA)
        //现在的计算机支持多进程,可以运行多个进程

        //开启进程后,会执行很多任务,这个任务称之为线程
        //比如QQ音乐开启后,其实就是一个进程,其中个下载,听音乐都是QQ音乐下的线程

        //在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。
        //所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。

        //因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
        //所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。

        /** 多线程的意义
         * 多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
         * 	那么怎么理解这个问题呢?
         * 	我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
         * 	CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
         * 	中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
         * 	中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.*/

并行和并发的区别

 /** 并行和并发
         * 并发是逻辑上同时发生,指在某一个时间内同时运行多个程序。
         * 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行
         *
         * 并行是物理上同时发生,指在某一个时间点同时运行多个程序。
         * 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理,
         	多线程并非是如果你开两个线程同时执行多个任务。
         *  执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果",
         *  其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已.
         *  就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来
         *  当你把这个过程以n倍速度执行时.可以想象一下.*/

Java程序运行原理和JVM的启动是多线程的吗

tXLdaj.png

 /** JVM的启动与运行原理
         * Java程序运行原理
         * Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
         * 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
         * 所以 main方法运行在主线程中。
         * JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。*/

多线程实现

 /** 多线程实现的方式
         * 由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
         * 而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
         * 但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
         * 但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
         * 由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
         * 然后提供一些类供我们使用。我们就可以实现多线程程序了。*/

        //Java给我们提供好了一个类 Thread 通过这个类,进创建线程,和开启线程。
        //Thread
        //线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

        //我们如何使用Thread这个类来创建子线程。

方式一

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VTrTEUCs-1592322948568)(https://s1.ax1x.com/2020/06/13/tXqZ7j.png)]

//自定义线程需要继承Java提供的Thread类
public class MyThread extends Thread{
    //ctrl+O c重写父类的里面的方法
    //run()方法是让线程来调用的方法,不能直接调用
    @Override
    public void run() {
        //run 方法里面写的代码就是子线程执行的耗时代码
        //一些耗时且不会影响主线程下一步执行的代码就会被写入子线程
        //模拟耗时操作
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName()+"--"+i);
        }
    }
}
/*  创建新执行线程有两种方法。
      方法1:
        1.将一个类声明为 Thread 的子类。
        2.该子类应重写 Thread 类的 run 方法。
        3.接下来可以分配并启动该子类的实例。*/
        System.out.println("当主线程执行到这个节点处,可能我会有一个耗时的操作,比如复制文件,在这个节点处开启一个子线程来执行耗时的代码");
        //开启线程 调用run()方法,其实子线程并没有开启,这种方式其实就是你创建了一个类的对象,调用了类中的一个 方法而已。
        //myThread.run();
        //创建一个线程
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        System.out.println("主线程执行了");
        //正确开启一个线程的方式是调用 start();当线程开启后,由子线程去调用run()方法来执行run()方法里面的代码
        //线程不要重复开启 重复开启会抛异常。IllegalThreadStateException
        myThread.start();
        System.out.println("主线程执行了");
        System.out.println("主线程执行了");
        System.out.println("主线程执行了");
        System.out.println("主线程执行了");
        System.out.println("主线程执行了");
        myThread1.start();
        System.out.println("主线程执行了");
    }
}
public class Test2 {
    public static void main(String[] args) {
        System.out.println("===========获取当前执行线程对象===========");
        //currentThread(); 获取当前执行的线程对象
        Thread thread = Thread.currentThread();
        System.out.println(thread);
        System.out.println("=========设置当前执行线程对象名称=========");
        thread.setName("主线程");
        System.out.println("============获取当前执行线程名===========");
        String name = thread.getName();
        System.out.println(name);
        //多个线程并发执行()多个抢占CPU的执行权,哪个线程抢到,在某一个时刻,就会执行哪个线程。线程的执行具有随机性。
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        MyThread th3 = new MyThread();
        th1.setName("小王");
        th2.setName("Tom");
        th3.setName("Adair");
        th1.start();
        th2.start();
        th3.start();
    }
}

案例利用多线程复制文件

分别创建子线程复制MP3文件和MP4文件,都要继承Thread类

public class copyMp3 extends Thread {
    @Override
    public void run() {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream("C:\\Users\\adair_liu\\Desktop\\复试资料\\Jam - 七月上.mp3");
            out = new FileOutputStream("多线程复制MP3.mp3");
            byte[] buffer = new byte[1024 * 8];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                if(in!=null){
                    in.close();
                }
                if(out!=null){
                    out.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}
public class copyVideo extends Thread{
    @Override
    public void run() {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream("C:\\Users\\adair_liu\\Desktop\\复试资料\\[迅雷下载Www.99b.Cc]入殓师1280x720日语中字版.mp4");
            out = new FileOutputStream("多线程复制MP4.mp4");
            byte[] buffer = new byte[1024 * 20];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                if(in!=null){
                    in.close();
                }
                if(out!=null){
                    out.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

主线程

public class Test1 {
    public static void main(String[] args) {
        System.out.println("主线程代码执行");
        copyMp3 copyMp3 = new copyMp3();
        copyVideo copyVideo = new copyVideo();
        copyMp3.setName("复制音频");
        copyVideo.setName("复制视频");
        copyMp3.start();
        copyVideo.start();
        System.out.println("主线程下面的代码");
    }
}

线程优先级问题

//自定义线程需要继承Java提供的Thread类
public class MyThread extends Thread{
    //ctrl+O c重写父类的里面的方法
    //run()方法是让线程来调用的方法,不能直接调用
    @Override
    public void run() {
        //run 方法里面写的代码就是子线程执行的耗时代码
        //一些耗时且不会影响主线程下一步执行的代码就会被写入子线程
        //模拟耗时操作
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName()+"--"+i);
        }
    }
}
 /**
         * 如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
         *     线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
         * B:线程有两种调度模型:
         *     分时调度模型     所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
         *     抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
         *                 优先级高的线程获取的 CPU 时间片相对多一些。
         *                 Java使用的是抢占式调度模型。
         * C:如何设置和获取线程优先级
         *     public final int getPriority() //获取线程的优先级
         *     public final void setPriority(int newPriority)//设置线程的优先级*/

        /**
         * 注意事项:  有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,那是为什么呢?
         * - 因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,
         * - 所以有的时候一两次的运行说明不了问题*/
public static void main(String[] args) {
        MyThread th1 = new  MyThread();
        MyThread th2 = new MyThread();
        MyThread th3 = new MyThread();

        //设置线程的优先级 范围1--10
        th1.setPriority(1);
        th2.setPriority(2);
        th3.setPriority(Thread.MAX_PRIORITY);
        //多个线程并发执行()多个抢占CPU的执行权,哪个线程抢到,在某一个时刻,就会执行哪个线程。线程的执行具有随机性。
        //获取线程的优先级
        int priority1 = th1.getPriority();
        int priority2 = th2.getPriority();
        int priority3= th3.getPriority();
        //线程默认的优先级是5
        System.out.println("线程的优先级"+ priority1);
        System.out.println("线程的优先级" + priority2);
        System.out.println("线程的优先级" + priority3);

        th1.setName("范冰冰");
        th2.setName("刘亦菲");
        th3.setName("张曼玉");
        th1.start();
        th2.start();
        th3.start();
    }
}

线程控制

//自定义线程需要继承Java提供的Thread类
public class MyThread extends Thread{
    public MyThread(){}

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

    //ctrl+O c重写父类的里面的方法
    //run()方法是让线程来调用的方法,不能直接调用
    @Override
    public void run() {
        //run 方法里面写的代码就是子线程执行的耗时代码
        //一些耗时且不会影响主线程下一步执行的代码就会被写入子线程
        //模拟耗时操作
        for (int i = 0; i < 10; i++) {
            String name = Thread.currentThread().getName();
            System.out.println(name+"--"+i);
        }
    }
}
public class MyThread1 extends Thread{
    public MyThread1(){}

    public MyThread1(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            String name = Thread.currentThread().getName();
            //线程礼让
            Thread.yield();
            System.out.println(name+"--"+i);
        }
    }
}
public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(("=========休眠线程========="));
        /**
        * static void sleep(long millis)
        * 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
        * static void sleep(long millis, int nanos)
        * 导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),这取决于系统定时器和调度器的精度和准确性。*/
        System.out.println("主线程开始执行了");
        Thread.sleep(5000);
        System.out.println("主线程下面的代码");
        //线程休眠:可以让当前正在执行的线程, 睡一会。
        //Thread(String name) 分配新的 Thread 对象。
        //通过有参构造,可以给线程起个名字
        MyThread tha = new MyThread("线程A");
        MyThread thb = new MyThread("线程B");
        tha.start();
        thb.start();

        System.out.println("=========加入线程=========");
        /**A:
         *         加入线程:
         *         public final void join ()
         *         意思就是:等待该线程执行完毕了以后, 其他线程才能再次执行
         *         注意事项:
         *         在线程启动之后, 在调用方法
         *         join ()可以让多个线程并发执行,变成串行(挨个排队执行,不用抢)*/
        MyThread th1 = new MyThread("小张");
        MyThread th2 = new MyThread("小王");
        MyThread th3 = new MyThread("小孙");
        th1.start();
        //在线程启动之后,再调用join()方法
        th1.join();
        th2.start();
        th2.join();
        th3.start();
        th3.join();

        System.out.println("=========礼让线程=========");
        /**
         * static void yield()
         * 暂停当前正在执行的线程对象,并执行其他线程。*/
        //在自定义线程中调用
        Thread thLR = new MyThread1("礼让的线程");
        thLR.start();

        System.out.println("=========守护线程=========");
        /**
         * void setDaemon(boolean on)
         * 将该线程标记为守护线程或用户线程。*/
        Thread.currentThread().setName("刘备:主线程");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"=="+i);
        }

        MyThread th4 = new MyThread();
        MyThread th5 = new MyThread();
        th4.setName("张飞");
        th5.setName("关羽");
        //设置为守护线程 当主线程死亡后,守护线程要立马死亡掉。
        //注意:setDaemon(true)该方法必须在启动线程前调用。
        th4.setDaemon(true);
        th5.setDaemon(true);
        th4.start();
        th5.start();
        System.out.println(Thread.currentThread().getName()+"退出了");

        System.out.println("=========中断线程=========");
        /**
         *  void interrupt()
         *  中断线程。*/
        MyThread th6 = new MyThread();
        th6.setName("停止线程");
        th6.start();
        Thread.sleep(5000);
        //让线程死亡
        //清除线程的阻塞状态
        th6.interrupt();
    }
}

实现多线程方式二(解决了Java中只能单继承问题)

  //创建线程的第二种方式
        /*
        * 1、定义一个类实现Runnable接口
        * 2、重写该接口的run方法
        * 3、然后就可以分配该类的实例
        * 4、在创建Thread对象时作为一个参数来传递并启动*/
        //这种方式扩展性强,实现一个接口,还可以再去继承其他类
        //可以避免由于Java单继承带来的局限性。
        /*Thread(Runnable target)
        分配新的 Thread 对象。*/
        //Runnable 任务 接口应该由那些打算通过某一线程执行其实例的类来实现

让线程实现一个Runnable接口

public class MyRunnable implements Runnable{
    //让线程来执行的方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Thread th = Thread.currentThread();
            System.out.println(th.getName() + "===" + i);
        }
    }
}
public class MyRunnable2 implements Runnable{
    @Override
    public void run() {
        System.out.println("另外一个任务");
    }
}
public class Test1 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread th1 = new Thread(myRunnable);
        //另外的Runnable类就是另一个任务,不会执行
        Thread th2 = new Thread(new MyRunnable2());
        Thread th3 = new Thread(new MyRunnable());
        Thread th4 = new Thread(myRunnable);
        th1.setName("线程A");
        th2.setName("线程B");
        th3.setName("线程C");
        th4.setName("线程D");
        th1.start();
        th2.start();
        th3.start();
        th4.start();
    }
}
public class Test2 {
    public static void main(String[] args) {
        /**
         * Thread(Runnable target, String name)
         *  分配新的 Thread 对象。*/
        MyRunnable myRunnable = new MyRunnable();
        Thread tha = new Thread(myRunnable, "线程A");
        Thread thb = new Thread(myRunnable, "线程B");
        tha.start();
        thb.start();
    }
}

实现多线程方式3

//创建线程的第三种方式
       /* C:
        实现步骤
        1. 创建一个类实现Callable 接口 重写接口中的call方法
        2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
        3. 创建Thread类, 将FutureTask对象作为参数传进去
        4. 开启线程*/
        
        执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。  
        FutureTask 是  Future 接口的实现类

        /** FutureTask类创建了一个任务,这个任务在执行时执行传入的Callable或Runnable
         * 再将这个任务传入线程中
         * FutureTask(Callable<V> callable)
         * 创建一个 FutureTask ,它将在运行时执行给定的 Callable 。
         * FutureTask(Runnable runnable, V result)
         * 创建一个 FutureTask ,将在运行时执行给定的 Runnable ,并安排 get将在成功完成后返回给定的结果。  */
public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //我们有一种需求:当子线程执行完,我想获取子线程执行完之后的结果。
        MyCallable myCallable = new MyCallable(100);
        //创建一个任务
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread th = new Thread(task);
        th.start();

        //获取线程执行完之后返回的结果
        Integer integer = task.get();
        System.out.println(integer);

        MyCallable myCallable1 = new MyCallable(1000);
        FutureTask<Integer> task1 = new FutureTask<>(myCallable1);
        Thread th2 = new Thread(task1);
        th2.start();
        System.out.println(task1.get());

        //Runnable 任务 让线程来执行 run() 没有返回值,这个方法不能抛出异常,只能抓

        //Callable 任务 让线程来执行 call() 有返回值,而且可以抛出异常。
    }
}
public class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num){this.num=num;}

    //call()方法 让线程来执行的方法
    @Override
    public Integer call() throws Exception {
        //求1-num之间的和
        int sum=0;
        for (int i = 0; i < num; i++) {
            sum+=i;
        }
        return sum;
    }
}

卖电影票案例

tXqoDg.png

继承Thread类的方式卖电影票案例

public class CellThread extends Thread{
    //设置成员变量,让三个线程共享
    static int ticket = 100;
    public CellThread(String name){
        super(name);
    }

    @Override
    public void run() {
        while (true) {
            if(ticket>=1){
                System.out.println(this.getName() + "正在出售第:" + (ticket--) + "张票");
            }
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        /*A:
        案例演示
        需求:某电影院目前正在上映贺岁大片,共有100张票,
        而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
        通过继承Thread类实现*/
        //三个串口并发执行售票
        CellThread win1 = new CellThread("窗口1");
        CellThread win2 = new CellThread("窗口2");
        CellThread win3 = new CellThread("窗口3");
        win1.start();
        win2.start();
        win3.start();
    }
}

实现Runnable接口的方式卖电影票

public class CellRunnable implements Runnable{
    static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if(ticket>=1){
                System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + "张票");
            }
        }
    }
}
public class Test2 {
    public static void main(String[] args) {
        /*A:
        案例演示
        需求:某电影院目前正在上映贺岁大片,共有100张票,
        而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
        通过Runnable接口实现*/

        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3 = new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

## 买电影票出现了同票和负数票的原因分析

public class Test3 {
    public static void main(String[] args) {
        //创建了一次任务 100
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3= new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
        //窗口1正在出售第:-1张票
        //输出了-1张票
        //我们写的这段售票代码,出现了线程安全问题。多线程的环境下,在对共享数据进行操作时,有可能会出现线程安全问题。
        //为什么会出现线程安全问题,以及我们如何来解决他,且听下回分解。
    }
}
public class CellRunnable implements Runnable{
    static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if(ticket>=1){
                //模拟一下真实的售票环境,有网络延迟。
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + "张票");
            }
        }
    }
}

线程安全问题

//线程安全问题,出现线程安全问题,要符合以下三个条件
        //1.是否是多线程环境
        //2.多条线程,有没有共享数据。
        //3.有没有多条语句去操作这个共享数据。
        //出现线程安全问题,怎么解决。
        // synchronized (锁对象){需要同步的代码} 锁对象:是Java里面的任意一个对象,多个线程要共用一把锁对象。
        //同步方法  用的锁对象是this
        //静态同步方法 用的锁对象是 字节码对象。
        //Lock 这个类中 提供有 lock()  unlock() 进行加锁与释放锁。
        //死锁现象。

生产消费者线程案例

public class Test1 {
    public static void main(String[] args) {
        //不同种类线程间的通信问题,或者说线程的等待唤醒机制。
        //生产者线程
        //消费者线程
        //资源 学生对象 要让线程来共享数据

        //使用同一个锁才能使线程在执行完一次逻辑前不会被打断
        Student student = new Student();
        SetThread th1 = new SetThread(student);
        GetThread th2 = new GetThread(student);
        th1.start();
        th2.start();
        //Object 类中的方法,让线程进行等待,以及唤醒等待到的线程
        //void wait ()
        //在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
        //void wait(long timeout)
        // 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。

      /*  void notify ()
        唤醒在此对象监视器上等待的单个线程。
        void notifyAll ()
        唤醒在此对象监视器上等待的所有线程。*/

        // wait() 方法和 sleep() 方法的区别
        //共同点:都可以使线程处于阻塞状态
        //sleep() 必须设置时间量
        //wait() 方法可以设置时间量,也可以不设置时间量
        // wait() 一旦等待,就会释放锁。
        //sleep() 一旦休眠,不释放锁。
    }
}

消费者线程:如果没有资源,就等着,通知生成者去生产资源

public class GetThread extends Thread{
    private Student student;
    public GetThread(Student student){this.student=student;}

    @Override
    public void run() {
        while (true){
            synchronized (student){
                if(!student.flag){
                    //标志为false,等待生产资源
                    try{
                        student.wait();//等待就会释放锁,在哪里等待,被唤醒后就继续从这里执行
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                //有资源就直接消费
                System.out.println(student.name + "===" + student.age);
                //消费了就没有了
                //标志设为false,通知生产者线程去生产
                student.flag=false;
                student.notify();
            }
        }
    }
}

public class SetThread extends Thread{
    Student student;
    int i=1;
    public SetThread(Student student){
        this.student=student;
    }

    @Override
    public void run() {
        while (true){
            synchronized (student){
                if(student.flag){
                    //有资源,生成者线程就等待
                    try{
                        //th1
                        student.wait();//线程一旦等待,就要立马释放锁,在哪里等待,被唤醒后,就从哪里开始执行
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                //没有资源
                if(i%2==0){
                    //生产资源
                    student.name="张三";
                    student.age=24;
                } else {
                    //生产资源 th1
                    student.name = "李四";
                    student.age = 26;
                }
                //有了资源通知消费者线程去消费
                //修改标记
                student.flag=true;
                student.notify();//通知唤醒之后,线程还得再次争抢时间片
            }
            i++;
        }
    }
}
public class Student {
    public String name;
    public int age;
    //定义一个标记,表示是否有资源。false表示没有资源,true表示有资源
    public boolean flag=false;
}
public class Test1 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        while (true){
            if(myRunnable.getFlag()){
                System.out.println("进来执行了");
                break;
            }
        }
        //Java的内存模型:成员变量存储在主存中
        //每个线程都有自己的工作内存,他们要操作主存中的数据,就需要把注册中的数据读取到自己的工作内存中进行操作,操作完之后再写回主存
    }
}

class MyRunnable implements Runnable {
    //volatile 不能保证原子性的操作
    volatile boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public boolean getFlag() {
        return flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //修改flag的值为true
        flag = true;
        System.out.println("线程进来执行了" + flag);
    }
}

同步代码块解决线程安全问题

同步代码块的格式
	格式:
	synchronized(对象){
		需要同步的代码;
	}
	同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
同步的好处:	同步的出现解决了多线程的安全问题。
同步的弊端:	当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
  因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
  java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
  线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
  获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
  java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
  当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
  直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
  java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
  但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
  类锁是用于类的静态方法或者一个类的class对象上的。
  我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
  所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
  但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
  它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
同步方法:	就是把同步关键字加到方法上
同步代码块的锁对象: 	任意一个对象
同步方法的锁对象:   	是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
public class CellRunnable implements Runnable {
    /**
     * 同步代码块的格式
     * 格式:
     * synchronized(对象){
     * 需要同步的代码;
     * }
     * 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
     */
    //这个票让三个线程共享
    static int ticket = 100;
    //确保这个锁对象,只有一个,多个线程公用一把锁
    static Object obj = new Object();
    int i = 1;
    @Override
    public void run() {
        while (true) {
            //假如就剩最后一张票 int ticket=1;
            //th1、th2、th3
            if (i % 2 == 0) {
                synchronized (this) {
                    //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
                    if (ticket >= 1) {
                        //模拟一下真实的售票环境,有网络延迟
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
                    }
                }
            } else {
                maiPiao();
            }
            i++;
            //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
        }
    }

    //同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
    //同步方法:默认用的锁对象是this
    public synchronized void maiPiao() {
        if (ticket >= 1) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        //创建了一次任务100
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3 = new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
        //我们写的这段售票代码,出现了线程安全问题。多线程的环境下
        //在对共享数据进行操作时,有可能会出现线程安全问题。
        //这个时候出现了一些不合理的数据(数据安全问题)
        //1.出现0张票或负数票:原因,是由于线程的随机性所导致的。

        //2.出现相同的票:原因,就是由于线程的原子性所导致的  原子性(不可分割性)
        //线程对 (ticket-- 不是一个原子性操作他要对ticket这个变量要进行 读 改 写 三个操作 )


        //出现线程安全的问题,得符合三个条件
        //1.是不是多线程环境
        //2.多个线程有没有共享数据
        //3.有没有多条语句在操作同一个共享变量

        //那出现了 数据安全问题,我们怎么来解决呢?

        //我们把有可能出现数据安全问题的代码,使用同步代码块进行包裹

        //同步代码块:
        //synchronized (锁){放你认为有肯能出现问题的代码}
        //锁:你可以使用Java里面的任意一个对象,来充当锁,注意,多个线程要共享一把锁,才能锁住
    }
}
public class CellRunnable implements Runnable {
    static int ticket = 100;
    static Object obj = new Object();
    int i = 1;

    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {
                synchronized (CellRunnable.class) {
                    if (ticket >= 1) {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
                    }
                }
            } else {
                maiPiao();
            }
            i++;
        }
    }

    public static synchronized void maiPiao() {
        if (ticket >= 1) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3 = new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
        //同步代码块:
        //synchronized (锁){放你认为有肯能出现问题的代码}
        //锁:你可以使用Java里面的任意一个对象,来充当锁,注意,多个线程要共享一把锁,才能锁住
    }
}

同步方法锁

public class CellRunnable implements Runnable {
    static int ticket = 100;
    static Object obj=new Object();
    @Override
    public void run() {
        while (true){
            maiPiao();
            //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
        }
    }
    // 同步方法的使用的是 this 这个锁对象
    public synchronized void maiPiao(){
        //当th1这个线程进来同步代码块后,就持有了这个锁
        //其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
        if(ticket>=1){
            //模拟一下真实的售票环境,有网络延迟。
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第:" + (ticket--) + " 张票");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //利用锁方法
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3 = new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

JVM内存模型

tXq8uF.png

硬要对比的话,虚拟机栈可以对应多线程的工作内存,堆可以对应多线程的主存

() {
while (true){
maiPiao();
//th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
}
}
// 同步方法的使用的是 this 这个锁对象
public synchronized void maiPiao(){
//当th1这个线程进来同步代码块后,就持有了这个锁
//其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
if(ticket>=1){
//模拟一下真实的售票环境,有网络延迟。
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + “正在出售第:” + (ticket–) + " 张票");
}
}
}


```java
public class Test {
    public static void main(String[] args) {
        //利用锁方法
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3 = new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

JVM内存模型

[外链图片转存中…(img-MPtL7fDi-1592322948571)]

硬要对比的话,虚拟机栈可以对应多线程的工作内存,堆可以对应多线程的主存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值