Java 多線程:并發(fā)編程分步指南

多線程是 Java 中一個(gè)強(qiáng)大的概念,它允許我們?cè)趩蝹€(gè)進(jìn)程中同時(shí)運(yùn)行多個(gè)線程。對(duì)于開(kāi)發(fā)響應(yīng)迅速且高效的應(yīng)用程序來(lái)說(shuō),這一點(diǎn)至關(guān)重要,尤其是在當(dāng)今的多核處理器環(huán)境中。在這份全面的指南中,我們將深入探討多線程,涵蓋理論和實(shí)際實(shí)現(xiàn),使我們精通 Java 編程的這一重要方面。

什么是多線程?

多線程是一種編程概念,允許單個(gè)進(jìn)程同時(shí)執(zhí)行多個(gè)線程。線程是進(jìn)程中的輕量級(jí)子進(jìn)程,它們共享相同的內(nèi)存空間,但可以獨(dú)立運(yùn)行。每個(gè)線程代表一個(gè)單獨(dú)的控制流,使得在單個(gè)程序中同時(shí)執(zhí)行多個(gè)任務(wù)成為可能。

重點(diǎn):

  • 線程是進(jìn)程的較小單元,共享相同的內(nèi)存空間。
  • 線程可以被視為獨(dú)立的、并行的執(zhí)行路徑。
  • 多線程能夠高效利用多核處理器。

為什么使用多線程?

多線程具有多個(gè)優(yōu)勢(shì),使其成為軟件開(kāi)發(fā)中的一個(gè)有價(jià)值的工具:

  • 提高響應(yīng)性:多線程允許應(yīng)用程序在執(zhí)行資源密集型任務(wù)時(shí)仍能對(duì)用戶輸入保持響應(yīng)。例如,一個(gè)文本編輯器可以在后臺(tái)執(zhí)行拼寫(xiě)檢查的同時(shí)繼續(xù)響應(yīng)用戶操作。
  • 增強(qiáng)性能:多線程程序可以利用多核處理器,從而帶來(lái)更好的性能。任務(wù)可以在多個(gè)線程之間分配,加速計(jì)算。
  • 資源共享:線程可以在同一進(jìn)程內(nèi)共享數(shù)據(jù)和資源,這可以導(dǎo)致更高效的內(nèi)存使用。在內(nèi)存密集型應(yīng)用程序中,這一點(diǎn)可能至關(guān)重要。
  • 并發(fā):多線程實(shí)現(xiàn)任務(wù)的并發(fā)執(zhí)行,使得同時(shí)管理多個(gè)任務(wù)更加容易。例如,一個(gè) Web 服務(wù)器可以使用線程同時(shí)處理多個(gè)客戶端請(qǐng)求。

術(shù)語(yǔ)和概念

為了理解多線程,掌握以下關(guān)鍵概念至關(guān)重要:

  • 線程:線程是進(jìn)程內(nèi)最小的執(zhí)行單元。單個(gè)進(jìn)程中可以存在多個(gè)線程,并且它們共享相同的內(nèi)存空間。
  • 進(jìn)程:進(jìn)程是在其內(nèi)存空間中運(yùn)行的獨(dú)立程序。它可以由一個(gè)或多個(gè)線程組成。
  • 并發(fā):并發(fā)是指在重疊的時(shí)間間隔內(nèi)執(zhí)行多個(gè)線程。它允許任務(wù)看起來(lái)像是同時(shí)執(zhí)行。
  • 并行:并行涉及多個(gè)線程或進(jìn)程的實(shí)際同時(shí)執(zhí)行,通常在多核處理器上。它實(shí)現(xiàn)真正的同時(shí)執(zhí)行。
  • 競(jìng)爭(zhēng)條件:當(dāng)兩個(gè)或多個(gè)線程同時(shí)訪問(wèn)共享數(shù)據(jù)時(shí),就會(huì)發(fā)生競(jìng)爭(zhēng)條件,最終結(jié)果取決于執(zhí)行的時(shí)間和順序。它可能導(dǎo)致不可預(yù)測(cè)的行為和錯(cuò)誤。
  • 同步:同步是一種用于協(xié)調(diào)和控制對(duì)共享資源的訪問(wèn)的機(jī)制。它通過(guò)只允許一個(gè)線程在同一時(shí)間訪問(wèn)資源來(lái)防止競(jìng)爭(zhēng)條件。
  • 死鎖:死鎖是一種兩個(gè)或多個(gè)線程因?yàn)楸舜说却龑?duì)方釋放資源而無(wú)法繼續(xù)執(zhí)行的情況。它可能導(dǎo)致系統(tǒng)凍結(jié)。

在 Java 中創(chuàng)建線程

  • 擴(kuò)展Thread
  • 實(shí)現(xiàn)Runnable接口

在 Java 中,我們可以通過(guò)兩種主要方式創(chuàng)建線程:通過(guò)擴(kuò)展Thread類或?qū)崿F(xiàn)Runnable接口。這兩種方法都允許我們定義在新線程中運(yùn)行的代碼。

擴(kuò)展Thread類:

要通過(guò)擴(kuò)展Thread類創(chuàng)建線程,我們需要?jiǎng)?chuàng)建一個(gè)新類,該類繼承自Thread并覆蓋run()方法。run()方法包含當(dāng)線程啟動(dòng)時(shí)將執(zhí)行的代碼。以下是一個(gè)例子:

class MyThread extends Thread {
    public void run() {
        // 新線程中要執(zhí)行的代碼
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getId() + ": Count " + i);
        }
    }
}
public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start(); // 啟動(dòng)第一個(gè)線程
        thread2.start(); // 啟動(dòng)第二個(gè)線程
    }
}

在這個(gè)例子中,我們通過(guò)擴(kuò)展Thread類創(chuàng)建了MyThread類。run()方法包含了新線程中要執(zhí)行的代碼。我們創(chuàng)建了兩個(gè)MyThread的實(shí)例,并使用start()方法啟動(dòng)它們。

實(shí)現(xiàn)Runnable接口:

另一種通常更靈活的創(chuàng)建線程的方法是實(shí)現(xiàn)Runnable接口。這種方法允許我們將線程的行為與其結(jié)構(gòu)分離,使得更容易重用和擴(kuò)展。以下是一個(gè)例子:

class MyRunnable implements Runnable {
    public void run() {
        // 新線程中要執(zhí)行的代碼
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getId() + ": Count " + i);
        }
    }
}
public class ThreadExample {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);
        thread1.start(); // 啟動(dòng)第一個(gè)線程
        thread2.start(); // 啟動(dòng)第二個(gè)線程
    }
}

在這個(gè)例子中,我們創(chuàng)建了一個(gè)實(shí)現(xiàn)Runnable接口的MyRunnable類。run()方法包含了新線程中要執(zhí)行的代碼。我們創(chuàng)建了兩個(gè)Thread實(shí)例,將MyRunnable實(shí)例作為構(gòu)造函數(shù)參數(shù)傳遞。然后,我們啟動(dòng)這兩個(gè)線程。

線程生命周期:

Java 中的線程在其生命周期中經(jīng)歷各種狀態(tài):

  • 新建:當(dāng)一個(gè)線程被創(chuàng)建但尚未啟動(dòng)時(shí)。
  • 可運(yùn)行:線程已準(zhǔn)備好運(yùn)行,正在等待輪到它執(zhí)行。
  • 運(yùn)行:線程正在積極執(zhí)行其代碼。
  • 阻塞/等待:線程暫時(shí)不活動(dòng),通常是由于等待資源或事件。
  • 終止:線程已完成執(zhí)行并已終止。

理解線程生命周期對(duì)于在多線程應(yīng)用程序中進(jìn)行正確的線程管理和同步至關(guān)重要。

使用多個(gè)線程

在使用多個(gè)線程時(shí),我們需要注意各種挑戰(zhàn)和概念,包括線程干擾、死鎖、線程優(yōu)先級(jí)和線程組。

線程干擾:

當(dāng)多個(gè)線程同時(shí)訪問(wèn)共享數(shù)據(jù)時(shí),就會(huì)發(fā)生線程干擾,導(dǎo)致意外和不正確的結(jié)果。為了避免線程干擾,我們可以使用同步機(jī)制,如synchronized塊或方法,以確保一次只有一個(gè)線程訪問(wèn)共享數(shù)據(jù)。這里有一個(gè)簡(jiǎn)單的例子說(shuō)明線程干擾:

class Counter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public synchronized int getCount() {
        return count;
    }
}
public class ThreadInterferenceExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("Final Count: " + counter.getCount());
    }
}

在這個(gè)例子中,兩個(gè)線程(thread1thread2)同時(shí)增加一個(gè)共享計(jì)數(shù)器。為了防止干擾,我們使用同步方法來(lái)增加和獲取計(jì)數(shù)器的值。

死鎖和解決方案:

當(dāng)兩個(gè)或多個(gè)線程因?yàn)楸舜说却龑?duì)方釋放資源而無(wú)法繼續(xù)執(zhí)行時(shí),就會(huì)發(fā)生死鎖。死鎖可能很難診斷和修復(fù)。防止死鎖的策略包括使用正確的鎖定順序、超時(shí)和死鎖檢測(cè)算法。這里是一個(gè)潛在死鎖情況的高級(jí)示例:

class Resource {
    public synchronized void method1(Resource other) {
        // 做一些事情
        other.method2(this);
        // 做一些事情
    }
    public synchronized void method2(Resource other) {
        // 做一些事情
        other.method1(this);
        // 做一些事情
    }
}
public class DeadlockExample {
    public static void main(String[] args) {
        Resource resource1 = new Resource();
        Resource resource2 = new Resource();
        Thread thread1 = new Thread(() -> resource1.method1(resource2));
        Thread thread2 = new Thread(() -> resource2.method1(resource1));
        thread1.start();
        thread2.start();
    }
}

在這個(gè)例子中,thread1調(diào)用resource1method1,而thread2調(diào)用resource2method1。兩個(gè)方法隨后都嘗試獲取另一個(gè)資源的鎖,導(dǎo)致潛在的死鎖情況。

線程優(yōu)先級(jí)和組:

Java 允許我們?cè)O(shè)置線程優(yōu)先級(jí),以影響 Java 虛擬機(jī)(JVM)的線程調(diào)度程序執(zhí)行線程的順序。具有較高優(yōu)先級(jí)的線程會(huì)被優(yōu)先考慮,盡管謹(jǐn)慎使用線程優(yōu)先級(jí)很重要,因?yàn)樗鼈冊(cè)诓煌?JVM 實(shí)現(xiàn)中可能表現(xiàn)不一致。此外,我們可以將線程分組以進(jìn)行更好的管理和控制。

Thread thread1 = new Thread(() -> {
    // 線程 1 的邏輯
});
Thread thread2 = new Thread(() -> {
    // 線程 2 的邏輯
});
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.MIN_PRIORITY);
ThreadGroup group = new ThreadGroup("MyThreadGroup");
Thread thread3 = new Thread(group, () -> {
    // 線程 3 的邏輯
});

在這個(gè)例子中,我們?yōu)?code>thread1和thread2設(shè)置線程優(yōu)先級(jí),并為thread3創(chuàng)建一個(gè)名為“MyThreadGroup”的線程組。線程優(yōu)先級(jí)范圍從Thread.MIN_PRIORITY(1)到Thread.MAX_PRIORITY(10)。

理解并有效地管理線程干擾、死鎖、線程優(yōu)先級(jí)和線程組在 Java 中使用多個(gè)線程時(shí)至關(guān)重要。

Java 的并發(fā)實(shí)用工具

Java 提供了一組強(qiáng)大的并發(fā)實(shí)用工具,簡(jiǎn)化了多線程應(yīng)用程序的開(kāi)發(fā)。Java 并發(fā)實(shí)用工具的三個(gè)基本組件是執(zhí)行器框架、線程池以及CallableFuture。

執(zhí)行器框架:

執(zhí)行器框架是一個(gè)用于在多線程環(huán)境中異步管理任務(wù)執(zhí)行的抽象層。它將任務(wù)提交與任務(wù)執(zhí)行解耦,使我們能夠?qū)W⒂谛枰瓿傻娜蝿?wù),而不是如何執(zhí)行它。

以下是如何使用執(zhí)行器框架執(zhí)行任務(wù)的示例:

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class ExecutorExample {
    public static void main(String[] args) {
        Executor executor = Executors.newSingleThreadExecutor();
        Runnable task = () -> {
            System.out.println("Task is executing...");
        };
        executor.execute(task);
    }
}

在這個(gè)例子中,我們使用Executors.newSingleThreadExecutor()創(chuàng)建一個(gè)執(zhí)行器,它創(chuàng)建一個(gè)單線程執(zhí)行器。然后,我們提交一個(gè)Runnable任務(wù)以異步執(zhí)行。

線程池:

線程池是一種用于管理和重用固定數(shù)量的線程來(lái)執(zhí)行任務(wù)的機(jī)制。與為每個(gè)任務(wù)創(chuàng)建一個(gè)新線程相比,它們提供了更好的性能,因?yàn)闇p少了線程創(chuàng)建和銷毀的開(kāi)銷。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Runnable task1 = () -> {
            System.out.println("Task 1 is executing...");
        };
        Runnable task2 = () -> {
            System.out.println("Task 2 is executing...");
        };
        executorService.submit(task1);
        executorService.submit(task2);
        executorService.shutdown();
    }
}

在這個(gè)例子中,我們創(chuàng)建一個(gè)固定大小的線程池,有兩個(gè)線程,并提交兩個(gè)任務(wù)執(zhí)行。當(dāng)不再需要時(shí),調(diào)用shutdown方法優(yōu)雅地關(guān)閉線程池。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Runnable task1 = () -> {
            System.out.println("Task 1 is executing...");
        };
        Runnable task2 = () -> {
            System.out.println("Task 2 is executing...");
        };
        executorService.submit(task1);
        executorService.submit(task2);
        executorService.shutdown();
    }
}

Callable接口類似于Runnable,但它可以返回結(jié)果或拋出異常。Future接口表示異步計(jì)算的結(jié)果,并提供方法來(lái)檢索結(jié)果或處理異常。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableAndFutureExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Callable<Integer> task = () -> {
            Thread.sleep(2000);
            return 42;
        };
        Future<Integer> future = executorService.submit(task);
        System.out.println("Waiting for the result...");
        Integer result = future.get();
        System.out.println("Result: " + result);
        executorService.shutdown();
    }
}

這些 Java 并發(fā)實(shí)用工具為我們的應(yīng)用程序提供了一種強(qiáng)大而高效的方式來(lái)管理多線程任務(wù)、線程池和異步計(jì)算。

高級(jí)多線程

在高級(jí)多線程中,我們更深入地探討管理線程、處理同步以及利用 Java 中的高級(jí)并發(fā)特性的復(fù)雜性。

守護(hù)線程:

守護(hù)線程是在 Java 應(yīng)用程序后臺(tái)運(yùn)行的后臺(tái)線程。它們通常用于非關(guān)鍵任務(wù),并且在主程序完成執(zhí)行時(shí)不會(huì)阻止應(yīng)用程序退出。


Thread daemonThread = new Thread(() -> {
    while (true) {
        // 執(zhí)行后臺(tái)任務(wù)
    }
});
daemonThread.setDaemon(true); // 設(shè)置為守護(hù)線程
daemonThread.start();

線程局部變量:

線程局部變量是每個(gè)線程本地的變量。它們?cè)试S我們存儲(chǔ)特定于特定線程的數(shù)據(jù),確保每個(gè)線程都有自己獨(dú)立的變量副本。


import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalExample {
    public static void main(String[] args) {
        ThreadLocal<AtomicInteger> threadLocal = ThreadLocal.withInitial(AtomicInteger::new);
        Runnable task = () -> {
            AtomicInteger value = threadLocal.get();
            value.incrementAndGet();
            System.out.println("Thread " + Thread.currentThread().getName() + " value: " + value);
            threadLocal.remove();
        };
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        thread1.start();
        thread2.start();
    }
}

線程狀態(tài)(等待、定時(shí)等待、阻塞):

線程可以處于不同的狀態(tài),包括等待、定時(shí)等待和阻塞。這些狀態(tài)代表線程正在等待資源或條件改變的各種情況。


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

public class ThreadStateExample {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Runnable waitingTask = () -> {
            lock.lock();
            try {
                System.out.println("Thread waiting...");
                condition.await();
                System.out.println("Thread resumed.");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                lock.unlock();
            }
        };
        Runnable signalingTask = () -> {
            lock.lock();
            try {
                System.out.println("Signaling thread...");
                condition.signal();
            } finally {
                lock.unlock();
            }
        };
        Thread waitingThread = new Thread(waitingTask);
        Thread signalingThread = new Thread(signalingTask);
        waitingThread.start();
        signalingThread.start();
    }
}

線程間通信:

線程間通信允許線程協(xié)調(diào)并交換信息。這包括使用wait、notifynotifyAll方法來(lái)同步線程。


class SharedResource {
    private boolean flag = false;

    public synchronized void waitForSignal() throws InterruptedException {
        while (!flag) {
            wait();
        }
    }

    public synchronized void setSignal() {
        flag = true;
        notifyAll();
    }
}

public class ThreadCommunicationExample {
    public static void main(String[] args) {
        SharedResource sharedResource = new SharedResource();
        Thread waitingThread = new Thread(() -> {
            try {
                sharedResource.waitForSignal();
                System.out.println("Waiting thread received signal.");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        Thread signalingThread = new Thread(() -> {
            sharedResource.setSignal();
            System.out.println("Signaling thread sent signal.");
        });
        waitingThread.start();
        signalingThread.start();
    }
}

并發(fā)集合:

并發(fā)集合是線程安全的數(shù)據(jù)結(jié)構(gòu),允許多個(gè)線程同時(shí)訪問(wèn)和修改它們,而不會(huì)導(dǎo)致數(shù)據(jù)損壞或同步問(wèn)題。一些例子包括ConcurrentHashMap、ConcurrentLinkedQueueCopyOnWriteArrayList


import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;

public class ConcurrentCollectionsExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.put("key1", 1);
        concurrentHashMap.put("key2", 2);
        System.out.println("ConcurrentHashMap: " + concurrentHashMap);

        ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<>();
        concurrentLinkedQueue.add("item1");
        concurrentLinkedQueue.add("item2");
        System.out.println("ConcurrentLinkedQueue: " + concurrentLinkedQueue);

        CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
        copyOnWriteArrayList.add("element1");
        copyOnWriteArrayList.add("element2");
        System.out.println("CopyOnWriteArrayList: " + copyOnWriteArrayList);
    }
}

線程安全和最佳實(shí)踐:

在多線程應(yīng)用程序中確保線程安全至關(guān)重要。本節(jié)涵蓋最佳實(shí)踐,如使用不可變對(duì)象、原子類以及避免字符串駐留,以編寫(xiě)健壯且線程安全的代碼。


import java.util.concurrent.atomic.AtomicInteger;

class ImmutableObject {
    private final int value;

    public ImmutableObject(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

public class ThreadSafetyBestPracticesExample {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        ImmutableObject immutableObject = new ImmutableObject(10);

        Runnable incrementTask = () -> {
            atomicInteger.incrementAndGet();
            System.out.println("Atomic integer value: " + atomicInteger.get());
        };

        Runnable readImmutableTask = () -> {
            System.out.println("Immutable object value: " + immutableObject.getValue());
        };

        Thread thread1 = new Thread(incrementTask);
        Thread thread2 = new Thread(readImmutableTask);
        thread1.start();
        thread2.start();
    }
}

Java 中的并行:

并行涉及并發(fā)執(zhí)行任務(wù)以提高性能。Java 在 Java 8 中提供了并行流和CompletableFuture用于異步編程等功能。


import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class JavaParallelismExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        List<Integer> squaredNumbers = numbers.parallelStream()
               .map(n -> n * n)
               .collect(Collectors.toList());
        System.out.println("Squared numbers using parallel stream: " + squaredNumbers);

        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("Asynchronous task using CompletableFuture.");
        });
        future.join();
    }
}

真實(shí)世界的多線程:

本節(jié)深入探討多線程的實(shí)際應(yīng)用,包括實(shí)現(xiàn) Web 服務(wù)器以及在游戲開(kāi)發(fā)中使用多線程。


// Web 服務(wù)器示例(簡(jiǎn)化版)
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleWebServerExample {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("Web server started on port 8080.");
            while (true) {
                Socket clientSocket = serverSocket.accept();
                handleRequest(clientSocket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleRequest(Socket clientSocket) {
        try {
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
            out.println("HTTP/1.1 200 OK");
            out.println("Content-Type: text/html");
            out.println();
            out.println("<html><body>Hello from simple web server!</body></html>");
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

這些高級(jí)多線程主題為開(kāi)發(fā)人員提供了構(gòu)建高效、并發(fā)和可擴(kuò)展的 Java 應(yīng)用程序所需的知識(shí)和技能。每個(gè)主題都附有示例以說(shuō)明概念和技術(shù)。

若你想提升Java技能,可關(guān)注我們的Java培訓(xùn)課程。