返回顶部
首页 > 资讯 > 后端开发 > Python >彻底搞懂Java多线程(二)
  • 649
分享到

彻底搞懂Java多线程(二)

2024-04-02 19:04:59 649人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录Java中的锁1.synchronized锁(JVM层的解决方案,也叫监视器锁)2.手动锁Locksynchronized锁synchronized使用场景1.使用synchro

Java中的锁

Java中的加锁操作有两种:

1.synchronized锁(jvm层的解决方案,也叫监视器锁)

操作系统的层面使用的是互斥锁(mutex lock)

在Java中放在了对象头中。

2.手动锁Lock

操作锁的流程

  • 1.尝试获取锁
  • 2.使用锁
  • 3.释放锁

synchronized锁


package ThreadDeom;

class Counter2 {
    private static volatile int count = 0;
    public void increase() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }
    public void decrease() {
        for (int i = 0; i < 10000; i++) {
            count--;
        }
    }
    public int getCount() {
        return count;
    }
}

public class ThreadDemo19 {
    public static void main(String[] args) throws InterruptedException {
        //声明锁对象,任何的对象都可以作为锁
        Object lock = new Object();
        Counter2 counter2 = new Counter2();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //使用锁
                synchronized (lock) {
                    counter2.decrease();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                counter2.increase();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter2.getCount());
    }
}

结果是:

在这里插入图片描述

synchronized使用场景

1.使用synchronized来修饰代码块(可以给任意的对象进行加锁操作)


public class ThreadDemo19 {
    public static void main(String[] args) throws InterruptedException {
        //声明锁对象,任何的对象都可以作为锁
        Object lock = new Object();
        Counter2 counter2 = new Counter2();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //使用锁
                synchronized (lock) {
                    counter2.decrease();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                counter2.increase();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter2.getCount());
    }
}

在这里插入图片描述

2.使用synchronized来修饰静态方法(对当前的类进行加锁的操作)


package ThreadDeom;

class Counter1 {
    private static volatile int count = 0;
    public void increase() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }
    public void decrease() {
        for (int i = 0; i < 10000; i++) {
            count--;
        }
    }
    public int getCount() {
        return count;
    }
}

public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException {
        Counter1 counter1 = new Counter1();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                counter1.decrease();
            }
        });
        Thread thread2 = new Thread(() -> {
            counter1.increase();
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter1.getCount());
    }
}

在这里插入图片描述

3.使用synchronized来修饰普通的方法(对当前类的实例来进行加锁)


package ThreadDeom;

public class ThreadDemo20 {
    private static int num = 0;
    private static final int maxSize = 100000;
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo20 threadDemo20 = new ThreadDemo20();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo20.increase();
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
               threadDemo20. decrease();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(num);
    }
    //给静态的方法进行加锁,被加的锁是当前的对象。
//    public synchronized static void increase(){
    //给普通的方法进行加锁的操作
    public synchronized void increase() {
        for (int i = 0; i < maxSize; i++) {
            num++;
        }
    }
    //    public synchronized static void decrease(){
    public synchronized void decrease() {
        for (int i = 0; i < maxSize; i++) {
            num--;
        }
    }
}

在这里插入图片描述

synchronized注意事项

1.加锁的时候一定要使用同一把锁对象

Lock类的使用

也叫手动锁


package ThreadDeom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo22 {
    private static int number = 0;
    private static final int maxSize = 100000;
    public static void main(String[] args) {
        //创建lock锁对象,lock是接口,不能实列化
        Lock lock = new ReentrantLock();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < maxSize; i++) {
                lock.lock();
                try {
                    number++;
                } finally {
                    lock.unlock();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < maxSize; i++) {
                lock.lock();
                try {
                    number--;
                } finally {
                    lock.unlock();
                }
            }
        });
        System.out.println(number);
    }
}

在这里插入图片描述

Lock锁使用的注意事项

lock()操作一定要放在try外面

如果放在try的里面:

1.try中抛出了异常,还没有加锁就释放了finally中的锁的操作了

2.如果放在了try,没加锁就释放了锁,就会抛出异常,就会将业务代码中的异常吞噬掉👇如果一定要放的话,将lock()放在try的第一行。


package ThreadDeom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo23 {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        try{
            System.out.println(1/0);
            lock.lock();
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述

公平锁、非公平锁

公平锁的调度:

一个线程释放锁。

主动唤醒“需要得到锁”的队列来得到锁。

非公平锁

当一个线程释放锁之后,另一个线程刚好执行到获取锁的代码就可以直接获取锁。

Java中的所有锁默认都是非公平锁。

非公平锁的性能更高。

ReentrantLock可以设置非公平锁。

公平锁


package ThreadDeom;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo24 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                reentrantLock.lock();
                try {
                    System.out.println("thread1");
                } finally {
                    reentrantLock.unlock();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                reentrantLock.lock();
                try {
                    System.out.println("thread2");
                } finally {
                    reentrantLock.unlock();
                }
            }
        });
        Thread.sleep(100);
        thread1.start();
        thread2.start();
    }
}

打印的结果是无序的

在这里插入图片描述

如果设置为公平锁:👇

在这里插入图片描述

在这里插入图片描述

thread1和thread2 交替输出

synchronzied 和 Lock 的区别

1.synchronzied可以自动的进行加锁和释放锁,而Lock需要手动的加锁、释放锁。

2.Lock是Java层面的锁实现,而synchronzied 是JVM层面锁的实现

3.synchronzed 即可以修饰代码块,又可以修饰普通方法和静态的方法,而Lock 只能修饰代码块

4.synchronized 实现的是 非公平的锁,而Lock 可以实现公平锁。

5.lock的灵活性更高

死锁

在两个或两个以上的线程运行中,因为资源的抢占而造成线程一直等待的问题。看👇:


package ThreadDeom;

public class ThreadDemo25 {
    public static void main(String[] args) throws InterruptedException {
        Object lockA = new Object();
        Object lockB = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "获取到lockA");
                //让线程2获取lockB
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockB");
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //线程2获取资源B
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    //让线程1先获取到锁lockA
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lockA) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    }
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}

这就造成了死锁

在这里插入图片描述

造成死锁的四个条件

1.互斥条件:

当资源被一个线程拥有之后,就不能被其它的线程拥有了

2.拥有请求条件:

当一个线程拥有了一个资源之后,又试图请求另一个资源。

3.不可剥夺条件:

当一个线程拥有了一个资源之后,如果不是这个线程主动的释放资源,其他线程就不能拥有这个线程。

4.环路等待条件:

两个或两个以上的线程拥有了资源之后,试图获取对方的资源的时候形成了一个环路。

死锁的解决方案

解决请求拥有和环路等待。

最有效的解决方案就是控制加锁的顺序。


package ThreadDeom;

public class ThreadDemo26 {
    public static void main(String[] args) throws InterruptedException {
        Object lockA = new Object();
        Object lockB = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "获取到lockA");
                //让线程2获取lockB
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockB) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockB");
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "获取到lockA");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (lockB) {
                        System.out.println(Thread.currentThread().getName() + "获取到lockB");
                    }
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}

在这里插入图片描述

线程间通信

线程之间的通讯是指在一个线程中的操作可以影响另一个线程。

wait/notify机制的原理

拥有相同锁的线程之间才能使用wait/notify机制。

wait()是Object()的方法,它的作用是是当前执行wait()方法的线程等待,在wati()所在的代码出停止执行,并释放锁,直到接到通知或者被中断为止。即在调用wait()的方法之前,线程必需先获取到对象级别的锁,也就是只能在同步方法或者同步块中使用wait()方法。

如果在使用wait()方法之前线程没有获得相应的锁,那么程序在执行时就会抛出异常。

notify()方法要在同步方法或者同步块中执行,即在调用notify()方法之前,线程必需要先获取到锁对象。如果线程没有持有锁对象的话,那么也会抛出异常。该方法用来通知可能在等待该锁的其它线程,如果有多个线程,那么则按照执行wait()方法的顺序来对处于wait()方法的线程发出通知,并使该线程重新获取锁。执行notify()方法之后,当前线程不会马上释放锁,处于wait()状态的线程也不会立马得到这个对象锁。而是要等notify的synchronized同步区域执行完成之后才会释放锁,处于wait()状态的线程才会得到锁对象。

总结:wait()方法用于让线程停止运行,而notify()方法用于通知暂停的线程继续运行。

在使用wait()或者notify()方法之前没有对象锁,就会报异常👇:


        lock.notify();

在这里插入图片描述

正确的使用之后


package ThreadDeom;

public class ThreadDemo27 {
    //设置锁对象
    private static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("在wait()");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("被notify()唤醒之后");
                }
            }
        });
        thread.start();
        Thread.sleep(1000);
        synchronized (lock) {
            lock.notify();
        }
    }
}

在这里插入图片描述

注意:使用wait()方法的时候一定要和线程的锁对象是一个锁。

notifyAll

多线程的情况下使用notify()方法只可以唤醒一个线程👇

在这里插入图片描述


package ThreadDeom;

public class ThreadDemo28 {
    //设置锁对象
    private static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("thread1在wait()");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread1被notify()唤醒之后");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("thread2在wait()");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread2被notify()唤醒之后");
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("thread3在wait()");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread3被notify()唤醒之后");
                }
            }
        });

        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
        synchronized (lock) {
            System.out.println("主线程调用notify()之后");
            lock.notify();
        }
    }
}

那么如果使用notifyAll()方法呢?

在这里插入图片描述

可以看到所有的线程都被唤醒了

在这里插入图片描述

那么使用notify()唤醒的线程有没有什么顺序呢?

使用notify()唤醒线程的顺序是正序、倒序、还是随机的,这取决与JVM的具体实现,并不是所有的JVM在执行notify()时都是按照wait()的执行顺序进行唤醒的,也不是所有的notidyAll()都是按照wait()方法的倒序进行唤醒的,这取决于JVM的具体实现。

wait()和notify()不能唤醒指定的线程。

wait()和sleep()的区别

也可以让wait()等待指定的时间,如果超过给定的时间,wait()不会无限期的等待下去.

在这里插入图片描述

没有被notify()唤醒,过了1000毫秒之后会自动停止。

在这里插入图片描述

wait()在不传入任何参数的时候,线程会进入waiting 的状态,而在wait()中加入一个大于0的参数的时候,线程会进入time_wating的状态。

sleep()和wait()的区别 : 线程在sleep()的时候是不会释放锁的,而执行wait()的时候它就会释放锁。👇:


package ThreadDeom;
import jdk.nashorn.internal.ir.Block;

public class ThreadDemo29 {
    private static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    try {
                        System.out.println("thread获取到了锁");
                        //如果sleep释放锁的话,会在thread获取到了锁和thread释放了锁之间打印
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
                System.out.println("thread释放了锁");
            }
        });
        thread.start();
        //让thread 先获取到锁
        Thread.sleep(1000);
        synchronized (lock) {
            System.out.println("主线程获取到了锁");
        }
    }
}

在这里插入图片描述

可以看到线程在sleep()的时候,线程是不会释放锁的。再来看看wait()方法👇:

在这里插入图片描述

在这里插入图片描述

线程使用wait()的时候它就会释放掉锁。

1.wait()和sleep()都是让线程进行休眠的

2.wait()和sleep()方法都有可能在执行的过程接收到线程终止的通知

3.wait()必须和synchronzied一起使用,而sleep()不用。

4.wait()会释放锁,而sleep()不会释放锁。

5.wait()时Object的方法,而sleep()时Thread的方法。

6.默认情况下,wait()不传任何的参数的情况下,wait()会进入waiting的状态,如果传递了参数,wait()会进入time_waiting的状态。而sleep()进入的是time_waiting的状态。

sleep(0) 和wait(0)的区别:

1.sleep(0)表示0毫秒之后继续执行,而wait(0)表示线程会一直休眠下去wait(0)和wait()是一样的,wait()的源码就是调用了wait(0)方法。

2.sleep(0)表示重新出发一次CPU的竞争。

为什么wait()会释放锁,而sleep()不会释放锁?

sleep()需要传递一个最大的等待时间,也就是说sleep()是可控的,而wait()是不可以传递参数的,从设计的层面来说,如果让wait()一直持有所得话,那么线程就可能一直阻塞。

为什么wait()是Object的方法,而sleep()是线程的方法?

wait()需要操作锁,而锁是属于对象级别的,所有的锁都是放在对象头中的,它不是线程级别的,一个线程可以有多把的锁,为了灵活,就将wait()放在Object中了。

LockSupport park()/unpark()

使用LockSupport可以解决wait()/notify()随机唤醒的问题。


package ThreadDeom;
import java.util.concurrent.locks.LockSupport;

public class ThreadDemo30 {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //让线程休眠
                LockSupport.park();
                System.out.println("unPark()了thread1");
            }
        });
        Thread thread2 = new Thread(() -> {
            LockSupport.park();
            System.out.println("unPark()了thread2");
        });

        Thread thread3 = new Thread() {
            @Override
            public void run() {
                LockSupport.park();
                System.out.println("unPark()了thread3");
            }
        };

        thread1.start();
        thread2.start();
        thread3.start();

        LockSupport.unpark(thread1);
        LockSupport.unpark(thread2);

    }
}

在这里插入图片描述

总结

本篇文章就到这里了,希望可以帮助到你,也希望您能够多多关注编程网的更多内容!

--结束END--

本文标题: 彻底搞懂Java多线程(二)

本文链接: https://lsjlt.com/news/129738.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 彻底搞懂Java多线程(二)
    目录Java中的锁1.synchronized锁(jvm层的解决方案,也叫监视器锁)2.手动锁Locksynchronized锁synchronized使用场景1.使用synchro...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(一)
    目录Java多线程线程的创建线程常用方法线程的终止1.自定义实现线程的终止2.使用Thread的interrupted来中断3.Thraed.interrupted()方法和Thre...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(三)
    目录Java线程池线程池的优点线程池的6种创建方式创建单个线程池的作用是什么?线程池的第七种创建方式ThreadPoolExecutor的执行方式ThreadPoolExecutor...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(四)
    目录SimpleDateFormat非线程安全问题ThreadLocalThreadLocal的原理ThreadLocal常用方法ThreadLocal的初始化Inheritable...
    99+
    2024-04-02
  • 彻底搞懂Java多线程(五)
    目录单例模式与多线程立即加载/饿汉模式延时加载/懒汉模式饿汉/懒汉对比阻塞队列的实现常见的锁策略乐观锁CASCAS在java中的应用CAS 的ABA问题ABA 问题的解决悲观锁独占锁...
    99+
    2024-04-02
  • Java基础:彻底搞懂java多线程
    目录进程与线程使用多线程的优势线程的状态创建线程线程中断总结进程与线程 进程 进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可...
    99+
    2024-04-02
  • 一文彻底搞懂java多线程和线程池
    目录 什么是线程 一. Java实现线程的三种方式1.1、继承Thread类1.2、实现Runnable接口,并覆写run方法二. Callable接口...
    99+
    2024-04-02
  • 如何彻底搞懂jdk8线程池
    这篇文章将为大家详细讲解有关如何彻底搞懂jdk8线程池,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。顶层设计,定义执行接口Interface Executor(){ &n...
    99+
    2023-06-25
  • 一篇文章彻底搞懂jdk8线程池
    这可能是最简短的线程池分析文章了。 顶层设计,定义执行接口 Interface Executor(){ void execute(Runnable command); ...
    99+
    2024-04-02
  • 彻底搞懂 javascript的Promise
    目录一、为什么要引入PromisePromise解决了什么问题?Promise有哪些具体的使用场景?二、手写Prromise身上的方法手写Promise.all手写Promise.r...
    99+
    2024-04-02
  • 彻底搞懂 Python 编码
    因为中文的特殊编码,导致 Python2 和 Python3 使用过程中的各种编码问题,如果不清楚其中的关联关系,那么这就一直是个大坑,不是懵逼就还是懵逼,所以就目前碰到的情况彻底梳理下 Python2 和 Python3 中编码的关系和区...
    99+
    2023-01-31
    Python
  • 彻底搞懂java并发ThreadPoolExecutor使用
    目录前言正文一. 线程池的简单原理二. 线程池的创建三. 线程池执行任务1. 执行无返回值任务2. 执行有返回值任务3. 执行有返回值任务时抛出错误4. ThreadPoolExec...
    99+
    2023-02-28
    java并发ThreadPoolExecutor java 并发
  • 一篇搞懂Java多线程
    Java多线程是指在一个程序中可以同时执行多个线程,每个线程执行不同的任务。多线程可以提高程序的并发性和效率。Java多线程的实现有...
    99+
    2023-09-14
    Java
  • 一文彻底搞懂Kotlin中的协程
    产生背景 为了解决异步线程产生的回调地狱 //传统回调方式 api.login(phone,psd).enquene(new Callback<User>(){ ...
    99+
    2024-04-02
  • 一文彻底搞懂IO底层原理
    目录一、混乱的 IO 概念二、用户空间和内核空间三、IO模型3.1、BIO(Blocking IO)3.2、“C10K”问题3.3、NIO非阻塞模型3.4、IO多路复用模型3.4.1...
    99+
    2024-04-02
  • 彻底搞懂 PHP 运算符 ?: 和 ??
    文章目录 快速掌握: 短三元运算符 NULL 合并运算符 附上官方文档查阅方式 快速掌握 : 短三元运算符 : 称之为短三元运算符,它是我们熟悉的三元运算符(也叫做条件运算符)的一种特殊写法,也就是省略了三元运算符中间的部分...
    99+
    2023-08-30
    php
  • 一文带你彻底搞懂Vuex
    大家可以思考一下,组件之间的传值有哪些?有父子通讯,兄弟组件通讯......但是传参对于多层嵌套就显得非常繁琐,代码维护也会非常麻烦。因此vuex就是把组件共享状态抽取出来以一个全局单例模式管理,把共享的数据函数放进vuex中,任何组件都可...
    99+
    2022-11-22
    Vue vue3 vue.js VueX
  • 彻底搞懂MySQL存储过程和函数
    目录1.0  创建存储过程和函数1. 创建存储过程2. 创建存储函数2|0变量1. 定义变量2. 变量赋值3|0定义条件和处理程序1. 定义条件2. 定义处理程序4|0光标...
    99+
    2024-04-02
  • 一文彻底搞懂PHP进程信号处理
    本篇文章给大家带来了关于PHP的相关知识,其中主要详细介绍了PHP 进程信号处理,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。背景前两周老大给我安排了一个任务,写一个监听信号的包。因为我司的项目是运行在容器里边的,每次上线,需要重新打...
    99+
    2023-05-14
    进程 PHP
  • Java基础之让你彻底搞懂代理模式
    目录一、代理模式二、静态代理三、动态代理四、总结一、代理模式 什么是代理模式? 先来生活常用例子:你想买票,你没必要去车站买;而是可以去一个代售点,代售点代理车站卖票,这就是一个简单...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作