初试多线程

155 阅读4分钟
  • 创建线程的方法

    • 继承Thread类

      public class ThreadA extends Thread {
          @Override
          public void run() {
              try {
                  System.out.println("true = " + true);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }
      
    • 实现Runnable接口

          static class Test1 implements Runnable{
              @Override
              public void run(){
                      try {
                          System.out.println("true = " + true);
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
              }
          }
      
    • 使用Lambda表达式,这是第二种方式的一个变种

      static class Test2{
              public static void main(String[] args) {
                  new Thread(()->{
                      System.out.println("args = " + "aaaa");
                  }).start();
              }
          }
      

      注意如果说是启动线程的三种方式那么并不是上面的三种

      这三种三种方法是:继承Thread 、实现Runnable、线程池创建

  • 线程的随机性

    当多线程开启的时候每个线程的输出都是随机的,这是因为CPU将时间片分给了不同的线程,线程获得时间片就去执行,但是CPU分给每个线程的时间片是很短的,因为在视觉上要让用户感觉是在并行,这就会使CPU在一秒钟内可能会切换很多次线程,每个线程交替运行就会产生输出的无序。不过我们需要注意的是CPU在切换线程的时候是需要耗费时间的所以并不是线程数越多执行的越快。

  • 线程方法

    • currentThread()

      public class Test {
          public static void main(String[] args){
              // 通过currentThread()方法可以获取当前线程的name id state等
              System.out.println( Thread.currentThread().getName());
              System.out.println( Thread.currentThread().getId());
              System.out.println( Thread.currentThread().getState());
          }
      }
      
    • isAlive()

      判断当前线程是否存活

    • sleep方法

      sleep(long millis) 是在指定时间内让当前“正在执行的线程”休眠 ,这个“正在执行的线程” 是指this.currentThread()返回的线程,如果调用sleep()方法所在的类是Thread,则执行代码

      Thread.sleep()和this.sleep()是一样的,如果调用sleep方法的不是Thread 则必须使用Thread.sleep()

      比如在main线程中只能用Thread.sleep()

      sleep(long millis,int nanos) 在指定的毫秒数+指定的纳秒数让当前的线程休眠

      注意 sleep()方法并不会释放锁

    • yield方法

      放弃当前的CPU资源,让其他的任务去占用CPU执行时间。放弃的时间不确定,有可能刚刚放弃,马上又获取CPU时间片

      注意 yield()方法并不会释放锁

    • join方法

      有线程A和线程B,在线程A中使用B.join,则线程A会挂起,然后执行B线程

  • 停止线程的方式

    • 异常法

      使用interrupt()打断线程 然后进行异常捕捉

    • 使用stop方法暴力终止(此方法已经弃用)

    • 线程正常结束

    注意: 一般不会强制退出线程,都是线程自己执行完毕之后退出

  • 创建线程的两种方式实现Runnable接口、继承Thread类的比较

    Runable接口中只有一个抽象方法run,所有实现Runnable接口的类都要实现run这个方法。

    @FunctionalInterface
    public interface Runnable {
     
        public abstract void run();
    }
    
    

    那么这个run方法又是怎么被调用的呢?

    其实我们看Thread的源码可以看见除了我们自己实现Runable方法外,Thread也实现了Runable接口。

    而且在Thread类中重写了run方法

     @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    

    重写的run方法中,如果target!=null 则执行target的run方法,那么这个target是什么呢?

    从源码中我们可以看到target对象是Runnable类型的,所以Thread的run方法是这样运行的:当传进来一个Runnable类时,他会执行Runnable的run方法,否则什么都不执行。

    同时我们还可以在源码中看见,这个target并不是在构造方法中初始化的,Thread的构造方法调用了init方法,所有的初始化都是在init方法中实现的

    总结:1.当我们实现Runnable接口的时候,我们需要将Runnable的对象传第到Thread类中,Thread类会先调用构造方法,然后调用初始化方法将线程的所有数据初始化,然后我们调用Thread的start方法通知JVM去执行Thread类中run方法,然后在run方法中调用我们重写的Runnable接口中的run方法。

    2.如果我们直接继承Thread类的时候那么我们就不需要这么麻烦了,直接通知JVM执行run方法就可以,所以继承Thread类的方式比实现Runnable接口的方式要快的多。

    3.不过可惜的时java只能单继承,所以在很多时候我们必须使用实现Runnable接口的方式去实现多线程。