当前位置 博文首页 > asd1358355022的博客:每日积累【Day2】Java 多线程重温Part 1
经过一段时间工作之后,返回来重温多线程相关的知识,会收获到不一样的东西哦。
? 多线程是指从软件或者硬件上实现多个线程并发执行的技术,它更多的是解决CPU调度多个进程的问
题,从而让这些进程看上去是同时执行(实际是交替运行的)。
这几个概念中,多线程解决的问题是最明确的,手段也是比较单一的,基本上遇到的最大问题就是线
程安全。在Java语言中,需要对JVM内存模型、指令重排等有深入了解,才能写出一份高质量的多线
程代码。
总结
分布式是从物理资源的角度去将不同的机器组成一个整体对外服务,技术范围非常广且难度非
常大,有了这个基础,高并发、高吞吐等系统很容易构建 。
高并发是从业务角度去描述系统的能力,实现高并发的手段可以采用分布式,也可以采用诸如缓存、CDN等,当然也包括多线程技术。
多线程技术则聚焦于如何使用编程语言将CPU调度能力最大化。
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();
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();
1、Thread使用继承方式去实现,如果当前类有其他父类,就不能使用Thread去实现。(类的单继承局限)
2、Runnable是接口类型,成员变量是共享的,Thread成员遍历如果需要线程共享则需要使用static或者其他的方式处理。
开发中优先选取实现Runnable接口。
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没有处理)
}
为了解决多线程线程安全安全问题,操作同步代码时,只能有一个线程在参与,其他线程在等待。相当于一个单线程过程,效率低
非静态同步监视器为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());
}
? 原子性、有序性、可见性
我们使用反汇编去看下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
关于这两条指令(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的所有权,过程如下:
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的异常的原因。
? 方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
? 以上引用csdn:https://blog.csdn.net/qq_26222859/article/details/53786134
|--------------------------------------------------------------------------------------------------------------------------------------|
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();
}
}