Cara untuk melaksanakan multi-threading: 1. Warisi kelas Thread, gunakan kelas Thread yang disediakan oleh JDK, dan tulis semula kaedah run kelas Thread 2. Laksanakan antara muka Runnable, iaitu "@; Antara muka formula fungsi FunctionalInterface", yang bermaksud bahawa anda boleh menggunakan kaedah lambda yang disediakan oleh JDK8 untuk membuat tugasan benang; 3. Gunakan kelas dalaman; 4. Gunakan pemasa; 5. Pelaksanaan benang dengan nilai pulangan; 6. Laksanakan berbilang benang berdasarkan benang kolam.
Persekitaran pengendalian tutorial ini: sistem Windows 7, versi Java 8, komputer DELL G3.
Terdapat dua cara utama untuk melaksanakan multi-threading dalam bentuk, satu adalah untuk mewarisi kelas Thread, dan satu lagi adalah untuk melaksanakan antara muka Runnable. Pada asasnya, kaedah pelaksanaan adalah untuk melaksanakan tugas utas, dan kemudian mulakan utas untuk melaksanakan tugas utas (tugas utas di sini sebenarnya kaedah larian). 6 jenis yang dinyatakan di sini sebenarnya adalah beberapa pengubahsuaian berdasarkan dua jenis di atas.
Berikut akan memperkenalkan setiap satu daripada 6 kaedah pelaksanaan ini satu demi satu.
Cara pertama: mewarisi kelas Thread
Semuanya adalah objek, kemudian thread juga objek, dan objek harus Ciri awamnya boleh diekstrak dan dikapsulkan ke dalam kelas, dan berbilang objek boleh digunakan menggunakan kelas Kemudian cara pertama untuk melaksanakan utas ialah mewarisi kelas Thread . Mewarisi kelas Thread ialah cara paling mudah untuk melaksanakan utas hanya menulis semula kaedah run kelas Thread melalui kelas Thread yang disediakan oleh JDK Kemudian apabila utas bermula, kandungan badan kaedah run akan dilaksanakan. Kodnya adalah seperti berikut:
package com.kingh.thread.create; /** * 继承Thread类的方式创建线程 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/13 19:19 */ public class CreateThreadDemo1 extends Thread { public CreateThreadDemo1() { // 设置当前线程的名字 this.setName("MyThread"); } @Override public void run() { // 每隔1s中输出一次当前线程的名字 while (true) { // 输出线程的名字,与主线程名称相区分 printThreadInfo(); try { // 线程休眠一秒 Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } } public static void main(String[] args) throws Exception { // 注意这里,要调用start方法才能启动线程,不能调用run方法 new CreateThreadDemo1().start(); // 演示主线程继续向下执行 while (true) { printThreadInfo(); Thread.sleep(1000); } } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); } }
Hasil larian adalah seperti berikut
当前运行的线程名为: main 当前运行的线程名为: MyThread 当前运行的线程名为: main 当前运行的线程名为: MyThread 当前运行的线程名为: MyThread 当前运行的线程名为: main
Perlu diingatkan di sini bahawa apabila memulakan utas, kaedah run thread kelas tidak dipanggil, tetapi kaedah larian kelas benang dipanggil Kaedah permulaan kelas benang. Jadi bolehkah kita memanggil kaedah larian? Jawapannya ya, kerana kaedah larian adalah kaedah yang diisytiharkan secara umum, jadi kita boleh memanggilnya, tetapi jika kita memanggil kaedah larian, maka kaedah ini akan dipanggil sebagai kaedah biasa dan tidak akan memulakan utas. Ini sebenarnya menggunakan corak kaedah templat dalam corak reka bentuk Kelas Thread berfungsi sebagai templat, dan kaedah larian berubah, jadi ia dilaksanakan dalam subkelas.
Dalam contoh di atas, sebagai tambahan kepada satu utas yang kami buat, sebenarnya terdapat satu. utas utama Juga melaksanakan. Jadi selain kedua-dua utas ini, adakah terdapat apa-apa utas lain yang melaksanakan sebenarnya, contohnya, benang kutipan sampah yang kita tidak dapat lihat juga dilaksanakan secara senyap. Di sini kita tidak mempertimbangkan berapa banyak utas yang sedang dilaksanakan. Kelas Thread ialah objek thread, jadi buat beberapa kelas Thread dan panggil kaedah permulaannya untuk memulakan berbilang thread. Kodnya adalah seperti berikut
package com.kingh.thread.create; /** * 创建多个线程同时执行 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 9:46 */ public class CreateMultiThreadDemo2 extends Thread { public CreateMultiThreadDemo2(String name) { // 设置当前线程的名字 this.setName(name); } @Override public void run() { // 每隔1s中输出一次当前线程的名字 while (true) { // 输出线程的名字,与主线程名称相区分 printThreadInfo(); try { // 线程休眠一秒 Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } } public static void main(String[] args) throws Exception { // 注意这里,要调用start方法才能启动线程,不能调用run方法 new CreateMultiThreadDemo2("MyThread-01").start(); // 创建多个线程实例,同时执行 new CreateMultiThreadDemo2("MyThread-02").start(); // 演示主线程继续向下执行 while (true) { printThreadInfo(); Thread.sleep(1000); } } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); } }
Hasil yang dijalankan adalah seperti berikut
当前运行的线程名为: main 当前运行的线程名为: MyThread-02 当前运行的线程名为: MyThread-01 当前运行的线程名为: main 当前运行的线程名为: MyThread-01 当前运行的线程名为: MyThread-02 当前运行的线程名为: main
untuk melihat, Dengan mencipta berbilang kelas Thread dan memanggil kaedah permulaannya, berbilang utas dimulakan. Setiap utas mempunyai nama sendiri Dalam kod di atas, nama MyThread-01 dan MyThread-02 diberikan kepada utas yang dibuat, dan kemudian nama utas diberikan kepada nama utas dengan memanggil kaedah setName kelas induk dalam kaedah pembina. Jika anda tidak menentukan nama utas, sistem akan menentukan nama utas secara lalai dan peraturan penamaan adalah dalam bentuk Thread-N. Walau bagaimanapun, untuk memudahkan penyelesaian masalah, adalah disyorkan untuk menentukan nama utas yang munasabah semasa membuat utas. Kod berikut tidak menggunakan nama benang
package com.kingh.thread.create; /** * 创建多个线程同时执行,使用系统默认线程名 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 9:46 */ public class CreateMultiThreadDemo3 extends Thread { @Override public void run() { // 每隔1s中输出一次当前线程的名字 while (true) { // 输出线程的名字,与主线程名称相区分 printThreadInfo(); try { // 线程休眠一秒 Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } } public static void main(String[] args) throws Exception { // 注意这里,要调用start方法才能启动线程,不能调用run方法 new CreateMultiThreadDemo3().start(); // 创建多个线程实例,同时执行 new CreateMultiThreadDemo3().start(); // 演示主线程继续向下执行 while (true) { printThreadInfo(); Thread.sleep(1000); } } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); } }
Hasil larian adalah seperti berikut:
当前运行的线程名为: main 当前运行的线程名为: Thread-1 当前运行的线程名为: Thread-0 当前运行的线程名为: main 当前运行的线程名为: Thread-1 当前运行的线程名为: Thread-0
Cara kedua: Melaksanakan antara muka Runnable
Melaksanakan antara muka Runnable juga merupakan cara biasa untuk mencipta urutan Menggunakan antara muka boleh mengurangkan gandingan program kami. Terdapat hanya satu kaedah yang ditakrifkan dalam antara muka Runnable, yang dijalankan. Mari kita lihat kod antara muka Runnable.
package java.lang; @FunctionalInterface public interface Runnable { public abstract void run(); }
Sebenarnya, Runnable ialah tugasan benang Kawalan tugasan benang dan benang diasingkan Ini ialah penyahgandingan yang dinyatakan di atas. Jika kita ingin melaksanakan thread, kita boleh menggunakan kelas Thread Tugasan yang akan dilaksanakan oleh kelas Thread boleh dikendalikan oleh kelas yang melaksanakan antara muka Runnable. Ini adalah intipati Runnable!
Runnable ialah antara muka berfungsi @FunctionalInterface, yang bermaksud anda boleh menggunakan kaedah lambda yang disediakan oleh JDK8 untuk membuat tugasan benang Kod berikut akan menunjukkan kepada pembaca cara menggunakannya.
Langkah-langkah untuk menggunakan Runnable untuk melaksanakan contoh di atas adalah seperti berikut:
线程任务就是线程要做的事情,这里我们让这个线程每隔1s中打印自己的名字
package com.kingh.thread.create; /** * 线程任务 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo4_Task implements Runnable { @Override public void run() { // 每隔1s中输出一次当前线程的名字 while (true) { // 输出线程的名字,与主线程名称相区分 printThreadInfo(); try { // 线程休眠一秒 Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); } }
在这里创建线程,并把任务交给线程处理,然后启动线程。
package com.kingh.thread.create; /** * 创建线程 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo4_Main { public static void main(String[] args) throws Exception { // 实例化线程任务类 CreateThreadDemo4_Task task = new CreateThreadDemo4_Task(); // 创建线程对象,并将线程任务类作为构造方法参数传入 new Thread(task).start(); // 主线程的任务,为了演示多个线程一起执行 while (true) { printThreadInfo(); Thread.sleep(1000); } } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); } }
线程任务和线程的控制分离,那么一个线程任务可以提交给多个线程来执行。这是很有用的,比如车站的售票窗口,每个窗口可以看做是一个线程,他们每个窗口做的事情都是一样的,也就是售票。这样我们程序在模拟现实的时候就可以定义一个售票任务,让多个窗口同时执行这一个任务。那么如果要改动任务执行计划,只要修改线程任务类,所有的线程就都会按照修改后的来执行。相比较继承Thread类的方式来创建线程的方式,实现Runnable接口是更为常用的。
这里就是为了简化内部类的编写,简化了大量的模板代码,显得更加简洁。如果读者看不明白,可以读完内部类方式之后,回过来再看这段代码。
package com.kingh.thread.create; /** * 创建线程with lambda * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo5_Lambda { public static void main(String[] args) throws Exception { // 使用lambda的形式实例化线程任务类 Runnable task = () -> { while (true) { // 输出线程的名字 printThreadInfo(); } }; // 创建线程对象,并将线程任务类作为构造方法参数传入 new Thread(task).start(); // 主线程的任务,为了演示多个线程一起执行 while (true) { printThreadInfo(); Thread.sleep(1000); } } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } }
这并不是一种新的实现线程的方式,只是另外的一种写法。比如有些情况我们的线程就想执行一次,以后就用不到了。那么像上面两种方式(继承Thread类和实现Runnable接口)都还要再定义一个类,显得比较麻烦,我们就可以通过匿名内部类的方式来实现。使用内部类实现依然有两种,分别是继承Thread类和实现Runnable接口。代码如下:
package com.kingh.thread.create; /** * 匿名内部类的方式创建线程 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo6_Anonymous { public static void main(String[] args) { // 基于子类的方式 new Thread() { @Override public void run() { while (true) { printThreadInfo(); } } }.start(); // 基于接口的实现 new Thread(new Runnable() { @Override public void run() { while (true) { printThreadInfo(); } } }).start(); } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } }
可以想象一下,我能不能既基于接口,又基于子类呢?像下面的代码会执行出什么样子呢?
package com.kingh.thread.create; /** * 匿名内部类的方式创建线程 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo7_Anonymous { public static void main(String[] args) { // 基于子类和接口的方式 new Thread(new Runnable() { @Override public void run() { while (true) { printInfo("interface"); } } }) { @Override public void run() { while (true) { printInfo("sub class"); } } }.start(); } /** * 输出当前线程的信息 */ private static void printInfo(String text) { System.out.println(text); try { Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } }
运行结果如下:
sub class sub class
我们可以看到,其实是基于子类的执行了,为什么呢,其实很简单,我们先来看一下为什么不基于子类的时候Runnable的run方法可以执行。这个要从Thread的源码看起,下面是我截取的代码片段。
public Thread(Runnable target) init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; // 注意这里 setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
其实上面的众多代码就是为了表现 this.target = target 那么target是什么呢,是Thread类的成员变量。那么在什么地方用到了target呢?下面是run方法的内容。
@Override public void run() { if (target != null) { target.run(); } }
我们可以看到,如果通过上面的构造方法传入target,那么就会执行target中的run方法。可能有朋友就会问了,我们同时继承Thread类和实现Runnable接口,target不为空,那么为何不执行target的run呢。不要忘记了,我们在子类中已经重写了Thread类的run方法,因此run方法已经不在是我们看到的这样了。那当然也就不回执行target的run方法。
刚才使用匿名内部类,会发现代码还是比较冗余的,lambda可以大大简化代码的编写。用lambda来改写上面的基于接口的形式的代码,如下
// 使用lambda的形式 new Thread(() -> { while (true) { printThreadInfo(); } }).start(); // 对比不使用lambda的形式 new Thread(new Runnable() { @Override public void run() { while (true) { printThreadInfo(); } } }).start();
定时器可以说是一种基于线程的一个工具类,可以定时的来执行某个任务。在应用中经常需要定期执行一些操作,比如要在凌晨的时候汇总一些数据,比如要每隔10分钟抓取一次某个网站上的数据等等,总之计时器无处不在。
在Java中实现定时任务有很多种方式,JDK提供了Timer类来帮助开发者创建定时任务,另外也有很多的第三方框架提供了对定时任务的支持,比如Spring的schedule以及著名的quartz等等。因为Spring和quartz实现都比较重,依赖其他的包,上手稍微有些难度,不在本篇博客的讨论范围之内,这里就看一下JDK所给我们提供的API来实现定时任务。
package com.kingh.thread.create; import java.text.SimpleDateFormat; import java.util.Timer; import java.util.TimerTask; /** * 定时任务 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo9_Timer { private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); public static void main(String[] args) throws Exception { // 创建定时器 Timer timer = new Timer(); // 提交计划任务 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("定时任务执行了..."); } }, format.parse("2017-10-11 22:00:00")); } }
package com.kingh.thread.create; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * 定时任务 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo10_Timer { public static void main(String[] args){ // 创建定时器 Timer timer = new Timer(); // 提交计划任务 timer.schedule(new TimerTask() { @Override public void run() { System.out.println("定时任务执行了..."); } }, new Date(), 1000); } }
关于Spring的定时任务,可以参考 《Spring计划任务》
我们发现上面提到的不管是继承Thread类还是实现Runnable接口,发现有两个问题,第一个是无法抛出更多的异常,第二个是线程执行完毕之后并无法获得线程的返回值。那么下面的这种实现方式就可以完成我们的需求。这种方式的实现就是我们后面要详细介绍的Future模式,只是在jdk5的时候,官方给我们提供了可用的API,我们可以直接使用。但是使用这种方式创建线程比上面两种方式要复杂一些,步骤如下。
创建一个类实现Callable接口,实现call方法。这个接口类似于Runnable接口,但比Runnable接口更加强大,增加了异常和返回值。
创建一个FutureTask,指定Callable对象,做为线程任务。
创建线程,指定线程任务。
启动线程
代码如下:
package com.kingh.thread.create; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; /** * 带返回值的方式 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo11_Callable { public static void main(String[] args) throws Exception { // 创建线程任务 Callable<Integer> call = () -> { System.out.println("线程任务开始执行了...."); Thread.sleep(2000); return 1; }; // 将任务封装为FutureTask FutureTask<Integer> task = new FutureTask<>(call); // 开启线程,执行线程任务 new Thread(task).start(); // ==================== // 这里是在线程启动之后,线程结果返回之前 System.out.println("这里可以为所欲为...."); // ==================== // 为所欲为完毕之后,拿到线程的执行结果 Integer result = task.get(); System.out.println("主线程中拿到异步任务执行的结果为:" + result); } }
执行结果如下:
这里可以为所欲为.... 线程任务开始执行了.... 主线程中拿到异步任务执行的结果为:1
Callable中可以通过范型参数来指定线程的返回值类型。通过FutureTask的get方法拿到线程的返回值。
我们知道,线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。当然了,线程池也不需要我们来实现,jdk的官方也给我们提供了API。
代码如下:
package com.kingh.thread.create; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池 * * @author <a href="https://blog.csdn.net/king_kgh>Kingh</a> * @version 1.0 * @date 2019/3/18 10:04 */ public class CreateThreadDemo12_ThreadPool { public static void main(String[] args) throws Exception { // 创建固定大小的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); while (true) { // 提交多个线程任务,并执行 threadPool.execute(new Runnable() { @Override public void run() { printThreadInfo(); } }); } } /** * 输出当前线程的信息 */ private static void printThreadInfo() { System.out.println("当前运行的线程名为: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (Exception e) { throw new RuntimeException(e); } } }
执行结果如下:
当前运行的线程名为: pool-1-thread-1 当前运行的线程名为: pool-1-thread-2 当前运行的线程名为: pool-1-thread-4 当前运行的线程名为: pool-1-thread-3 当前运行的线程名为: pool-1-thread-7 当前运行的线程名为: pool-1-thread-8 当前运行的线程名为: pool-1-thread-9 当前运行的线程名为: pool-1-thread-6 当前运行的线程名为: pool-1-thread-5 当前运行的线程名为: pool-1-thread-10
线程池的内容还有非常多,这里不再详细地讲解。
更多编程相关知识,请访问:编程教学!!
Atas ialah kandungan terperinci Beberapa cara untuk melaksanakan multithreading dalam java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!