> Java > java지도 시간 > 멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술

멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술

WBOY
풀어 주다: 2022-04-26 18:05:36
앞으로
2867명이 탐색했습니다.

이 글은 핵심 클래스 라이브러리에서 주로 멀티스레딩과 스레드 동기화와 관련된 문제를 소개하는 java에 대한 관련 지식을 함께 살펴보겠습니다.

멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술

추천 학습: "java 비디오 튜토리얼"

1 Multi-threading

1.1 Process

  • Process: 실행 중인 프로그램입니다
    • 자원 할당 및 독립된 단위 of call
    • 각 프로세스에는 고유한 메모리 공간과 시스템 리소스가 있습니다
  • 프로세스의 세 가지 특징
    • 독립성: 프로세스는 서로 독립적이며 자체 메모리 영역을 갖습니다.
    • 동적: 프로세스는 메모리, CPU 및 네트워크와 같은 리소스를 동적으로 점유하는 실행 프로그램입니다.
    • 동시성: CPU 시간 공유 폴링 전환. 전환 속도가 매우 빠르기 때문에 동시에 실행되는 느낌을 주기 때문에 각 프로세스를 차례로 제공합니다. 이것이 동시성(동시성: 동시에 여러 실행)입니다.
Thread

: 프로세스의 단일 순차 제어 흐름, 실행 경로입니다싱글 스레드: 프로세스에 실행 경로가 하나만 있습니다

    멀티 스레딩: 프로세스에 여러 개의 실행 경로가 있습니다
    • 1.3 멀티스레딩 구현
  • 1.3.1 방법 1: Tread 클래스 상속

프로세스 :

    1. MyTread 클래스를 정의하여 Tread 클래스를 상속합니다. 2. 에서 MyTread 클래스
  • 3의 run() 메서드를 재정의합니다. MyTread 클래스4의 개체를 만듭니다. void start()

    • run() 메소드를 작성하는 것이 왜 중요한가요? run()方法
    • 3、创建MyTread类的对象
    • 4、启动线程:void start()
  • 为什么要重写run()方法?

    • 因为run()是用来封装被线程执行的代码
  • run()方法和start()方法的区别?

    • run():封装线程执行的代码,直接调用,相当于普通方法的的调用
    • start():启动线程,然后由JVM调用此线程中的run()方法
  • 范例

  • MyTread类:

package test;//1、定义一类MyTread继承Tread类public class MyThread extends Thread{
    2、在MyTread类中重写run()方法
    @Override
    public void run() {
        for(int i=0;i
로그인 후 복사
  • 测试类
package test;public class Demo {
    public static void main(String[] args) {
        //3、创建MyTread类的对象
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //4、启动线程:void start():启动线程,由Java虚拟机调用此线程的run()方法
        my1.start();
        my2.start();
    }}
로그인 후 복사

1.3.2 方式2:实现Runnable接口

  • 流程
    • 1、定义一个MyRunnable类实现Runnable接口
    • 2、在MyRunnable类中重写run()
    • run()은 스레드에 의해 실행되는 코드를 캡슐화하는 데 사용되기 때문입니다.
    • run() 메서드와 start() 메서드의 차이점은 무엇인가요?
  • run(): 스레드에 의해 실행되는 코드를 캡슐화하고 이를 직접 호출합니다. 이는 일반 메서드를 호출하는 것과 같습니다.
    • start(): 스레드를 시작한 다음 JVM이 여기에서 run() 메서드를 호출합니다.

MyTread 클래스:

package test;public class Demo {
    public static void main(String[] args) {
        //3、创建MyRunnable类的对象
        MyRunnable mr = new MyRunnable();

        //4、创建Tread类的对象,把MyRunnable对象作为构造方法的参数//        Thread t1 = new Thread(mr);//        Thread t2 = new Thread(mr);
        //Thread(Runnable target,String name)
        Thread t1 = new Thread(mr,"高铁");
        Thread t2 = new Thread(mr,"飞机");

        //5、启动线程
        t1.start();
        t2.start();
    }}
로그인 후 복사

테스트 클래스
  • package test;public class MyThread extends Thread{
        //构造方法添加线程名称
        public MyThread(){}
        public MyThread(String name) {
            super(name);
        }
        
        @Override
        public void run() {
            for(int i=0;i<strong></strong>1.3.2 방법 2: 실행 가능한 인터페이스 구현
    로그인 후 복사
Process 2. MyRunnable 클래스의 run() 메서드를 다시 작성합니다. 3. MyRunnable 클래스의 개체를 생성합니다. 여러 동일한 프로그램의 코드가 동일한 리소스를 처리하는 상황에 적합합니다. 스레드와 프로그램의 코드와 데이터를 효과적으로 분리하고 객체 지향 설계 이론을 더 잘 반영합니다1.3.3 방법 3: Callable 인터페이스 구현 Thread 클래스에서 스레드 이름을 설정하고 가져오는 메서드
: 1. 정의합니다. MyRunnable 클래스 Runnable 인터페이스 구현
4. MyRunnable 객체를 생성자의 매개변수로 사용합니다. 5. 스레드 시작
이점: Java 단일 상속의 제한을 피하세요
package test;public class Demo {
    public static void main(String[] args) {
        /*        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //2,void setName(Stringname) 	将此线程的名称更改为等于参数name
        my1.setName("高铁");
        my2.setName("飞机");*/
        
        //3,通过构造方法设置线程名称
        //需要自己定义的类中提供此带参构造方法,并通过super访问父类带参构造方法
        /*MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机"); 

        my1.start();
        my2.start();*/
        
        //4,public static Thread currentThread() 	返回对当前正在执行的线程对象的引用(可以返回main()方法中线程)
        System.out.println(Tread.currentThread().getName()); //main
    }}
로그인 후 복사
로그인 후 복사
1.4 스레드 이름 설정 및 가져오기
🎜🎜Method name🎜🎜Description🎜🎜🎜🎜🎜🎜void setName(Stringname)🎜🎜이 스레드의 이름을 매개변수 이름과 동일하게 변경하세요. 🎜🎜🎜🎜String getName()🎜🎜이 스레드의 이름을 반환합니다🎜🎜 🎜 🎜public Thread(문자열 이름)🎜🎜스레드 이름은 생성자 메서드를 통해서도 설정할 수 있습니다🎜🎜🎜🎜public static Thread currentThread()🎜 🎜현재 실행 중인 스레드 개체에 대한 참조를 반환합니다(main() 메서드에서 스레드를 반환할 수 있음)🎜 🎜🎜🎜public static void sleep(long time)🎜🎜실행을 계속하기 전에 현재 스레드를 휴면 상태로 유지하는 데 몇 밀리초가 걸릴지🎜 🎜🎜🎜
  • MyThread类
package test;public class MyThread extends Thread{
    //构造方法添加线程名称
    public MyThread(){}
    public MyThread(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        for(int i=0;i
로그인 후 복사
  • 测试类
package test;public class Demo {
    public static void main(String[] args) {
        /*        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //2,void setName(Stringname) 	将此线程的名称更改为等于参数name
        my1.setName("高铁");
        my2.setName("飞机");*/
        
        //3,通过构造方法设置线程名称
        //需要自己定义的类中提供此带参构造方法,并通过super访问父类带参构造方法
        /*MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机"); 

        my1.start();
        my2.start();*/
        
        //4,public static Thread currentThread() 	返回对当前正在执行的线程对象的引用(可以返回main()方法中线程)
        System.out.println(Tread.currentThread().getName()); //main
    }}
로그인 후 복사
로그인 후 복사

1.5 线程调度

  • 线程有两种调度模型

    • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
    • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
  • Java使用的是抢占式调度模型

  • 假如计算机只有一个CPU, 那么CPU在某一个时刻只能执行条指令, 线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

  • Thread类中设置和获取线程优先级的方法

方法名 说明
public final int getPriority() [praɪˈɔːrəti] 返回此线程的优先级
public final void setPriority(int newPriority) 更改此线程的优先级
  • 线程默认优先级是5;线程优先级范围是:1-10
  • 线程优先级高仅仅表示线程获取的CPU时间的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
package test;public class Demo {
    public static void main(String[] args) {

        ThreadPriority tp1 = new ThreadPriority();
        ThreadPriority tp2 = new ThreadPriority();
        ThreadPriority tp3 = new ThreadPriority();


        tp1.setName("高铁");
        tp2.setName("飞机");
        tp3.setName("汽车");

        //1,public final int getPriority() [praɪˈɔːrəti] 	返回此线程的优先级//        System.out.println(tp1.getPriority()); //5//        System.out.println(tp2.getPriority()); //5//        System.out.println(tp3.getPriority()); //5

        //2,public final void setPriority(int newPriority) 	更改此线程的优先级
        System.out.println(Thread.MAX_PRIORITY); //10
        System.out.println(Thread.MIN_PRIORITY); //1
        System.out.println(Thread.NORM_PRIORITY); //5

        //设置正确优先级
        tp1.setPriority(5);
        tp2.setPriority(10);
        tp3.setPriority(1);

        tp1.start();
        tp2.start();
        tp3.start();
    }}
로그인 후 복사

1.6 线程控制

方法名 说明
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join() 等待这个线程死亡
void setDaemon(boolean on) [ˈdiːmən] 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机很快将退出 (并不是立刻退出)

案例:sleep()方法

  • 线程类
package test;public class ThreadSleep extends Thread{
    @Override
    public void run() {
        for(int i=0;i
로그인 후 복사
  • 测试类
package test;public class Demo {
    public static void main(String[] args) {

        ThreadSleep ts1 = new ThreadSleep();
        ThreadSleep ts2 = new ThreadSleep();
        ThreadSleep ts3 = new ThreadSleep();


        ts1.setName("曹操");
        ts2.setName("刘备");
        ts3.setName("孙权");


        ts1.start();
        ts2.start();
        ts3.start();//        曹操:0//        孙权:0//        刘备:0//        孙权:1//        曹操:1//        刘备:1//        ...
    }}
로그인 후 복사

案例:join()方法

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

        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj1.setName("康熙");
        tj2.setName("四阿哥");
        tj3.setName("八阿哥");

        tj1.start();
        //2,void join() 	等待这个线程死亡
        try {
            tj1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tj2.start();
        tj3.start();//        康熙:0//        康熙:1//        康熙:2//        四阿哥:0//        四阿哥:1//        八阿哥:0//        八阿哥:1//        八阿哥:2//        四阿哥:2//        ...
    }}
로그인 후 복사

案例:setDaemon()方法

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

        ThreadJoin tj1 = new ThreadJoin();
        ThreadJoin tj2 = new ThreadJoin();
        ThreadJoin tj3 = new ThreadJoin();

        tj2.setName("关羽");
        tj3.setName("张飞");
        //设置主线程为刘备
        Thread.currentThread().setName("刘备");

        //3,void setDaemon(boolean on) 	将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
        tj1.setDaemon(true);
        tj2.setDaemon(true);

        tj1.start();
        tj2.start();

        for(int i=0;i<h3><strong>1.7 线程生命周期</strong></h3><p><img src="https://img.php.cn/upload/article/000/000/067/331836944d1895cfc26297fee263339c-0.png" alt="멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술"></p><h3><strong>1.8 数据安全问题之案例:买票</strong></h3><p><img src="https://img.php.cn/upload/article/000/000/067/331836944d1895cfc26297fee263339c-1.png" alt="멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술"><br><img src="https://img.php.cn/upload/article/000/000/067/331836944d1895cfc26297fee263339c-2.png" alt="멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술"></p>
로그인 후 복사
  • 为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据
  • 如何解决多线程安全问题呢?

  • 基本思想:让程序没有安全问题的环境

  • 怎么实现呢?

    • 把多条语句操作共享 数据的代码给锁起来,让任意时刻只能有一一个线程执行即可
    • Java提供 了同步代码块的方式来解决

1.9 线程同步_同步代码块

  • 锁多条语句操作共享数据,可以使用同步代码块实现
  • 格式
synchronized(任意对象) {
	多条语句操作共享数据的代码}
로그인 후 복사
  • 好处:让多个线程实现先后依次访问共享资源,解决了多线程的数据安全问题

  • 弊端:当线程很多的时候,因为每个线程都会去判断同步上的锁,这是很消耗资源的,无形中降低程序的运行效率

  • sellTicket类

package test;//1,定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets= 100;public class SellTicket implements Runnable{
    private int tickets = 100;
    private Object obj = new Object();

    //2,在ellTicket类中重写run0方法实现卖票, 代码步骤如下
    @Override
    public void run() {
        while(true) {
            //tickes=100
            //t1,t2,t3
            //假设t1抢到CPU执行器
            synchronized (obj){
                //t1进来后把代码锁起来了
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets=99
                }
                //t1出来了,锁就被释放了
            }
        }
    }}
로그인 후 복사
  • 测试类
package test;public class SellTicketDemo {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();

    }}
로그인 후 복사

1.10 线程同步_同步方法

  • 作用:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待
  • 同步方法:就是把synchronized关键字加到方法上;锁对象为:this  
    • 格式:修饰符 synchronized 返回值类型 方法名(方法参数) {}
  • 同步静态方法:就是把synchronized关键字加到静态方法上面;锁对象为:类名.class  
    • 格式:修饰符 static synchronized 返回值类型 方法名(方法参数) {}
package test;public class SellTicket implements Runnable{//1非静态    private int tickets = 100;
    private static int tickets = 100;
    private Object obj = new Object();
    private int x = 0;

    @Override
    public void run() {
        while(true) {
            if(x%2==0) {//1非静态                synchronized (this) {
                synchronized (SellTicket.class) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                        tickets--; //tickets=99
                    }
                }
            } else {//                synchronized (obj) {//                    if (tickets > 0) {//                        try {//                            Thread.sleep(100);//                        } catch (InterruptedException e) {//                            e.printStackTrace();//                        }//                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");//                        tickets--; //tickets=99//                    }//                }
                sellTicket();
            }
            x++;
        }
    }//1非静态//    private synchronized void sellTicket() {//        if (tickets > 0) {//            try {//                Thread.sleep(100);//            } catch (InterruptedException e) {//                e.printStackTrace();//            }//            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");//            tickets--; //tickets=99//        }//    }

    private static synchronized void sellTicket() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
            tickets--; //tickets=99
        }
    }}
로그인 후 복사

1.11 线程安全的类(了解)

源码中方法都被synchronized修饰

StringBuffer

  • 线程安全, 可变的字符序列
  • 从版本JDK 5开始,被StringBuilder替代。通常应该使用StringBuilder类, 因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector

  • 从Java 2平台v1.2开始,该类改进了List接口, 使其成为Java Collections Framework的成员。 与新的集合实现不同,Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector

Hashtable

  • 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值
  • 从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable

Collections类中static <t> List<t> snchronizedList(List<t> list)</t></t></t>:返回由指定列表支持的同步(线程安全)的列表

package test;import java.util.ArrayList;import java.util.Collection;import java.util.Collections;public class Demo {
    public static void main(String[] args)  {
        //static <t> List<t> snchronizedList(List<t> list):返回由指定列表支持的同步(线程安全)的列表
        Collection<string> list = Collections.synchronizedList(new ArrayList<string>());

        /*源码都是返回Synchronized
        public static <t> List<t> synchronizedList(List<t> list) {
            return (list instanceof RandomAccess ?
                    new Collections.SynchronizedRandomAccessList(list) :
                    new Collections.SynchronizedList(list));
        }*/

    }}</t></t></t></string></string></t></t></t>
로그인 후 복사

1.12 Lock锁

  • Lock是接口不能直接实例化,采用实现类ReentrantLock来实例化(JDK5以后)
  • ReentrantLock构造方法:
方法名 说明
ReentrantLock() 创建一个ReentrantLock的实例对象
  • Lock中获得锁和释放锁方法:
方法名 说明
void lock() 获得锁
void unlock() 释放锁
  • 推荐使用try{} finall{}代码块来加锁和释放锁
package test;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class SellTicket implements Runnable{
    private static int tickets = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }finally {
                lock.unlock();
            }
        }
    }}
로그인 후 복사

1.13 线程通讯

  • 线程通信一定是多个线程在操作同一个资源才需要通信
方法名 说明
public void wait() 让当前线程进入到等待状态,此方法必须锁对象调用
public void notify() 唤醒当前锁对象上等待状态的某个线程,此方法必须锁对象调用
public void notifyAll() 唤醒当前锁对象上等待状态的全部线程,此方法必须锁对象调用

1.14 生产者消费者

1.14.1 生产者消费者概述

멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술

  • 为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中
  • Object类的等待和唤醒方法
方法名 说明
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll() 方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

1.14.2 生产者消费者案例

멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술

  • 奶箱类
package test;//1:定义奶箱类public class Box {
    //定义一个成员变量,表示第x瓶奶
    private int milk;
    //定义一个成员变量表示奶箱的状态
    private boolean state = false;

    //提供存储牛奶和获取牛奶的操作
    public  synchronized void put(int milk) {
        //如果有牛奶等待消费
        if(state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没有牛奶,就生产牛奶
        this.milk = milk;
        System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");

        //生产完毕后,修改奶箱状态
        state = true;

        //唤醒其他等待线程
        notifyAll();
    }

    public  synchronized void get() {
        //如果没有牛奶,就等到生产
        if(!state) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果有牛奶,就消费牛奶
        System.out.println("用户拿到第" + this.milk + "瓶奶");

        //消费完毕后,修改奶箱状态
        state = false;

        //唤醒其他等待线程
        notifyAll();
    }}
로그인 후 복사
  • 生产者类
package test;//2:生产者类(Producer):实现Runnable接口public class Producer implements Runnable {
    private Box b;

    public Producer(Box b) {
        this.b = b;
    }

    //重写run()方法,调用存储牛奶的操作
    @Override
    public void run() {
        for (int i = 1; i 
로그인 후 복사
  • 消费者类
package test;//3:消费者类(Customer);实现Runnable接口public class Customer implements Runnable{
    private Box b;

    public Customer(Box b) {
        this.b = b;
    }

    //重写run()方法,调用获取牛奶的操作
    @Override
    public void run() {
        while(true) {
            b.get();
        }
    }}
로그인 후 복사
  • 测试类
package test;public class BoxDemo {
    public static void main(String[] args) {
        //创建奶箱对象,这是共享数据区域
        Box b = new Box();

        //创建生产者对象,把奶箱对象作为构造方法参数传递。因为在这个类中要谓用存储牛奶的操作
        Producer p = new Producer(b);

        //创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
        Customer c  =new Customer(b);

        //创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);

        //启动线程
        t1.start();
        t2.start();//        送奶工将第1瓶奶放入奶箱//                用户拿到第1瓶奶//        送奶工将第2瓶奶放入奶箱//                用户拿到第2瓶奶//        送奶工将第3瓶奶放入奶箱//                用户拿到第3瓶奶//        送奶工将第4瓶奶放入奶箱//                用户拿到第4瓶奶//        送奶工将第5瓶奶放入奶箱//                用户拿到第5瓶奶
    }}
로그인 후 복사

推荐学习:《java视频教程

위 내용은 멀티스레딩 및 스레드 동기화를 달성하는 Java 클래식 기술의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:csdn.net
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿