开发者社区> 力不竭战不止> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Java 多线程重温Part 1 持续补充中

简介: Java 多线程重温Part 1 持续补充中
+关注继续查看

Java 多线程重温Part 1

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


什么是多线程?

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


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


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


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


程代码。


总结


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


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


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


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


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

创建一个类继承Thread

重写Thread类里面的run方法

创建继承Thread的类对象

调用此对象的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();

多线程常用的方法:

start() 启动一个线程,调用run方法

currentThread() 静态方法,返回当前执行代码的线程

getName() 获取当前线程名称

setName() 设置当前线程名称

yield() 释放当前线程执行权

join() 在a线程中调用了b线程的join方法,a进入阻塞状态直到b线程执行完毕之后才继续执行a线程。

stop() 已停用,强制停止当前线程

sleep(long n) 指定当前线程睡眠n毫秒

isAlive() 判断当前线程是否存活

setPriority(int priority) 更改线程的优先级。


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

创建一个类实现Runnable接口

实现Runnable接口里面的run方法

创建实现Runnable接口的类对象

将创建好的类对象放入Thread类的构造器中,创建出此Thread对象

调用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的所有权,过程如下:


如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。

如果其他线程已经占用了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();
    }

}

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【java多线程】多线程为什么跑的比单线程还要慢?!
【java多线程】多线程为什么跑的比单线程还要慢?!
0 0
十分钟掌握java多线程进阶
十分钟掌握java多线程进阶
0 0
java多线程实现体重跟踪
java多线程实现体重跟踪
0 0
Java并发编程之多线程
我们首先,先要了解什么是进程,什么是线程。
0 0
Java多线程与并发框(完结篇)——再看不懂我找不到女朋友
Java多线程与并发框(完结篇)——再看不懂我找不到女朋友
0 0
JAVA多线程 | 实现用户任务排队 | 预估排队时长
JAVA多线程 | 实现用户任务排队 | 预估排队时长
0 0
Java 基础入门 | 第十五章 Java多线程
目录线程的概念线程的使用线程的状态线程同步问题死锁和解决办法线程的概念什么是线程在一个程序中同时运行的多个独立流程,每一个独立的流程就是一个线程线程并发多个线程并发执行主线程 CPU 优先级 时间片 资源当
0 0
Java 多线程之间如何通知通信
Java 多线程之间如何通知通信
0 0
Java多线程进阶——JUC常见类和死锁
java中的JUC就是java.util.concurrent包下的一些标准类或者接口,这个包里的东西都是和多线程相关的,以下就是这个包中常见的类和接口的用法及示例:
0 0
+关注
力不竭战不止
本人csdn地址:https://blog.csdn.net/asd1358355022?spm=1000.2115.3001.5343
文章
问答
文章排行榜
最热
最新
相关电子书
更多
JAVA 应用排查全景图
立即下载
Java工程师必读手册
立即下载
Java应用提速(速度与激情)
立即下载