IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java多线程总结 -> 正文阅读

[Java知识库]Java多线程总结

基础知识

多线程的实现方式

  • 继承Thread类
  • 实现Runnable接口
注意点
  1. Thread 也是实现了 Runnable 接口的类
  2. 启动一个线程中要执行的代码都Runnable接口的run()方法中实现
线程安全问题

线程安全问题是由于多个线程共同操作同一个变量和实例而产生的

  1. 在非原子操作中,一个线程多变量的操作还没有完成,另一个线程对这个变量进行操作,就导致错误的发生
  2. 在没有同步的方法中,线程是异步执行的
常用Thread类API
  1. currentThread() , 返回当前正在执行的线程
  2. isAlive() 用于测试这个线程是否处于活动状态
  3. sleep() ,让当前在指定的时间内处于阻塞状态,时间到了后,资源允许可以恢复为可执行状态。

sleep() 在阻塞时,不会释放锁,若方法不是同步方法,同样会造成不同步的问题

public class Test {
    private String name = "sdf";
    private String password = "zzp";
    public void setValue(String u , String p) throws InterruptedException {
        this.name = u;
        if (Thread.currentThread().getName().equals("a")){
            System.out.println("线程a休眠5秒");
            Thread.currentThread().sleep(5000);
        }
        this.password = p;
    }
    public void printValue(){
        System.out.println(name + " " + password);
    }
    
    public static void main(String[] args)  {
        Test test = new Test();
        Thread thread1 = new Thread(()->{
            try {
                test.setValue("a" , "aa");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread1.setName("a");
        thread1.start();
        Thread thread2 = new Thread(()->{
            test.printValue();
        });
        thread2.start();
    }
}
  1. **geId()**用于获得线程ID
  2. yield() 让此线程放弃cpu资源,让给其他cpu,不是立即让出,让出的时间不一定
停止线程
  • interrupt(),为当前线程打一个停止标记,不会立即停止标记
判断线程停止
  • this.interrupted() : 判断此线程是否处于中断, 同时调用会会改变中断标志为false
  • this.isInterrupted() : 测试线程是否处于中断,不会清楚状态标志
    public boolean isInterrupted() {
        return interrupted;
    } 
    public static boolean interrupted() {
        Thread t = currentThread();
        boolean interrupted = t.interrupted;
        if (interrupted) {
            t.interrupted = false;
            clearInterruptEvent();
        }
        return interrupted;
    }

从jdk的源码也可以看出,判断中断的实现方式

停止线程的方法
  1. 异常法
class MyThread extends Thread {
    private String name = "sdf";
    private String password = "zzp";

    @Override
    public void run() {
        try {
            for (int i = 0; i < 50000; i++) {
                if (this.interrupted()) {
                    System.out.println("停住了");
                    throw new InterruptedException();
                }
                System.out.println("i =" + (i + 1));
            }
        } catch (InterruptedException e) {
            System.out.println("用catch停止了线程");
            e.printStackTrace();
        }

    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(50);
        myThread.interrupt();
    }
}
  1. 使用return 停止线程
import com.sun.jdi.ClassType;

import java.io.*;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Random;

class MyThread extends Thread {
    private String name = "sdf";
    private String password = "zzp";

    @Override
    public void run() {
        for (int i = 0; i < 50000; i++) {
            if (this.isInterrupted()) {
                System.out.println("停住了");
                return;
            }
            System.out.println("i =" + (i + 1));
        }
        for (int i = 0 ; i < 2 ; i ++) {
            System.out.println("我又继续继续运行了 , 线程并没有停止");
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
        Thread.sleep(10);
        myThread.interrupt();
    }
}
线程优先级
  1. 使用**setPriority()**可以设置优先级,优先级高的线程得到cpu资源的概率比较高
  2. A线程启动B线程,B线程与A线程的优先级是相同的
线程分类
  1. 用户线程
  2. 守护线程
    1. 守护线程的作用是为其他线程提供服务
synchronized 关键字的用法
  1. synchronized是对象锁
  2. synchronized,支持重入锁(一个线程获得对象锁后,再次请求该对象锁是可以再次得到的,称为可重入锁 , 一个synchronized 方法内部调用本类其他synchronized方法永远可以得到锁)
public class Server {
    synchronized public void service1(){
        System.out.println("service1");
        service2();
    }
    synchronized public void service2(){
        System.out.println("service2");
        service3();
    }
    synchronized public void service3(){
        System.out.println("service3");
    }
}

class Mythread extends Thread {
    @Override
    public void run() {
        Server server = new Server();
        server.service1();
    }
}
public class Test {
    public static void main(String[] args) {
        Mythread t = new Mythread();
        t.start();
    }
}

可重入锁支持在子类调用父类的同步方法

public class Main {
    public int i = 10;
    synchronized public void operateIMainMethod (){
        try {
            i--;
            System.out.println("main i =" + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Sub extends Main{
    synchronized public void operateISubMethod(){
        try {
            while (i > 0){
                i --;
                System.out.println("Sub i = " + i);
                Thread.sleep(100);
                this.operateIMainMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(()->{
            Sub sub = new Sub();
            sub.operateISubMethod();
        }).start();
    }
}
  1. 出现异常,锁自动释放
  2. 同步没有继承性 , 子类重写父类的方法,不会继承父类的同步
synchronized 同步块
  1. synchronized(this) , 锁定的是括号中的对象,可以使用任意对象作为对象监视器,只有括号中对象是相同的**synchronized(this)**才是同步,否则是异步的。
静态synchronized方法synchronized(class)代码块
  1. 锁定的是,*.java文件对应的.class类
volatile 关键字
  1. 修饰的变量保证了可见性
  2. 修饰的变量不具有原子性
  3. synchronized代码块有volatile同步功能,将线程中内存与公共内存变量进行同步。

线程通信

通知等待机制
  1. wait():阻塞调用此方法的线程,在执行run方法中的代码后,此线程进入到此对象监视器的等待队列中。
  2. notify():随机唤醒一个在此对象监视器上的等待队列的线程
  3. notifyAll():唤醒所有在此对象监视器上的等待队列上的所有线程
等待队列
  1. 执行wait()方法后进入此对象监视器的等待队列,在等待状态必须有显示唤醒才能进入同步队列

阻塞装态与等待状态的区别

  • 阻塞状态是线程阻塞在获取锁的状态
  • 等待状态是已经获取到了锁,执行了wait()方法,进入到等待队列
同步队列
  1. 对象监视器的锁别其它线程获取,所有想获得锁的线程都处在同步队列中等待获取锁。

利用通知等待机制实现生产者与消费者模式

  1. 单生产者和单消费者
import java.util.Random;

class P{
    private String lock;
    public P(){}
    public P(String lock){
        this.lock = lock;
    }
    public void setValue() throws InterruptedException {
        synchronized (lock){
            if (!ValueObject.value.equals("")){
                lock.wait();
            }
            ValueObject.value = "" + (1 + Math.random()*10);
            System.out.println("set=" + ValueObject.value);
            lock.notify();
        }
    }
}
class C{
    private String lock;
    public C(){}
    public C(String lock){
        this.lock = lock;
    }
    public void getValue() throws InterruptedException {
        synchronized (lock){
            if (ValueObject.value.equals("")){
                lock.wait();
            }
            System.out.println("get= " + ValueObject.value);
            ValueObject.value = "";
            lock.notify();
        }
    }
}
class ValueObject{
    public static String value = "";
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        String lock = "";
        P production = new P(lock);
        C consumer = new C(lock);
        Thread p = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    production.setValue();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread c = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    consumer.getValue();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        c.start();
        p.start();
    }
}
  1. 多生产者与多消费者

注意点

  1. 多生产者多多消费者中使用notify(),可能会造成死锁现象,因为nofify()是随机唤醒等待队列中的线程,如果唤醒的是同类的话,就会造成死锁,解决方法是使用notifyAll()
  2. 判断是否进入阻塞状态时,应该使用while循环判断消息队列中的消息
import java.util.Random;

class P{
    private String lock;
    public P(){}
    public P(String lock){
        this.lock = lock;
    }
    public void setValue() throws InterruptedException {
        synchronized (lock){
            while (!ValueObject.value.equals("")){
                System.out.println(Thread.currentThread().getName() + "wait了");
                lock.wait();
            }
            ValueObject.value = "" + (1 + Math.random()*10);
//            System.out.println(Thread.currentThread().getName() + "run了");
//            System.out.println("set=" + ValueObject.value);
            lock.notifyAll();
        }
    }
}
class C{
    private String lock;
    public C(){}
    public C(String lock){
        this.lock = lock;
    }
    public void getValue() throws InterruptedException {
        synchronized (lock){
            while (ValueObject.value.equals("")){
                System.out.println(Thread.currentThread().getName() + "wait了");
                lock.wait();
            }
//            System.out.println("get= " + ValueObject.value);
//            System.out.println(Thread.currentThread().getName() + "run了");
            ValueObject.value = "";
            lock.notifyAll();
        }
    }
}
class ValueObject{
    public static String value = "";
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        String lock = "";
        P production = new P(lock);
        C consumer = new C(lock);
        Runnable r1 = ()->{
            for (int i = 0; i < 100; i++) {
                try {
                    production.setValue();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable r2 = ()->{
            for (int i = 0; i < 100; i++) {
                try {
                    consumer.getValue();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 3; i++) {
            new Thread(r1,"生产者"+i).start();
            new Thread(r2,"消费者"+i).start();
        }
    }
}
通过管道进行线程间通信:字符流
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.charset.StandardCharsets;

class WriteData{
    public void write(PipedOutputStream out) throws IOException {
        System.out.println("write:");
        for (int i = 0; i < 10; i++) {
            String outData = "" + (i+1);
            out.write(outData.getBytes(StandardCharsets.UTF_8));
        }
        System.out.println();
        out.close();
    }
}
class ReadData{
    public void readMethod(PipedInputStream input) throws IOException {
        System.out.println("read :");
        byte[] byteArray = new byte[20];
        int readLength = input.read(byteArray);
        while(readLength != -1){
            String newData = new String(byteArray,0,readLength);
            System.out.println(newData);
            readLength = input.read(byteArray);
        }
        System.out.println();
        input.close();
    }
}
class ThreadWrite implements Runnable{
    WriteData write;
    private PipedOutputStream out;
    public ThreadWrite(WriteData write , PipedOutputStream out){
        this.write = write;
        this.out = out;
    }
    @Override
    public void run() {
        try {
            write.write(out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
class ThreadRead implements Runnable{
    private ReadData read;
    private PipedInputStream input;
    public ThreadRead(ReadData read , PipedInputStream input){
        this.input = input;
        this.read = read;
    }

    @Override
    public void run() {
        try {
            read.readMethod(input);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class TestPipe {
    public static void main(String[] args) throws IOException, InterruptedException {
        WriteData write = new WriteData();
        ReadData read = new ReadData();

        PipedInputStream input = new PipedInputStream();
        PipedOutputStream out = new PipedOutputStream();
        out.connect(input);
        Thread threadRead = new Thread(new ThreadRead(read , input));
        Thread threadWrite =new Thread(new ThreadWrite(write ,out));
        threadRead.start();
        Thread.sleep(2000);
        threadWrite.start();

    }
}
重写回顾面向对象的思想

写这个程序的时候又重写思考了什么是面向对象的思想

  1. 封装,什么是封装呢,学习了半年的面向对象,目前理解的面向对象就是,一个程序需要AB,C三个内容,把A,B,C封装为三个类,A,B,C只需要要知道其他类的对象有什么方法,不需要知道怎么实现的了。这也就形成了高内聚,低耦合,把一个模块集中在一块,只让其他模块知道自己有什么方法,不同模块之间没有联系,排查bug时可以精确定位到某一个模块。
  2. 继承,是为了提高代码的利用率,俩个模块相似度极高,A与B模块特别相似,B已经封装好了,此时如果再次重新封装A会有大量重复的代码,此时A继承B,改动A中部分代码就可以实现封装,减少了大量的重复代码
  3. 多态,多态是建立在继承的基础之上,用父类引用不同子类,调用不同的方法,会有不同的实现,这也避免了大量重复的代码。
关于join()
  1. 此方法的作用呢就是,将调用次此方法的线程进入等待状态,直到获得在此线程中调用的线程的锁

这个解释可能有点不清楚,先看一个简单的代码。

public class learnJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread r = new Thread(()->{
            System.out.println("准备join");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        r.start();
        r.join();
        System.out.println("main线程在r线程后执行");
    }
}
//经过测试main线程总是在r线程之后执行

这是为什么呢,我们来看join的源代码

 public final synchronized void join(final long millis)
    throws InterruptedException {
        if (millis > 0) {
            if (isAlive()) {
                final long startTime = System.nanoTime();
                long delay = millis;
                do {
                    wait(delay);
                } while (isAlive() && (delay = millis -
                     TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) >0);
            }
        } else if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            throw new IllegalArgumentException("timeout value is negative");
        }
    }

  1. 首先join()是一个synchronized()的方法,说明在上面程序中main线程要执行join()方法线程得获得synchronized(this)锁,此处的this是什么呢应该是就在main线程中执行的线程了
  2. 也就是说,r.join()执行已经,main线程已经进入等待状态了,只有获得了线程r的锁,而且被唤醒后才能继续执行,我们从join()源码中看到了进入等待状态,但是唤醒在哪儿呢,这个经过查资料,JVM进行的唤醒。

ThreadLocal

此处先引用俩篇博客

  1. 关于ThreadLocal的讲解
  2. 关于Java引用

作用:

  1. 为不同的线程提供可以全局访问的
public class TestLocal {
    public static ThreadLocal<String> t1 = new ThreadLocal<>();

    public static void main(String[] args) {
        Runnable r = ()->{
          if (TestLocal.t1.get() == null){
              System.out.println(Thread.currentThread().getName()+"ThreadLocal目前是Null");
          }
          TestLocal.t1.set(Thread.currentThread().getName() + "有值了");
          System.out.println(t1.get());
        };
        for (int i = 0; i < 10; i++) {
            new Thread(r,"" + i).start();
        }
    }
}

对于 Java Web 应用而言,Session 保存了很多信息。很多时候需要通过 Session 获取信息,有些时候又需要修改 Session 的信息。一方面,需要保证每个线程有自己单独的 Session 实例。另一方面,由于很多地方都需要操作 Session,存在多方法共享 Session 的需求。如果不使用 ThreadLocal,可以在每个线程内构建一个 Session实例,并将该实例在多个方法间传递,如下所示。

public class SessionHandler {

  @Data
  public static class Session {
    private String id;
    private String user;
    private String status;
  }

  public Session createSession() {
    return new Session();
  }

  public String getUser(Session session) {
    return session.getUser();
  }

  public String getStatus(Session session) {
    return session.getStatus();
  }

  public void setStatus(Session session, String status) {
    session.setStatus(status);
  }

  public static void main(String[] args) {
    new Thread(() -> {
      SessionHandler handler = new SessionHandler();
      Session session = handler.createSession();
      handler.getStatus(session);
      handler.getUser(session);
      handler.setStatus(session, "close");
      handler.getStatus(session);
    }).start();
  }
}

该方法是可以实现需求的。但是每个需要使用 Session 的地方,都需要显式传递 Session 对象,方法间耦合度较高。

这里使用 ThreadLocal 重新实现该功能如下所示。

public class SessionHandler {

  public static ThreadLocal<Session> session = ThreadLocal.<Session>withInitial(() -> new Session());

  @Data
  public static class Session {
    private String id;
    private String user;
    private String status;
  }

  public String getUser() {
    return session.get().getUser();
  }

  public String getStatus() {
    return session.get().getStatus();
  }

  public void setStatus(String status) {
    session.get().setStatus(status);
  }

  public static void main(String[] args) {
    new Thread(() -> {
      SessionHandler handler = new SessionHandler();
      handler.getStatus();
      handler.getUser();
      handler.setStatus("close");
      handler.getStatus();
    }).start();
  }
}

使用 ThreadLocal 改造后的代码,不再需要在各个方法间传递 Session 对象,并且也非常轻松的保证了每个线程拥有自己独立的实例。

如果单看其中某一点,替代方法很多。比如可通过在线程内创建局部变量可实现每个线程有自己的实例,使用静态变量可实现变量在方法间的共享。但如果要同时满足变量在线程间的隔离与方法间的共享,ThreadLocal再合适不过。

InheritableThreadLocal的区别
  1. 区别在与InheritableThreadLocal类可以在子线程从父线程中取得继承下来的值

ReentranLock类

  1. 是一个互斥锁,lock也是对象锁
Condition类
  1. **await()**与wait()方法类似
  2. **signal(),signalAll()**与notify(),notifyAll()方法类似

实现的单消费者与单生产者,多消费者和生产者与上面的用wait的类似

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Son {
    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    boolean flag = true;
    public void son() throws InterruptedException {
        lock.lock();
        System.out.println("ljh you are my son , 请说父亲好");
        while(flag){
            condition.await();
        }
        flag = true;
        condition.signal();
        lock.unlock();
    }
    public void father() throws InterruptedException {
        lock.lock();
        while(!flag){
            condition.await();
        }
        System.out.println("我是你father , 乖儿子");
        flag = false;
        condition.signal();
        lock.unlock();
    }
    public static void main(String[] args) throws InterruptedException {
        Son ljh = new Son();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    ljh.son();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
//        Thread.sleep(1000);
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    ljh.father();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
公平锁与非公平锁
  1. 所谓公平与非公平,就是进入等待队列后,唤醒的顺序,如果按照进入等待队列是时间唤醒,那么就是公平的,随机唤醒就不公平的。
  2. **ReentranLock()**支持公平锁与非公平锁

ReentranReadWriteLock类

  1. 只要和写有关线程都是互斥锁
  2. 只有读的情况是共享锁

乐观锁与悲观锁

悲观锁认为线程拿到锁,一定会对变量进行脏读,拿到锁后,其他线程就进入阻塞状态

  1. synchronized , Lock锁都是悲观锁

乐观锁,总是乐观地假设最好的情况,每次去拿数据的时候都认为别人不会修改这个数据,所以不会上锁,只会要对数据进行更新时判断一下在此期间(拿到数据到更新的期间)别人有没有去更改这个数据,可以使用版本号机制和CAS算法实现。

CAS 机制

AS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

举个例子

  1. 线程1拿到了变量a = 10,
  2. 此时发生了线程切换,运行在线程2上
  3. 线程2将a 更新为11
  4. 切换为线程1 , 内存地址与旧值A对比,发现不同,修改失败,然后重新获取(此过程称为自旋)
  5. 线程再次进行比较

缺点

CAS的缺点:

1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

偏向锁,轻量级锁,重量级锁

要是分清这几个概念首先必须知道对象头这个东西

对象头

对象头是java中对象都具有的属性,是jvm在编译和运行阶段读取的信息。对象头包含三个部分:

  1. mark word
  2. 指针向类的指针
  3. 数组的长度(只有数组的对象用到)

这3个中最复杂的是MarkWordMarkWord用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。

偏向锁

当只有一个线程重复同一个锁时,使用阻塞机制,消耗太大。便有了偏向锁的概念

什么是偏向锁,顾名思义就偏向一个线程的锁,

偏向锁的过程

  1. 一个线程访问同步块时,在锁的对象监视器的对象头中,在栈帧中获取锁偏向的线程id,
  2. 以后该线程获取锁时,只需判断对象监视器的对象头的markWork是否存在该线程的偏向锁,
  3. 成功,则获取锁,
  4. 失败,查看此对象头偏向锁标识是否设置为1 ,如果设置为1 (说明是偏向锁但是还没有任何线程获取),所以将使用CAS机制将对象头的偏向锁指向当前线程的线程id
  5. 查看此对象头偏向锁标识是否设置为1 ,如果没有设置为1,则说明已经不是偏向锁了,需要用CAS机制获取锁

偏向锁的撤销

偏向锁在出现竞争时,释放锁 ,需要run内代码执行完

  1. 出现竞争时
  2. 把当前的在执行的线程1的run方法执行完,暂停该线程
  3. 检查线程是否存活
  4. 不存活,偏向锁标志设置为0
  5. 线程2使用CAS机制,将偏向锁指向线程2
  6. 线程1仍然存在,暂停线程1;
  7. 设置锁标志位为00(变为轻量级锁),偏向锁为0;
轻量级锁

加锁

  1. 将对象的mark word 复制到自己空间中
  2. 使用CAS机制将Mark word 替换为指向自己锁记录的指针
  3. 成功,则获取锁,失败,通过自旋获取

解锁

  1. 解锁时,使用CAS操作将对象头中的Mark word换为原来的内容,就是线程中存储的次对象头的Mark word换回去
  2. 成功,释放锁
  3. 失败,说明有竞争的线程,此时膨胀为重量级锁

轻量级锁,竞争锁是不断通过自选获取锁,会占用资源

重量级锁,直接将竞争的线程进行阻塞,不会占用CPU资源

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-26 11:28:38  更:2022-04-26 11:29:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:57:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码