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并发编程学习篇1_JUC概述、虚假唤醒问题、JUC的生产者和消费者关系、8锁问题(对象锁、类锁区别) -> 正文阅读

[Java知识库]Java并发编程学习篇1_JUC概述、虚假唤醒问题、JUC的生产者和消费者关系、8锁问题(对象锁、类锁区别)

目录

  • 回顾
  • synchronized下的生产者、消费者关系之虚假唤醒问题
  • JUC下的生产者、消费者关系
  • 8锁问题(对象锁、类锁)
    1. 创建一个Phone实例多线程调用两个方法,问哪一个先执行?
    2. 创建一个Phone实例多线程调用两个方法,其中第一个线程调用的方法中加延迟,问哪一个先执行?
    3. 创建一个Phone实例多线程调用两个方法,其中一个是普通方法,而且该线程位置靠后,问哪一个先执行?
    4. 创建两个Phone实例多线程调用两个方法,问哪一个先执行?
    5. 创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
    6. 创建两个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
    7. 创建一个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
    8. 创建两个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?

一、回顾

概念

  • JUC即并发编程,也是多线程的进阶版
  • 主要涉及的三个类java.util.concurrent、 java.util.concurrent.automic、 java.util.concurrent.locks
  • 注意java实际上是开启不了线程的,而是调用本地方法,底层是c++开启线程,java是运行在虚拟机上的,无法直接操作硬件

线程的几个状态

  • new 新生
  • runnable 运行
  • blocked 阻塞
  • waiting 等待
  • timed_witing 超时等待
  • terminated 终止

wait和sleep的区别

  • 来自不同的类,wait来自Object,sleep来自Thread
  • 锁是否是否释放不同,wait会释放锁,sleep不会释放锁
  • 使用范围不同,wait必须放在同步代码块中(与显示锁Lock对应)
  • 是否需要异常捕获,wait不需要异常捕获,sleep需要异常捕获

Synchronized 和 Lock的区别

  • Synchronized 是内置java 关键字,Lock是接口
  • Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
  • Synchronized 会自动释放锁,Lock必须手动释放
  • Synchronized 线程1获得锁,则洽谈线程会一直等待,Lock 锁下的线程就不一定会一直等
  • Synchronized 是可重入锁,不可中断的,非公平,Lock 可重入锁,可以判断锁,非公平锁可以设置公平性
  • Synchronized 适合对少量代码加锁,Lock 适合大量同步代码

二、虚假唤醒问题

举栗synchronized下的生产者、消费者模式场景

  • 资源类的属性num 为 0,方法是 对num 加1 和 减1
  • 初始是两个线程进行wait和notifyAll通信,保证num为0时候+1,为1时-1
  • 线程A是对num进行10次的+1,线程B是对num进行10次的-1
  • 线程A执行+1的条件是if(num == 0) ,否则wait等待,执行完 +1 就 notifyAll 唤醒其他线程(此时只有B线程);B线程同理
  • 结果可以发现两个线程间的 线程间通信 正常 可以使逻辑结果正常
package synchronized_productor_consumer;

public class Demo {
    public static void main(String[] args) {

        Data data = new Data();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程A").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程B").start();

    }
}


/**
 * 待操作的Data资源类,需要解耦
 */

class Data{
    private int num = 0;

    public synchronized void increment() throws InterruptedException {
        if(num != 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num ++;
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        if(num == 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num --;

        this.notifyAll();
    }


}

输出:
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1

现在是A一个线程做 + 1,B一个线程做 - 1,如果是多个线程呢?


package synchronized_productor_consumer;

public class Demo {
    public static void main(String[] args) {

        Data data = new Data();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程A").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程B").start();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程C").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程D").start();

    }
}


/**
 * 待操作的Data资源类,需要解耦
 */

class Data{
    private int num = 0;

    public synchronized void increment() throws InterruptedException {
        if(num != 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num ++;
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        if(num == 0){
            // 等待
            this.wait();
        }
        System.out.println(Thread.currentThread().getName() + "=> " + num);
        num --;

        this.notifyAll();
    }


}


输出:
线程A=> 0
线程D=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程D=> 0
线程D=> -1
线程D=> -2
线程D=> -3
线程D=> -4
线程D=> -5
线程D=> -6
线程D=> -7
线程D=> -8
线程A=> -9

虚假唤醒问题分析

  • 结果出现问题的原在于 资源类内部if判断,因为if判断只进行一次,造成多个线程之间的虚假唤醒问题
    在这里插入图片描述

  • 解决办法就是将 if 判断 换成 while 即可,避免虚假唤醒问题

三、JUC下的生产者、消费者关系

Lock下的线程通信

  • Condition接口取代了Object监视器方法(wait、notify、notifyAll)使用自己的方法完成线程间通信
  • 使用lock对象的newCondition()方法可以获得condition对象,该对象有一个await()方法和singal()方法
  • 同时Condition支持精准的通知、唤醒线程(使线程按照顺序执行)

场景举栗

package lock_productor_consumer;

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

public class Demo {
    public static void main(String[] args) {

        Data data = new Data();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程A").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程B").start();

        // 线程A,进行加1
        new Thread(()->{
            try {
                // 10 次操作
                for (int i = 0; i < 10; i++) {
                    data.increment();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程C").start();

        // 线程B,进行减1
        new Thread(()->{
            try {

                for (int i = 0; i < 10; i++) {
                    data.decrement();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        },"线程D").start();

    }
}


/**
 * 待操作的Data资源类,需要解耦
 */

class Data{
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public  void increment()  {

        // 加锁
        lock.lock();

        try {
            while(num != 0){
                // 等待
                condition.await();


            }
            System.out.println(Thread.currentThread().getName() + "=> " + num);
            num ++;
            // 唤醒
            condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }


    }

    public  void decrement()  {
        // 加锁
        lock.lock();

        try {
            while(num == 0){
                // 等待
               condition.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> " + num);
            num --;
            // 唤醒
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            lock.unlock();
        }


    }


}
输出:
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1

Condition控制线程间精准通信

  • 场景如三个线程顺序执行
    • A线程先执行,使达到B线程的执行条件,唤醒B线程,A线程处于等待状态;
    • B线程接着执行,使达到C线程的执行条件,唤醒C线程,B线程处于等待状态;
    • C线程接着执行,使达到A线程的执行条件,唤醒A线程,C线程处于等待状态;
  • 实现原理是condition对象可以设置多个,每个对象可以和线程绑定一块。一个condition对象服务一个线程,因为加锁了,所以其他线程正在等待,而有执行条件的线程会接到通知从 等待--->执行--->更新num状态---->等待
package study_condition;

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

public class Demo {

    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.A();
            }
        },"1线程").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.B();
            }
        },"2线程").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.C();
            }
        },"3线程").start();

    }
}

class Data {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1;

    public void A(){
        lock.lock();

        try {
            while(num != 1){
                // 等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> A 方法" );
            num  = 2;

            // 唤醒执行方法B的线程
            condition2.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
    public void B(){
        lock.lock();

        try {
            while(num != 2){
                // 等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> B 方法" );
            num  = 3;

            // 唤醒执行方法B的线程
            condition3.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
    public void C(){
        lock.lock();

        try {
            while(num != 3){
                // 等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=> C 方法" );
            num  = 1;

            // 唤醒执行方法B的线程
            condition1.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }

}

输出:

1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法

四、8锁问题(对象锁、类锁)

初始场景

  • Phone资源类有 void sendMessage() 、void call()两个方法
  • main方法开启 多线程场景 进行测试,8种情况可以提出8个问题,即8锁问题
  • JUC下线程sleep方法:imeUnit.SECONDS.sleep(1);
package eight_locks;

public class Demo1 {


    public static void main(String[] args) {
        
    }
}


class Phone {

    public synchronized void sendMessage(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

1.创建一个Phone实例多线程调用两个方法,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo1 {


    public static void main(String[] args) {

        Phone p = new Phone();

        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            p.call();
        }).start();

    }
}


class Phone {

    public synchronized void sendMessage(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话


结果分析

  • 因为synchronized关键字 是对 该资源类的对象 上锁,因此哪个线程先拿到对象锁,就先执行
  • 因此上面的线程先拿到锁,先执行,如果还不理解接着看问题2
2.创建一个Phone实例多线程调用两个方法,其中第一个线程调用的方法中加延迟,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo1 {


    public static void main(String[] args) {

        Phone p = new Phone();

        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            p.call();
        }).start();

    }
}


class Phone {

    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话

结果分析

  • 原理同上。还是上面的线程先拿到 资源类 锁对象
  • 同一资源类下,synchronized修饰的方法 使用的是当前资源类 对象锁。谁先拿到谁执行。
3.创建一个Phone实例多线程调用两个方法,其中一个是普通方法,而且该线程位置靠后,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo3 {


    public static void main(String[] args) {

        Phone3 p = new Phone3();

        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            p.watchMovie();
        }).start();

    }
}


class Phone3 {

    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }

    public void watchMovie(){
        System.out.println("看电影");
    }
}

输出:
看电影
发短信

结果分析

  • watchMovie()普通方法,不受同步方法的影响,也就是不受 锁 的影响
  • 因为 sendMessage()方法体 中有延迟语句,因此会后输出,其实 抛去延迟来说,两个输出结果为不一定
4.创建两个Phone实例多线程调用两个方法,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo4 {


    public static void main(String[] args) {

        Phone4 one = new Phone4();
        Phone4 two = new Phone4();

        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            two.call();
        }).start();

    }
}


class Phone4 {

    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }


}
输出:
打电话
发短信

结果分析

  • 区别于问题1,该情况是 两个资源类对象分别开启两个线程,因此锁对象 并无互相干扰,因为线程延时的原因,打电话 先输出
  • 由此可在次证明synchronized加载方法前面锁住的是 调用该方法的 实例对象,多个实例之间的锁 不干扰
5.创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo5 {


    public static void main(String[] args) {

        Phone5 one = new Phone5();


        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            one.call();
        }).start();

    }
}


class Phone5 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized static void call(){
        System.out.println("打电话");
    }


}

输出:
发短信
打电话

问题分析

  • 加上static关键字之后,两个方法都变为静态方法。
  • 发短信 在前面的原因是 synchronized 加 静态方法 锁的是 ClassPhone5.Class只有单个。因此第二个线程需要 等待第一个线程释放Class锁才能执行。
6.创建两个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo6 {


    public static void main(String[] args) {

        Phone6 one = new Phone6();
        Phone6 two = new Phone6();


        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            two.call();
        }).start();

    }
}


class Phone6 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized static void call(){
        System.out.println("打电话");
    }


}

输出:
发短信
打电话

结果分析

  • 两个对象的Class只有一个Phone6.Class
  • 原理同5,synchronized 加 静态方法 锁的是 Class
7.创建一个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo7 {


    public static void main(String[] args) {

        Phone7 one = new Phone7();



        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            one.call();
        }).start();

    }
}


class Phone7 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized  void call(){
        System.out.println("打电话");
    }


}

输出:
打电话
发短信

结果分析

  • 打电话 先输出的原因是 Phone7 实例 和 Phone7.Class 分别被锁,两个线程之间并无影响,因为线程延迟的原因。
  • 再次 证明 synchronized 锁的是 类实例即对象synchronized 加 静态方法 锁的是 Class
8.创建两个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
package eight_locks;

import java.util.concurrent.TimeUnit;

public class Demo7 {


    public static void main(String[] args) {

        Phone8 one = new Phone8();
        Phone8 two = new Phone8();



        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            two.call();
        }).start();

    }
}


class Phone7 {

    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("发短信");
    }

    public synchronized  void call(){
        System.out.println("打电话");
    }


}
输出:
打电话
发短信

结果分析

  • 原理同7。两个线程分别锁的是 Phone8.Class 和 Phone8实例,因为线程延迟,打电话 才会优先输出。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-11 16:30:26  更:2021-07-11 16:33:07 
 
开发: 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年5日历 -2024/5/20 10:12:10-

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