首页 > 编程语言 >Java并发(线程状态、线程调度、线程同步)

Java并发(线程状态、线程调度、线程同步)

时间:2022-10-13 15:55:17浏览次数:49  
标签:Java Thread void 并发 线程 sleep 方法 public

Java并发(线程状态、线程调度、线程同步)

线程状态

​ 线程共有5种状态,在特定情况下,线程可以在不同的状态之间切换。

  1. 5种具体状态

    • 创建状态:实例化一个新的线程对象,还未启动。
    • 就绪状态:创建好的线程对象调用start方法完成启动,进入线程池等待抢占CPU资源。
    • 运行状态:线程对象获取了CPU资源,在一定的时间内执行任务。
    • 阻塞状态:正在运行的线程暂停执行任务,释放所占用的CPU资源,并在解除阻塞状态之后也不能直接回到运行状态,而是重新回到就绪状态,等待获取CPU资源。
    • 终止状态:线程运行完毕或因为异常导致该线程终止运行。
  2. 线程状态的转换

    线程状态转换

线程调度

  1. 线程休眠

    让当前线程暂停执行,从运行状态进入阻塞状态,将CPU资源让给其他线程的调度方式,通过sleep()来实现。sleep(long millis),调用时需要传入休眠时间,单位为豪秒。

    public static native void sleep(long millis) throws InterruptedException;
    

    sleep是静态本地方法,可以通过类调用,也可以通过对象调用,方法定义抛出InterruptedException异常,lnterruptedException继承Exception,外部调用时必须手动处理异常。

    在类中调用sleep方法

    public class MyRunnable extends Thread  {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if (i==5){
                    //单位是毫秒 所以5000毫秒即5秒 同时sleep需要处理抛出的异常
                    try {
                        sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(i+"-----MyRunnable");
                }
            }
        }
    }
    

    也可以在类的外部调用sleep方法

    public static void main(String[] args) throws InterruptedException {
           MyThread myThread = new MyThread();
           try {
               myThread.sleep(5000);
           }catch (InterruptedException e){
               e.printStackTrace();
           }
           myThread.start();
        }
    

​ 在外部调用需要注意,休眠一定要放在启动之前。否则如果启动后马上休眠不一定能够休眠成功。

​ 也可以通过这种方法来使主线程(main)休眠,即直接通过静态方式调用sleep方法。

如果线程采用的是实现Runnable接口的方式,那么没办法直接使用sleep()方法。因为sleep()是Thread类里 的方法,所以其可以直接用。而Runnable只是接口,其里面并没有sleep()方法,只能采用Thread.sleep() 这种方法。

  1. 线程合并

    合并是指将指定的某个线程加入到当前线程中,合并为一个线程,由两个线程交替执行变成一个线程中的两个自线程顺序执行。通过调用join方法来合并,具体操作如下。

    线程甲和线程乙,线程甲执行到某个时间点的时候调用线程乙的join方法,则表示从当前时间点开始CPU资源被线程乙独占(即乙.jion()后便到乙线程执行了),线程甲进入阻塞状态,直到线程乙执行完毕,线程甲进入就绪状态,等待获取CPU资源进入运行状态。

    join方法重载,join()表示乙线程执行完毕之后才能执行其他线程,join(long millis)表示乙线程执行millis 毫秒之后,无论是否执行完毕,其他线程都可以和它争夺CPU 资源。

    public final void join() throws InterruptedException {
            join(0);
        }
    
    public final synchronized void join(long millis)
        throws InterruptedException {
            //源代码太长 在这里就不贴出来了
        }
    

    使用方法如下

    public class test {
        public static void main(String[] args) throws InterruptedException {
           MyThread myThread = new MyThread();
           myThread.start();
           try {
               for (int i = 0; i < 5000; i++) {
                   //在i=1500时候main线程合并了MyThread线程,此时切换为MyThread线程进行
                    if (i==1500){
                        myThread.join();
                    }
                   System.out.println(i+"----main");
               }
           }catch (InterruptedException e){
               e.printStackTrace();
           }
        }
    }
    class MyThread extends Thread  {
        @Override
        public void run() {
            for (int i = 0; i < 5000; i++) {
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i+"-----MyThread");
            }
        }
    }
    
  2. 线程礼让

    线程礼让是指在某个特定的时间点,让线程暂停抢占CPU资源的行为,运行状态/就绪状态---》阻塞状态,将CPU资源让给其他线程来使用。

    假如线程甲和线程乙在交替执行,某个时间点线程甲做出了礼让,所以在这个时间节点线程乙拥有了CPU资源,执行业务逻辑,但不代表线程甲一直暂停执行。线程甲只是在特定的时间节点礼让,过了时间节点,线程甲再次进入就绪状态,和线程乙争夺CPU资源。

     public static native void yield();
    

    使用方法如下

    public class test {
        public static void main(String[] args) throws InterruptedException {
           YieldThread1 yieldThread1 = new YieldThread1();
           YieldThread2 yieldThread2 = new YieldThread2();
           yieldThread1.start();
           yieldThread2.start();
        }
    }
    class YieldThread1 extends Thread  {
        @Override
        public void run() {
            for (int i = 0; i < 5000; i++) {
                //当i=2000到输出语句之间就是YieldThread1礼让的时间 此后500次都是这样
                if (i>=2000&&i<=2500){
                    Thread.yield();
                }
                System.out.println(i+"-----YieldThread1");
            }
        }
    }
    class YieldThread2 extends Thread  {
        @Override
        public void run() {
            for (int i = 0; i < 5000; i++) {
                System.out.println(i+"-----YieldThread2");
            }
        }
    }
    

    要注意礼让的时间很短,所以可能才刚下CPU,马上的又上CPU了

  3. 线程中断

    有很多种情况会造成线程停止运行:

    • 线程执行完毕自动停止。

    • 线程执行过程中遇到错误抛出异常并停止线程。

    • 执行过程中根据需求手动停止。

    Java中实现线程中断有如下几个常用方法:

    • public void stop()

    • public void interrupt()

    • public boolean isInterrupted()

      stop方法在新版本JDK中已经不推荐使用。因为这就是相当于想关电脑却直接拔电源这种简单粗暴的方式。所以重点关注后两种方法。

      interrupt是一个实例方法,当一个线程对象调用该方法时,表示中断当前线程对象。每个线程对象都是通过一个标志位来判断当前是否为中断状态。

      isInterrupted就是用来获取当前线程对象的标志位:true表示清除了标志位,当前线程已经中断;false表示没有清除标志位,当前对象没有中断。

    当一个线程对象处于不同的状态时,中断机制也是不同的。

    ​ 创建状态:实例化线程对象,不启动。

    public class test {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread();
            System.out.println(thread.getState());
            thread.interrupt();
            System.out.println(thread.isInterrupted());
            /**
             * 输出结果为
             * NEW
             * false
             */
        
    }
    

    ​ NEW表示当前线程对象为创建状态,false表示当前线程并未中断,因为当前线程根本没有启动就不存在 中断,不需要清楚标志位。

    ​ 运行状态:

    ​ 具体来说,当对一个线程,调用 interrupt() 时,
    ​ ① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
    ​ ② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。

    public class test {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        System.out.println("线程中"+i);
                    }
                }
            });
            System.out.println("新建立线程时候的状态"+thread.getState());
            thread.start();
            System.out.println("线程启动后的状态"+thread.getState());
            thread.interrupt();
            System.out.println("线程中断后的中断标志"+thread.isInterrupted());
            System.out.println("线程中断后的状态"+thread.getState());
        }
        /**
         * 输出为:
         * 新建立线程时候的状态NEW
         * 线程启动后的状态RUNNABLE
         * 线程中断后的中断标志true
         * 线程中断后的状态RUNNABLE(因为interrupt只是设置中断标志而不影响线程运行)
         */
    }
    
    

    总结:

    Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
    也就是说,一个线程如果有被中断的需求,那么就可以这样做。
    ① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
    ② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)

线程同步

  1. 定义

    Java中允许多线程并行访问,同一时间段内多个线程同时完成各自的操作。多个线程同时操作同一个共享数据时,可能会导致数据不准确的问题。

    public class test {
        public static void main(String[] args) {
            //数据共享需要把这个对象放在外面,而不是放在for循环里面,只有这样才能是数据共享。(虽然操作的是静态变量,但这是Java,讲的是对象,放在for循环里则对象不一样)
            MyRunnable myRunnable= new MyRunnable();
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(myRunnable,"线程"+i);
                thread.start();
            }
            /**
             * 输出结果为
             * 线程0是当前的第1位访客
             * 线程3是当前的第4位访客
             * 线程1是当前的第3位访客
             * 线程2是当前的第3位访客(甚至出现两个第三的)
             * 线程5是当前的第6位访客
             * 线程4是当前的第5位访客
             * 线程6是当前的第7位访客
             * 线程7是当前的第8位访客
             * 线程9是当前的第10位访客
             * 线程8是当前的第9位访客
             */
        }
    }
    
     class MyRunnable implements Runnable {
        private static int num;
        @Override
        public void run() {
            num++;
            System.out.println(Thread.currentThread().getName()+"是当前的第"+num+"位访客");
        }
    }
    
    

    如果不进行线程同步,可以看到此时输出的结果就是错误了。

    class MyRunnable implements Runnable {
        private static int num;
        //通过对run方法的加锁可以就不会出错了
        @Override
        public synchronized void run() {
            num++;
            System.out.println(Thread.currentThread().getName()+"是当前的第"+num+"位访客");
        }
    }
    
  2. 使用

    可以通过synchronized关键字修饰方法来实现线程同步。每一个Java对象都有一个内置锁,内置所会保护使用synchronized关键字所修饰的方法,对象要使用该方法就得先获得锁,否则就得处于阻塞状态。

    synchronized关键字可以修饰实例方法(即不加static关键字修饰的方法),也可以修饰静态方法,但两者在使用上还是有些区别的:

    synchronized修饰静态方法

    import java.time.temporal.Temporal;
    //调用加锁下的静态方法
    public class test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //调用静态方法
                        test.testFunction1();
                        //输出结果为start和end成对出现
                        /**
                         * start-------
                         * end--------
                         * start-------
                         * end--------
                         * ......
                         */
                    }
                });
                thread.start();
            }
        }
    
        public synchronized static void testFunction1(){
            System.out.println("start-------");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end--------");
        }
    }
    
    

    synchronized修饰非静态方法:

    import java.time.temporal.Temporal;
    
    public class test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //实例化方法
                        test test = new test();
                        test.testFunction2();
                        /**
                         * 输出结果为:
                         * 10个start输出完再输出10个end
                         */
                    }
                });
                thread.start();
            }
        }
    
        public synchronized void testFunction2(){
            System.out.println("start-------");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end--------");
        }
    }
    

    加锁静态方法下:由于该方法是所有类共享该方法,其实也就复合“数据共享”这一概念,此时加锁的话对于所有的实例对象都是一样的。如下图所示,大家都是共享该静态方法,所以此时当有一个线程获得了该方法的锁后,其他人都得被阻塞。

    image-20221013151521981

    加锁实例化方法:由于该实例化方法是每个对象调用自己的实例化方法,所以其实加锁是加给自己的,如果不是同一个对象多次调用同一个方法,则是分别各自加锁了,只对自己有限制。如下图所示各个实例化对象所加锁的方法只是自己的实例化方法。

    image-20221013151820566

    synchronized还可以修饰代码块,会为代码块加上内置锁,从而实现同步。

    public class test {
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test.testFunction1();
                        //输出结果为start和end成对出现
                        /**
                         * start-------
                         * end--------
                         * ......
                         */
                    }
                });
                thread.start();
            }
        }
    
        public  static void testFunction1(){
           //在代码块前加synchronized关键字后还需要再其括号后添加对象 比如该类或该类的某一个实例化对象this
            synchronized (test.class)
            {
                System.out.println("start-------");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("end--------");
            }
        }
    }
    

    如果此时括号内添加的是类, 则该代码块的锁是类级别的是跨对象的,锁的范围是针对类,多个线程访问互斥。 (类似于锁定静态方法)

    如果此时括号内添加的是对象实例this, 则该代码块的锁是不可跨对象,所以多个线程不同对象实例访问此方法,互不影响,无法产生互斥。 (类似于锁定非静态方法)

  3. 总结

    1、给实例方法(非静态方法)加synchronized关键字并不能实现线程同步。

    2、线程同步的本质是锁定多个线程所共享的数据,比如虽然synchronized修饰的是实例方法,但在实例方法中有静态变量(这种情况一般见于run方法中),此时本质上就是在修饰静态变量。

    3、每人一份的东西并不需要排队,如果是多人共享的一个东西就需要排队实现同步。

    ​ 锁定的资源在内存中是一份还是多份?一份大家需要排队,线程同步,多份(一人一份),线程不同步。

    ​ 无论是锁定方法还是锁定对象,锁定类,只需要分析这个方法、对象、类在内存中有几份即可。对象一般都是多份(如果只创建一个对象那当然是只有一份了),类一定是一份;方法就看是静态方法还是非静态方法,静态方法一定是一份,非静态方法一般是多份。

标签:Java,Thread,void,并发,线程,sleep,方法,public
From: https://www.cnblogs.com/zwl-/p/16788411.html

相关文章

  • Java并发(进程、线程、多线程,使用)
    Java并发(进程、线程、多线程,使用)进程和线程定义进程:进程是计算机正在运行的一个独立的应用程序。线程:线程是组成进程的基本单位,可以完成特定的功能,一个进程是由一个......
  • ajax+javascript+tp搜索页面跳转
      搜索页面代码:<formclass="a"action="/news/search.html"method="get"role="form"id="searchform"><divclass="form-group"><inputtype="tex......
  • 【JS】167-JavaScript设计模式——装饰者模式
    四、装饰者模式(DecoratorPattern)1.概念介绍装饰者模式(DecoratorPattern):在不改变原类和继承情况下,动态添加功能到对象中,通过包装一个对象实现一个新的具有原对象相同接口......
  • 【JS】169-JavaScript设计模式——外观模式
    六、外观模式(FacadePattern)1.概念介绍外观模式(FacadePattern) 是一种简单又常见的模式,它为一些复杂的子系统接口提供一个更高级的统一接口,方便对这些子系统的接口访问......
  • 【JS】168-JavaScript设计模式——策略模式
    五、策略模式(StrategyPattern)1.概念介绍策略模式(StrategyPattern):封装一系列算法,支持我们在运行时,使用相同接口,选择不同算法。它的目的是为了将算法的使用与算法的实现......
  • 【JS】166-JavaScript设计模式——迭代器模式
    三、迭代器模式(IteratorPattern)1.概念介绍迭代器模式(IteratorPattern) 是提供一种方法,顺序访问一个聚合对象中每个元素,并且不暴露该对象内部。这种模式属于行为型模式......
  • 【JS】172-JavaScript设计模式——观察者模式
    九、观察者模式(ObserverPatterns)1.概念介绍观察者模式(ObserverPatterns) 也称订阅/发布(subscriber/publisher)模式,这种模式下,一个对象订阅定一个对象的特定活动,并在状......
  • 【JS】170-JavaScript设计模式——代理模式
    七、代理模式(ProxyPattern)1.概念介绍代理模式(ProxyPattern) 为其他对象提供一种代理,来控制这个对象的访问,代理是在客户端和真实对象之间的介质。简单的理解:如我们需要......
  • 【JS】89-用JavaScript实现的5个常见函数
    前言    在学习 JavaScript,或者前端面试中,有人会问你节流函数、防抖函数、递归函数等,本文分享了5个常见函数,希望对你有所帮助。    在 JavaScript 中有一些问题......
  • Java 内存区域
     JAVA虚拟机在执行java程序过程中会把他所管理的内存划分为若干个不同的数据区域,包括以下几个运行时数据区域  1、程序计数器程序计数器是一块小的内存......