zoukankan      html  css  js  c++  java
  • 多线程看着篇就够了

    认识多线程:

    计算机的操作系统大多采用多任务,就是在一个系统中可以同时运行多个程序,例如可以在听音乐的同时聊天。即就是有多个任务,每个任务对应一个进程,每个进程可以 产生多个线程。

     什么是进程:

     进程就是一段程序的运行过程,程序就是一段静态代码,进程是系统应用程序的基本单位。

    什么是多线程:

     线程是进程的一个最小的单位,在一个进程里至少有一个线程,线程的运行必须依靠进程,多个同类的线程可以共享一个进程的堆和方法区的资源,所以线程也被称为是轻量级的进程。

    如何编写一个线程:

    每个程序至少自动拥有一个主线程,也称为核心级线程,当程序加载到内存时启动主线程,java中的 public class void main()方法就是主线程的入口,运行java时会先执行这个方法,在开发中用户编写的线程是除了主线程之外的线程,我们称为用户级线程,使用一个线程可以分为以下四个步骤:

    1. 定义一个线程,同时指明这个线程所要执行的代码也就是需要完成的功能。
    2. 创建线程对象
    3. 启动线程
    4. 终止线程

    定义一个线程通常有两种方法:

    继承java.lang .Thread类或者实现java.lang.Runnable接口:

    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    class BootViewApplicationTests {
    public static class Mythread extends Thread  {
        @Override
        public void run() {
    
            System.out.println("这是我继承thread类定义的一个线程");
        }
    }
        @Test
        void contextLoads() {
            Mythread mythread = new Mythread();
            mythread.start();
        }
    
    }
    @SpringBootTest
    class BootViewApplicationTests {
    public static class Myrunnabe implements Runnable {
        @Override
        public void run() {
    
            System.out.println("这是我实现runnable类定义的一个线程");
        }
    }
        @Test
        void contextLoads() {
         Thread thread = new Thread(new Myrunnabe());
           thread.start();
        }
    
    }

    继承Thread类的方式简单明了,那么为什么要实现Runnable呢,我们都知道继承是单继承的,而实现,可以多实现,所以当一个类已经继承了某个类,那么他就无法在继承Thread。因此可以用接口去实现它。其实Thread类也是继承runnable接口的,而runnable接口中只有一个抽象方法,就是run()方法。

    线程的状态:

    线程的生命周期可以分为四个阶段,每个阶段对应一种状态。

    • 新建状态:创建线程对象但是未调用start()方法之前,这个线程有了生命,但是此时线程只是一个空对象,系统没有为其分配资源。此时线程只能启动和终止,任何其他操作都会引发异常。
    • 可运行状态:当调用了start()方法启动线程后,系统会为线程分配除了CPU之外所需的资源,这个线程就成为可运行状态。在这个状态中,线程可能正在运行,也可能没有运行。只有当线程获得CPU资源的时候,此时系统才会调用run方法,才会真正处于运行状态。
    • 阻塞状态:线程在运行过程中由于某种原因不能继续运行,就会进入堵塞状态。堵塞状态是一种不可运行状态,而处于这种状态的线程在得到一个特定事件后才会转成可运行状态。导致线程堵塞的原因有(1)调用了sleep()方法。(2)一个线程执行一个I/O操作尚未完成,线程会进入堵塞状态。(3)如果一个对象的执行需要另一个对象的锁,而这个对象的锁正被其他线程占用,那么此线程会被阻塞。
    • 死亡状态:一个线程run()方法执行完毕,stop方法被调用,或者在运行中出现没有被捕获的异常时,线程就会进入死亡状态。

    线程调度:

    当同时有多个线程处于可运行状态,这样就会遇见一个问题,排队等待CPU分配资源从而调用run方法进入运行状态,那么线程的优先级就决定了排队的顺序。优先级越高,那么他就被先分配cup资源,总结一句就是先到先服务。

    在java中有线程的调度管理器只专门负责管理线程优先级的,他是根据线程的调度算法进行调度的。

    线程的优先级:优先级用1-10 表示,10表示优先级最高,默认是5

     /**
         * The minimum priority that a thread can have.
         */
        public final static int MIN_PRIORITY = 1;
    
       /**
         * The default priority that is assigned to a thread.
         */
        public final static int NORM_PRIORITY = 5;
    
        /**
         * The maximum priority that a thread can have.
         */
        public final static int MAX_PRIORITY = 10;

    我们看一下Threa的类中的源码,其中每个优先级对应一个公共的静态常量。

    实现调度的三种方法:join(),sleep(),yield()

    我们先看一下第一种:join()方法,使得当前线程暂停执行,等待调用方法的线程结束后,在继续执行本线程。

    我们看一下Thread类中定义join方法的三种重载:

       //第一种:调用join方法,等待其他线程,等待时间是millis
     public final synchronized void join(long millis)
        throws InterruptedException {
            long base = System.currentTimeMillis();
            long now = 0;
    
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (millis == 0) {
                while (isAlive()) {
                    wait(0);
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
        }
    
    //第二种
        public final synchronized void join(long millis, int nanos)
        throws InterruptedException {
    
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                                    "nanosecond timeout value out of range");
            }
    
            if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
                millis++;
            }
    
            join(millis);
        }
    
      //第三种:调用join方法,等待其他线程执行完毕
        public final void join() throws InterruptedException {
            join(0);
        }

    三种方法,后两种最终都调用了第一种方法。

    实战1:写一个join方法阻塞线程:

    第一步:定义线程类,打印五次。

    第二步:定义一个测试类,使用join方法阻塞这个线程。

    @SpringBootTest
    class BootViewApplicationTests {
        //定义一个线程,并打印五次
    public static class Myrunnabe implements Runnable {
        @Override
        public void run() {
         int a=5;
            for (int i = 0; i < a; i++) {
                System.out.println(i+"这是我实现runnable类定义的一个线程");
            }
    
        }
    }
    //在主线程中执行五次后,调用定义的线程
        @Test
        void contextLoads() {
         int b=10;
            for (int i = 0; i < b; i++) {
                System.out.println("这是主线程");
                //当1=5的时候,调用上边的线程
                if (i==5){
                    Thread thread = new Thread(new Myrunnabe());
                    try {
                        thread.start();
                        thread.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }

    我们看一下测试结果:

     当主线程执行到第五次的时候,就会调用我定义的方法,调用join方法,会堵塞当前的线程,也就是主线程,等定义的线程执行完毕在去执行主线程。

    实战2:写一个join方法实现两个线程间的数据传递:

    步骤一:定义线程类,为变量赋值。

    步骤二:编写测试类

    我们先看一下不使用join(),实现数据传递会出现什么结果。

    class BootViewApplicationTests {
    public static class Myrunnabe extends  Thread{
          public String a;
           public String b;
        @Override
        public void run() {
           a="zl的测试数据1";
           b="zl的测试数据2";
            }
    
        }
        
        @Test
        void contextLoads() {
    
            Myrunnabe thread = new Myrunnabe();
            thread.start();
            System.out.println("a:"+thread.a);
            System.out.println("b:"+thread.b);
        }
    
    }

    测试结果:

    a:null
    b:null

    为什么会出现这种情况,因为start后,线程是处于可运行状态,可运行状态有两种可能,就是正在运行,和尚未运行,显然在执行了start方法后,run方法还没来得及执行赋值的方法就被打印了。所以可以使用join方法,去处理这个问题:

    @SpringBootTest
    class BootViewApplicationTests {
    public static class Myrunnabe extends  Thread{
          public String a;
           public String b;
        @Override
        public void run() {
           a="zl的测试数据1";
           b="zl的测试数据2";
            }
    
        }
    
        @Test
        void contextLoads() {
    
            Myrunnabe thread = new Myrunnabe();
    
            try {
                thread.start();
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("a:"+thread.a);
            System.out.println("b:"+thread.b);
        }
    
    }

    测试结果:

    a:zl的测试数据1
    b:zl的测试数据2

    这样就可以拿到数据了。

    我们再看一下第二种实现线程调度的方法:sleep()方法,他会让当前线程睡眠多少秒,线程进入不可运行 状态,等睡眠时间结束后,会进入可运性状态。

    实战1:使用sleep方法堵塞线程

    步骤一:定义线程

    步骤二:在run方法中sleep休眠

    步骤三:编写测试

    @SpringBootTest
    class BootViewApplicationTests {
    public static void sleep(long s){
        for (int i = 0; i<5; i++) {
            System.out.println(i+1+"s");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }
    
        @Test
        void contextLoads() {
            System.out.println("等待");
            sleep(5);
    
            System.out.println("恢复");
        }
    
    }

    测试结果:

    等待
    1s
    2s
    3s
    4s
    5s
    恢复

    主线程等待5s,然后执行定义的线程,5s后主线程恢复,执行主线程

    我们再看一下第二种实现线程调度的方法:yield()方法:yield方法可让当前线程暂停执行,允许其他线程执行,但该线程还是处于可运行状态,并不变成阻塞状态,此时系统选择相同或者更高优先级的线程执行,如果没有,则该线程继续执行。

    实战:使用yeid()方法暂停线程

    步骤一:定义两个线程

    步骤二:在run方法中使用yeid()方法暂停线程

    步骤三:测试类

      public static class mythread1 extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("mythread1第:"+(i + 1 )+ "次执行");
                    Thread.yield();
                }
    
    
            }
        }
        public static class mythread2 extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("mythread2第:"+(i + 1 )+ "次执行");
                    Thread.yield();
                }
            }
        }
        @Test
        void contextLoads() {
            mythread1 mythread1 = new mythread1();
            mythread2 mythread2 = new mythread2();
            mythread1.start();
            mythread2.start();
        }
    
    }

    测试结果:

    mythread2第:1次执行
    mythread1第:1次执行
    mythread2第:2次执行
    mythread2第:3次执行
    mythread2第:4次执行
    mythread2第:5次执行
    mythread1第:2次执行
    mythread1第:3次执行
    mythread1第:4次执行
    mythread1第:5次执行

    我们可以看到,当使用了yeid()方法之后,线程并不是转入了堵塞状态,依旧是可运行状态,两个线程相互之间抢夺CPU资源,就会出现这样的结果。

    实现线程调度的三种方法讲完了,我们再来总结一下:

    1. join方法和sleep方法都是将线程的可运行状态打破,进入不可运行状态,也就是堵塞状态,而yeid方法只是让线程暂停,而没有改变线程的状态
    2. join方法暂停当前线程,等待其他线程执行结束在重新启用,也就是需要其他线程唤醒,而sleep方法,是休眠,休眠结束后线程自己就苏醒。不需要其他线程的唤醒。
    3. yeid方法是暂停当前线程,先执行线程优先级高的,自身线程一直处于可运行状态

    线程同步:

    前面的案例线程都是独立且异步的,所以不需要外部的资源和方法,也不需要关系其他线程的状态和行为。但是如果同时运行的多个线程共享同一个外部资源,那么就需要考虑线程的状态和行为。

    线程同步的方法:

         1.synchronized,这个关键字能够保证方法或代码块在某一时刻只能有一个线程访问。他可以作用于两个地方,一个是方法上,一个是代码块上。

         2.volatile关键字,这个关键字是线程同步的轻量级实现,效率要远远高于synchronized,但是有个弊端就是volatile只能用于修饰变量。

    线程通讯
    举个栗子 :生产者和消费者,生产者生产产品,通知消费者消费,如果消费者没有消费则停止生产,等到消费者消费完成后,再生产,如果生产者没有生产,消费者则等待生产者生产。
    线程间实现通讯的方法有三个:wait()挂起线程,notify()唤起线程,notifyAll()唤起所有线程
    案例:
    步骤一:定义共享资源
    步骤二:定义生产者线程
    步骤三:定义消费者线程
    步骤四:测试
    public class Acount {
      private char c;
      private boolean isProduced=false;
      public synchronized void pShar(char c){
          //如果产品未消费则停止生产
          if(isProduced){
              try {
                  System.out.println("消费者还未消费因此生产者停止生产");
                  wait();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
          this.c=c;
          isProduced=true;
          notify();
          System.out.println("生产者生产了"+c+",通知消费者消费");
    
      }
        public synchronized char cShar(){
          //如果产品没生产,则消费者停止消费
            if(!isProduced){
                try {
                    System.out.println("生产者还未生产,因此消费者停止消费");
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            isProduced=false;
            notify();
            System.out.println("消费者消费了"+c+",通知生产者生产");
         return this.c;
        }
    }
    @SpringBootTest
    class BootViewApplicationTests {
        //定义生产线程
    class Product extends Thread{
        private Acount acount;
        Product(Acount acount){
            this.acount=acount;
        }
        @Override
        public void run() {
            char c;
            for(c='A';c<='D';c++){
                try {
                    Thread.sleep((int)Math.random()*3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //将产品放入仓库
                acount.pShar(c);
            }
        }
    }
    //定义消费者线程
        class Consumer extends Thread{
            private Acount acount;
            Consumer(Acount acount){
                this.acount=acount;
            }
    
            @Override
            public void run() {
                char ch;
                do{
                    try {
                        Thread.sleep((int)Math.random()*3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //从仓库中取走产品
                    ch=acount.cShar();
                }while (ch!='D');
    
            }
        }
        @Test
        //测试类
        void contextLoads() {
            Acount acount = new Acount();
            new Consumer(acount).start();
            new Product(acount).start();
        }

    测试结果:

    生产者还未生产,因此消费者停止消费
    生产者生产了A,通知消费者消费
    消费者还未消费因此生产者停止生产
    消费者消费了A,通知生产者生产
    生产者还未生产,因此消费者停止消费
    生产者生产了B,通知消费者消费
    消费者还未消费因此生产者停止生产
    消费者消费了B,通知生产者生产
    生产者还未生产,因此消费者停止消费
    生产者生产了C,通知消费者消费
    消费者还未消费因此生产者停止生产
    消费者消费了C,通知生产者生产
    生产者生产了D,通知消费者消费
    消费者消费了D,通知生产者生产

     
  • 相关阅读:
    视差插件parallarx
    经典幻灯片插件Swiper
    经典全屏滚动插件fullPage.js
    Dialog插件artDialog
    html5 canvas 做的一个时钟效果
    CSS3 Transitions, Transforms和Animation使用简介与应用展示
    微软官方下载地址
    Windows 7 下配置IIS,并且局域网内可访问(转载)
    C# 使用HttpWebRequest 实现文件的上传
    小图标网站
  • 原文地址:https://www.cnblogs.com/javazl/p/12721451.html
Copyright © 2011-2022 走看看