멀티스레딩 구현 방법: 1. Thread 클래스를 상속하고 JDK에서 제공하는 Thread 클래스를 통해 Thread 클래스의 실행 메서드를 다시 작성합니다. 2. Runnable 인터페이스를 구현합니다. Runnable은 "@FunctionalInterface" 기능적 인터페이스입니다. 이는 JDK8에서 제공하는 람다 메소드를 사용하여 스레드 작업을 생성할 수 있음을 의미합니다. 3. 내부 클래스를 사용합니다. 5. 반환 값을 사용하여 스레드를 구현합니다. 6. 스레드 풀을 기반으로 합니다.
이 튜토리얼의 운영 환경: windows7 시스템, java8 버전, DELL G3 컴퓨터.
양식에서 멀티스레딩을 구현하는 방법에는 크게 두 가지가 있습니다. 하나는 Thread 클래스를 상속하는 것이고, 다른 하나는 Runnable 인터페이스를 구현하는 것입니다. 기본적으로 구현 방법은 스레드 작업을 구현한 다음 스레드를 시작하여 스레드 작업을 실행하는 것입니다(여기서 스레드 작업은 실제로 실행 메서드입니다). 여기에 언급된 6가지 유형은 실제로 위의 두 가지 유형을 기반으로 일부 수정된 것입니다.
다음에서는 이러한 6가지 구현 방법을 하나씩 소개합니다.
모든 것이 객체이고 스레드도 객체이며 객체는 공용 특성을 추출하여 클래스로 캡슐화할 수 있어야 합니다. 클래스를 사용하면 여러 인스턴스를 인스턴스화할 수 있습니다. 스레드를 사용하는 첫 번째 방법은 Thread 클래스를 상속하는 것입니다. Thread 클래스를 상속받는 것은 스레드를 구현하는 가장 간단한 방법입니다. JDK에서 제공하는 Thread 클래스를 통해 Thread 클래스의 run 메서드를 다시 작성하면 됩니다. 그런 다음 스레드가 시작되면 run 메서드 본문의 내용이 실행됩니다. 코드는 다음과 같습니다.
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()); } }
실행 결과는 다음과 같습니다
当前运行的线程名为: main 当前运行的线程名为: MyThread 当前运行的线程名为: main 当前运行的线程名为: MyThread 当前运行的线程名为: MyThread 当前运行的线程名为: main
여기서 주의해야 할 점은 스레드 시작 시 스레드 클래스의 run 메서드가 호출되지 않고 스레드 클래스의 시작 메서드가 호출된다는 점입니다. 라고 불리는. 그러면 run 메소드를 호출할 수 있나요? 대답은 '예'입니다. run 메소드는 공개적으로 선언된 메소드이므로 호출할 수 있지만, run 메소드를 호출하면 이 메소드는 일반 메소드로 호출되어 스레드를 시작하지 않습니다. 실제로는 디자인 패턴에서 템플릿 메소드 패턴을 사용하는데, Thread 클래스가 템플릿 역할을 하고, run 메소드도 바뀌어서 서브클래스에 구현됩니다.
위의 예에서는 우리가 생성한 하나의 스레드 외에도 실제로 실행 중인 메인 스레드가 있습니다. 그렇다면 이 두 스레드 외에 실행 중인 다른 스레드도 있나요? 예를 들어 우리가 볼 수 없는 가비지 수집 스레드도 자동으로 실행되고 있습니다. 여기서는 몇 개의 스레드가 실행되고 있는지 고려하지 않습니다. 위에서 직접 스레드를 생성했으므로 몇 개 더 생성하여 함께 실행할 수 있습니까? Thread 클래스는 스레드 개체입니다. 따라서 여러 Thread 클래스를 만들고 해당 시작 메서드를 호출하여 여러 스레드를 시작합니다. 코드는 다음과 같습니다
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()); } }
실행 결과는 다음과 같습니다
当前运行的线程名为: main 当前运行的线程名为: MyThread-02 当前运行的线程名为: MyThread-01 当前运行的线程名为: main 当前运行的线程名为: MyThread-01 当前运行的线程名为: MyThread-02 当前运行的线程名为: main
여러 개의 Thread 클래스를 생성하고 해당 시작 메소드를 호출하면 여러 개의 스레드가 시작되는 것을 볼 수 있습니다. 위의 코드에서는 생성된 스레드에 MyThread-01 및 MyThread-02라는 이름이 할당된 다음 상위 클래스의 setName 메서드를 호출하여 스레드 이름이 스레드 이름에 할당됩니다. 생성자 방법. 스레드 이름을 지정하지 않으면 시스템에서는 기본적으로 스레드 이름을 지정하며 명명 규칙은 Thread-N 형식입니다. 그러나 문제 해결을 용이하게 하려면 스레드를 생성할 때 적절한 스레드 이름을 지정하는 것이 좋습니다. 다음 코드는 스레드 이름을 사용하지 않습니다
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()); } }
실행 결과는 다음과 같습니다.
当前运行的线程名为: main 当前运行的线程名为: Thread-1 当前运行的线程名为: Thread-0 当前运行的线程名为: main 当前运行的线程名为: Thread-1 当前运行的线程名为: Thread-0
Runnable 인터페이스를 구현하는 것도 스레드를 생성하는 일반적인 방법입니다. 인터페이스를 사용하면 프로그램의 결합을 줄일 수 있습니다. Runnable 인터페이스에는 실행되는 메서드가 하나만 정의되어 있습니다. Runnable 인터페이스의 코드를 살펴보겠습니다.
package java.lang; @FunctionalInterface public interface Runnable { public abstract void run(); }
사실 Runnable은 스레드 작업과 스레드 제어가 분리되어 있습니다. 스레드를 구현하려면 Thread 클래스를 사용할 수 있습니다. Thread 클래스에서 수행할 작업은 Runnable 인터페이스를 구현하는 클래스에서 처리할 수 있습니다. 이것이 Runnable의 핵심입니다!
Runnable은 @FunctionalInterface 기능 인터페이스입니다. 이는 JDK8에서 제공하는 람다 메서드를 사용하여 스레드 작업을 생성할 수 있음을 의미합니다. 다음 코드는 독자에게 이를 사용하는 방법을 보여줍니다.
위 예제를 구현하기 위해 Runnable을 사용하는 단계는 다음과 같습니다.
线程任务就是线程要做的事情,这里我们让这个线程每隔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
线程池的内容还有非常多,这里不再详细地讲解。
更多编程相关知识,请访问:编程教学!!
위 내용은 Java에서 멀티스레딩을 구현하는 여러 가지 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!