多线程基础知识回顾

多线程的基础知识复习

1)进程和线程的区别java

进程:
    程序运行后,一个QQ,微信等就是一个进程。
线程:
    线程是进程中的最小单元。说简单的话说,线程就是程序中不一样的执行路径。
程序:
    QQ是一个程序,是一个硬盘上的程序,

2)线程run方法和start方法的区别segmentfault

public class T01_WhatIsThread {
    //新建了静态内部类继承Thrread,线程修改1秒,输入T1
    private static class T1 extends Thread {
        @Override
        public void run() {
           for(int i=0; i<10; i++) {
               try {
                   TimeUnit.MICROSECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("T1");
           }
        }
    }
    //main方法
    public static void main(String[] args) {
        new T1().run();
        //new T1().start();
        for(int i=0; i<10; i++) {
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main");
        }

    }
}
输出
T1
T1
main
main
//这个时候注释18run方法 开始start方法 执行结果大不同
T1
main
T1
main

#结论 therad的start方法 执行路径是分支的形式,而run方法是重上到下依次执行。

3)多线程的经常使用实现方法安全

//线程主要实现的方法有3种
public class T02_HowToCreateThread {
    //集成Thread的方法
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }
    //实现Runnable接口
    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }
    //三种线程不一样的运行方式
    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRun()).start();
        //lamda表达式来执行一个线程
        new Thread(()->{
            System.out.println("Hello Lambda!");
        }).start();
    }
}
// lamda表达式也是一种方式,线程池也是一种方式
//Executors.newCachedThreadPool();
//经过线程池去拿到一个线程,而这个线程仍是要执行runable或者start的方法。

4)线程最基本的方法微信

  • 4.1 sleep多线程

    • 当前线程睡眠多少时间,由其余线程来执行
  • 4.2 joinide

    • 经常使用于等待另一个线程结束,也就是保证线程有序执行。
      25456de9-85b5-4703-887b-22e1e45b6ecb.png
    //测试join线程
    static void testJoin() {
        Thread t1 = new Thread(()->{
            //线程1建立了10个线程
            for(int i=0; i<10; i++) {
                System.out.println("A" + i);
                try {
                    //每一个线程休眠500毫秒
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //线程2
        Thread t2 = new Thread(()->{
            try {
                //线程1的线程加入。等待线程1结束
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //线程1结束后,才开始执行此代码
            for(int i=0; i<10; i++) {
                System.out.println("B" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //分别启动
        t1.start();
        t2.start();
    }
    #执行结果
    A0
    A1
    A2
    A3
    A4
    A5
    A6
    A7
    A8
    A9
    B0
    B1
    B2
    B3
    B4
    B5
    B6
    B7
    B8
    B9
  • 4.3 yield学习

    • 当前线程先离开,返回到就绪的状态,至于这个线程是否会被CPU立刻执行,仍是先执行等待队列中已经等待的线程,这个不必定。看竞争
线程的状态图

file

  • Ready(就绪) Running(正在运行) 都属于Runnable状态。
  • TimedWaiting 的相关方法是指时间一到,就会自动恢复runnable状态
  • Waiting 状态是指必须被唤醒才能进入Runnabel状态
  • synchronized 获得同步代码块的锁 以前会进入阻塞状态,获得锁以后,线程运行
  • Terminated 线程中止
  • 不建议用Thread的stop方法来强行中止线程,有安全问题。
  • 对于interrupt的一些说明:测试

    #线程经过wait()进入阻塞状态,此时经过interrupt()中断该线程;
    #调用interrupt()会当即将线程的中断标记设为“true”,可是因为线程处于阻塞状态,因此该“中断标记”会当即被清除为“false”,同时,会产生一个InterruptedException的异常。
    #咱们会catch这个异常,再根据业务逻辑去处理线程的后续行为。
    #代码示例
    @Override
    public void run() {
        try {
            // 1. isInterrupted()保证,只要中断标记为true就终止线程。
            while (!isInterrupted()) {
                // 执行任务...
            }
        } catch (InterruptedException ie) {  
            // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
        }
    }
    //interrupt 用于控制业务场景的用法极少,正经常使用法通常是某一个线程阻塞时间很长很长,经过interrupt来打断线程。
  • 线程状态的小例子this

    static class MyThread extends Thread {
            @Override
            public void run() {
                System.out.println(this.getState());
    
                for(int i=0; i<4; i++) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println(i);
                }
            }
        }
    
        public static void main(String[] args) {
            Thread t = new MyThread();
    
            System.out.println(t.getState());
    
            t.start();
    
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(t.getState());
    
        }
        #输出结果
        NEW
        RUNNABLE
        0
        1
        2
        3
        TERMINATED
synchronize 锁
  • 须要注意的是 锁,所的是对象,而不是代码spa

    • 好比:

public class T {
    private int count = 10;
    private Object o = new Object();
    public void m() {
    synchronized(o) { // **任何线程要执行下面的代码,必须先拿到o的锁**
        count--;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
}
#问题 
上述代码若是new了2个不一样的object,o和o1,那synchronize(o) 是锁誰呢,固然锁o。

    ```
  • synchronize 的几种锁的形式

    • 锁this

      private int count = 10;
      public void m() {
          synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
              count--;
              System.out.println(Thread.currentThread().getName() + " count = " + count);
          }
      }
    • 锁方法

      private int count = 10;
      public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
    • 锁静态方法

      private static int count = 10;
      public synchronized static void m() { //这里等同于synchronized(T.class)
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
      
      public static void mm() {
          synchronized(T.class) { //考虑一下这里写synchronized(this)是否能够?(不可,由于没有new,)
              count --;
          }
      }
    • 小思考

      1) 下面的线程如何输出?
      2)加上synchronize后又有什么区别
      3)加上volatile后又什么区别
      private /*volatile*/ int count = 100;
      
      public /*synchronized*/ void run() { 
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
      
      public static void main(String[] args) {
          T t = new T();
          for(int i=0; i<100; i++) {
              new Thread(t, "THREAD" + i).start();
          }
      }
      #1 打印列会出现重复或者实际减的数和打印的数不一致。
      //打印结果抽取异常部分
      THREAD81 count = 37
      THREAD70 count = 37
      #2 synchronize 既保证可见又保证一致性
      #3 volatile 保证可见性,这个变量改后立马被线程发现。
      #4 加了synchronize就没必要加volatile。
    • 同一个线程能够同时掉加锁和不加锁的方法,并不会致使一个方法有锁,而致使不加锁的方法没法执行。
    • 读方法和写方法是否都须要上锁来保持一致,看具体的业务逻辑。若是写上锁,读不上锁,有可能脏读数据,这个根据业务来定。读上锁会让读取的效率大大降。
    • synchronize 是可重入锁,若是上锁的方法1,调用上锁的方法2,若是不能够重入,会产生死锁。
    • $\color{red}{程序若是出现异常,默认状况下锁会被释放}$

      public class T {
      int count = 0;
      synchronized void m() {
          System.out.println(Thread.currentThread().getName() + " start");
          while(true) {
              count ++;
              System.out.println(Thread.currentThread().getName() + " count = " + count);
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              
              if(count == 5) {
                  int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,能够在这里进行catch,而后让循环继续
                  System.out.println(i);
              }
          }
      }
      }}
      public static void main(String[] args) {
          T t = new T();
          Runnable r = new Runnable() {
      
              @Override
              public void run() {
                  t.m();
              }
              
          };
          new Thread(r, "t1").start();
          
          try {
              TimeUnit.SECONDS.sleep(3);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          
          new Thread(r, "t2").start();
      }
      }
      #//打印结果 人为异常不补货,t2获得锁。继续执行
      t1 count = 2
      t1 count = 3
      t1 count = 4
      t1 count = 5
      t2 start
      Exception in thread "t1" t2 count = 6
      java.lang.ArithmeticException: / by zero
      at com.mashibing.juc.c_011.T.m(T.java:27)
      at com.mashibing.juc.c_011.T$1.run(T.java:39)
      at java.base/java.lang.Thread.run(Thread.java:844)
      t2 count = 7
    • synchronize的底层升级概

      推荐文章 锁升级的小段子
      偏向锁(谁来谁第一,谁偏向)----》
      竞争 自旋锁,(自旋10次或者 JDK目前规定为自旋线程超过CPU内核数的一半)
      若是超过了自旋的上限,就升级重量级锁,重锁是OS(操做系统)级别的锁,并进入等待队列。

    • 锁使用的场景:

      $\color{red}{线程少,上锁代码执行快,用自旋锁}$
      $\color{red}{线程多,上锁代码执行长,用OS重量锁}$
  • 总结
    线程的概念、线程的启用方式,经常使用的方法介绍。
    线程的状态机、
    synchronize 锁的是对象,不是代码。
    synchronize 锁的集中方式
    synchronize 的锁的升级。
    异常锁,默认放弃锁。除非catch跳过循环。
    偏向锁、自旋锁 (公平、非公平)重量级锁。
  • 做者QQ 68335397 有问题指正或者讨论学习,留言也可。
相关文章
相关标签/搜索