当前位置 博文首页 > asd1358355022的博客:每日积累【Day2】Java 多线程重温Part 1

    asd1358355022的博客:每日积累【Day2】Java 多线程重温Part 1

    作者:[db:作者] 时间:2021-07-25 12:35

    Java 多线程重温Part 1

    经过一段时间工作之后,返回来重温多线程相关的知识,会收获到不一样的东西哦。

    什么是多线程?

    ? 多线程是指从软件或者硬件上实现多个线程并发执行的技术,它更多的是解决CPU调度多个进程的问

    题,从而让这些进程看上去是同时执行(实际是交替运行的)。

    这几个概念中,多线程解决的问题是最明确的,手段也是比较单一的,基本上遇到的最大问题就是线

    程安全。在Java语言中,需要对JVM内存模型、指令重排等有深入了解,才能写出一份高质量的多线

    程代码。

    总结

    分布式是从物理资源的角度去将不同的机器组成一个整体对外服务,技术范围非常广且难度非

    常大,有了这个基础,高并发、高吞吐等系统很容易构建 。

    高并发是从业务角度去描述系统的能力,实现高并发的手段可以采用分布式,也可以采用诸如缓存、CDN等,当然也包括多线程技术。

    多线程技术则聚焦于如何使用编程语言将CPU调度能力最大化。

    1、创建多线程方式一(Thread方式):

    1. 创建一个类继承Thread
    2. 重写Thread类里面的run方法
    3. 创建继承Thread的类对象
    4. 调用此对象的start方法(注意!直接调用run方法相当于使用当前线程去串行执行,并不是另起一个线程去执行。)

    代码常规写法

    public class ThreadTest extends Thread{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            ThreadTest threadTest = new ThreadTest();
            threadTest.start();
        }
    }
    

    代码简化写法

    可以使用比较简化的方式创建一个线程:使用匿名子类去创建。

    new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            }.start();
    

    多线程常用的方法:

    1. start() 启动一个线程,调用run方法
    2. currentThread() 静态方法,返回当前执行代码的线程
    3. getName() 获取当前线程名称
    4. setName() 设置当前线程名称
    5. yield() 释放当前线程执行权
    6. join() 在a线程中调用了b线程的join方法,a进入阻塞状态直到b线程执行完毕之后才继续执行a线程。
    7. stop() 已停用,强制停止当前线程
    8. sleep(long n) 指定当前线程睡眠n毫秒
    9. isAlive() 判断当前线程是否存活
    10. setPriority(int priority) 更改线程的优先级。

    2、创建多线程方式二(Runnable方式):

    1. 创建一个类实现Runnable接口
    2. 实现Runnable接口里面的run方法
    3. 创建实现Runnable接口的类对象
    4. 将创建好的类对象放入Thread类的构造器中,创建出此Thread对象
    5. 调用Thread对象的start方法

    代码常规写法

    public class RunnableTest implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            RunnableTest runnableTest = new RunnableTest();
            Thread thread = new Thread(runnableTest);
            thread.start();
        }
    }
    

    代码简化写法

    new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            }).start();
    

    3、Runnable和Thread两种创建方式对比

    1、Thread使用继承方式去实现,如果当前类有其他父类,就不能使用Thread去实现。(类的单继承局限)

    2、Runnable是接口类型,成员变量是共享的,Thread成员遍历如果需要线程共享则需要使用static或者其他的方式处理。

    开发中优先选取实现Runnable接口。

    4、线程状态

    Thread类里面有状态相关的枚举

    public enum State {
            /**
             * Thread state for a thread which has not yet started.
             */
            NEW, //创建新线程还没有调用start方法
    
            /**
             * Thread state for a runnable thread.  A thread in the runnable
             * state is executing in the Java virtual machine but it may
             * be waiting for other resources from the operating system
             * such as processor.
             */
            RUNNABLE, //线程运行种状态
    
            /**
             * Thread state for a thread blocked waiting for a monitor lock.
             * A thread in the blocked state is waiting for a monitor lock
             * to enter a synchronized block/method or
             * reenter a synchronized block/method after calling
             * {@link Object#wait() Object.wait}.
             */
        	/*
        	阻塞状态,线程进入等待状态,线程因为某种原因,放弃了CPU的使用权阻塞的几种情况:
    		A. 等待阻塞:运行的线程执行了wait(),JVM会把当前线程放入等待队列
    		B. 同步阻塞:运行的线程在获取对象的同步锁时,如果该同步锁被其他线程占用了,JVM会把当前线程放入锁池中
    		C. 其他阻塞:运行的线程执行sleep(),join()或者发出IO请求时,JVM会把当前线程设置为阻塞状态,当sleep()执行完,join()线程终止,IO处理完毕线程再次恢复
    		D.等待同步锁
        	*/
            BLOCKED, 
    
            /**
             * Thread state for a waiting thread.
             * A thread is in the waiting state due to calling one of the
             * following methods:
             * <ul>
             *   <li>{@link Object#wait() Object.wait} with no timeout</li>
             *   <li>{@link #join() Thread.join} with no timeout</li>
             *   <li>{@link LockSupport#park() LockSupport.park}</li>
             * </ul>
             *
             * <p>A thread in the waiting state is waiting for another thread to
             * perform a particular action.
             *
             * For example, a thread that has called <tt>Object.wait()</tt>
             * on an object is waiting for another thread to call
             * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
             * that object. A thread that has called <tt>Thread.join()</tt>
             * is waiting for a specified thread to terminate.
             */
            WAITING, //待状态,等待就绪,等待cpu调度
    
            /**
             * Thread state for a waiting thread with a specified waiting time.
             * A thread is in the timed waiting state due to calling one of
             * the following methods with a specified positive waiting time:
             * <ul>
             *   <li>{@link #sleep Thread.sleep}</li>
             *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
             *   <li>{@link #join(long) Thread.join} with timeout</li>
             *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
             *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
             * </ul>
             */
            TIMED_WAITING,//超时等待状态,超时以后自动返回,等待队列
    
            /**
             * Thread state for a terminated thread.
             * The thread has completed execution.
             */
            TERMINATED;//终止状态,当前线程执行完毕(执行完run方法、调用了stop()方法、执行的时候出现了error或者exception没有处理)
        }
    

    5、synchronized同步代码块、同步方法

    为了解决多线程线程安全安全问题,操作同步代码时,只能有一个线程在参与,其他线程在等待。相当于一个单线程过程,效率低

    注意事项:

    非静态同步监视器为this,静态同步监视器为类本身(需要考虑到同步监视器是否类唯一)

    同步代码块使用方式

    //结构如下
    @Override
    public void run() {
        synchronized(当前类名.class){
            //实现代码
        }
    }
    
    //或者
    @Override
    public void run() {
        synchronized(this){
            //实现代码
        }
    }
    

    同步方法使用方式

    @Override
    public void run() {
        test();
    }
    
    private synchronized void test(){
        System.out.println(Thread.currentThread().getName());
    }
    

    6、synchronized相关

    三大特性

    ? 原子性、有序性、可见性

    我们使用反汇编去看下jvm如何执行我们的synchronized代码块和synchronized方法

    案例代码

    public class RunnableTest implements Runnable{
    
        @Override
        public void run() {
            synchronized (RunnableTest.class) {
                System.out.println(Thread.currentThread().getName());
            }
            test();
        }
    
    
        private synchronized void test(){
            System.out.println(Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            RunnableTest runnableTest = new RunnableTest();
            Thread thread = new Thread(runnableTest);
            thread.start();
        }
    }
    

    去项目文件路径执行
    javac RunnableTest.java
    javap -p -v RunnableTest.class

    反编译结果

    $ javap -p -v RunnableTest.class
    Classfile /F:/IDEA/IntelliJ%20IDEA%202018.3/workspace/LetCodeTest/src/test/java/org/example/RunnableTest.class
      Last modified 2021-7-18; size 898 bytes
      MD5 checksum f492ffde04e51cdf2a2c862e35d7f2dc
      Compiled from "RunnableTest.java"
    public class org.example.RunnableTest implements java.lang.Runnable
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #12.#28        // java/lang/Object."<init>":()V
       #2 = Class              #29            // org/example/RunnableTest
       #3 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
       #4 = Methodref          #9.#32         // java/lang/Thread.currentThread:()Ljava/lang/Thread;
       #5 = Methodref          #9.#33         // java/lang/Thread.getName:()Ljava/lang/String;
       #6 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #7 = Methodref          #2.#36         // org/example/RunnableTest.test:()V
       #8 = Methodref          #2.#28         // org/example/RunnableTest."<init>":()V
       #9 = Class              #37            // java/lang/Thread
      #10 = Methodref          #9.#38         // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      #11 = Methodref          #9.#39         // java/lang/Thread.start:()V
      #12 = Class              #40            // java/lang/Object
      #13 = Class              #41            // java/lang/Runnable
      #14 = Utf8               <init>
      #15 = Utf8               ()V
      #16 = Utf8               Code
      #17 = Utf8               LineNumberTable
      #18 = Utf8               run
      #19 = Utf8               StackMapTable
      #20 = Class              #29            // org/example/RunnableTest
      #21 = Class              #40            // java/lang/Object
      #22 = Class              #42            // java/lang/Throwable
      #23 = Utf8               test
      #24 = Utf8               main
      #25 = Utf8               ([Ljava/lang/String;)V
      #26 = Utf8               SourceFile
      #27 = Utf8               RunnableTest.java
      #28 = NameAndType        #14:#15        // "<init>":()V
      #29 = Utf8               org/example/RunnableTest
      #30 = Class              #43            // java/lang/System
      #31 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
      #32 = NameAndType        #46:#47        // currentThread:()Ljava/lang/Thread;
      #33 = NameAndType        #48:#49        // getName:()Ljava/lang/String;
      #34 = Class              #50            // java/io/PrintStream
      #35 = NameAndType        #51:#52        // println:(Ljava/lang/String;)V
      #36 = NameAndType        #23:#15        // test:()V
      #37 = Utf8               java/lang/Thread
      #38 = NameAndType        #14:#53        // "<init>":(Ljava/lang/Runnable;)V
      #39 = NameAndType        #54:#15        // start:()V
      #40 = Utf8               java/lang/Object
      #41 = Utf8               java/lang/Runnable
      #42 = Utf8               java/lang/Throwable
      #43 = Utf8               java/lang/System
      #44 = Utf8               out
      #45 = Utf8               Ljava/io/PrintStream;
      #46 = Utf8               currentThread
      #47 = Utf8               ()Ljava/lang/Thread;
      #48 = Utf8               getName
      #49 = Utf8               ()Ljava/lang/String;
      #50 = Utf8               java/io/PrintStream
      #51 = Utf8               println
      #52 = Utf8               (Ljava/lang/String;)V
      #53 = Utf8               (Ljava/lang/Runnable;)V
      #54 = Utf8               start
    {
      public org.example.RunnableTest();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
    
      public void run();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: ldc           #2                  // class org/example/RunnableTest
             2: dup
             3: astore_1
             4: monitorenter
             5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             8: invokestatic  #4                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
            11: invokevirtual #5                  // Method java/lang/Thread.getName:()Ljava/lang/String;
            14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            17: aload_1
            18: monitorexit
            19: goto          27
            22: astore_2
            23: aload_1
            24: monitorexit
            25: aload_2
            26: athrow
            27: aload_0
            28: invokespecial #7                  // Method test:()V
            31: return
          Exception table:
             from    to  target type
                 5    19    22   any
                22    25    22   any
          LineNumberTable:
            line 7: 0
            line 8: 5
            line 9: 17
            line 10: 27
            line 11: 31
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 22
              locals = [ class org/example/RunnableTest, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
    
      private synchronized void test();
        descriptor: ()V
        flags: ACC_PRIVATE, ACC_SYNCHRONIZED
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: invokestatic  #4                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
             6: invokevirtual #5                  // Method java/lang/Thread.getName:()Ljava/lang/String;
             9: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            12: return
          LineNumberTable:
            line 15: 0
            line 16: 12
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=3, args_size=1
             0: new           #2                  // class org/example/RunnableTest
             3: dup
             4: invokespecial #8                  // Method "<init>":()V
             7: astore_1
             8: new           #9                  // class java/lang/Thread
            11: dup
            12: aload_1
            13: invokespecial #10                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
            16: astore_2
            17: aload_2
            18: invokevirtual #11                 // Method java/lang/Thread.start:()V
            21: return
          LineNumberTable:
            line 19: 0
            line 20: 8
            line 21: 17
            line 22: 21
    }
    SourceFile: "RunnableTest.java"
    
    

    我们可以在其中的内容发现

      /*-------------------------------synchronized代码块相关---------------------------*/
    
    	     4: monitorenter  //监控器开始,上锁
             5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             8: invokestatic  #4                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
            11: invokevirtual #5                  // Method java/lang/Thread.getName:()Ljava/lang/String;
            14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            17: aload_1
            18: monitorexit  //监视器退出,释放锁
    
    
     /*-------------------------------synchronized方法相关---------------------------*/      
        private synchronized void test();
        descriptor: ()V
        flags: ACC_PRIVATE, ACC_SYNCHRONIZED //ACC_SYNCHRONIZED是synchronized方法是运行时常量池中多了
    
    

    |--------------------------------------------------------------------------------------------------------------------------------------|

    以下引用csdn:https://blog.csdn.net/qq_26222859/article/details/53786134

    synchronized代码块相关信息:

    关于这两条指令(monitorenter、monitorexit)的作用,我们直接参考JVM规范中描述:

    monitorenter : Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:? If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.? If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.? If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
    

    这段话的大概意思为:

    每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

    1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
    2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。
    3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
    monitorexit:  The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
    

    这段话的大概意思为:

    执行monitorexit的线程必须是objectref所对应的monitor的所有者。

    指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

    通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

    synchronized方法:

    ? 方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

    ? 以上引用csdn:https://blog.csdn.net/qq_26222859/article/details/53786134

    |--------------------------------------------------------------------------------------------------------------------------------------|

    7、ReentrantLock可重入锁

    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockTest implements Runnable{
    
        private int ticket = 100;
    
        private ReentrantLock reentrantLock = new ReentrantLock(true);
    
        @Override
        public void run() {
            while (true){
    
                try {
                    //上锁
                    reentrantLock.lock();
                    if (ticket > 0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("current thread name:" + Thread.currentThread().getName() + "--ticket number:" + ticket);
                        ticket--;
                    }else {
                        break;
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    //解锁
                    reentrantLock.unlock();
                }
            }
        }
    
        public static void main(String[] args) {
            ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
    
            new Thread(reentrantLockTest).start();
            new Thread(reentrantLockTest).start();
            new Thread(reentrantLockTest).start();
        }
    
    }
    

    持续补充中。。。。。。

    cs