Ways to implement multi-threading: 1. Inherit the Thread class, use the Thread class provided by JDK, and rewrite the run method of the Thread class; 2. Implement the Runnable interface, which is a "@FunctionalInterface" functional formula interface, which means that you can use the lambda method provided by JDK8 to create thread tasks; 3. Use internal classes; 4. Use timers; 5. Thread implementation with return values; 6. Implement multiple threads based on thread pools. thread.
The operating environment of this tutorial: windows7 system, java8 version, DELL G3 computer.
There are two main ways to implement multi-threading in form, one is to inherit the Thread class, and the other is to implement the Runnable interface. Essentially, the implementation method is to implement the thread task, and then start the thread to execute the thread task (the thread task here is actually the run method). The 6 types mentioned here are actually some modifications based on the above two types.
The following will introduce each of these 6 implementation methods one by one.
The first way: inherit the Thread class
Everything is an object, then threads are also objects, and objects should Its public characteristics can be extracted and encapsulated into classes, and multiple objects can be instantiated using classes. Then the first way to implement threads is toinherit the Thread class. Inheriting the Thread class is the simplest way to implement threads. Just rewrite the run method of the Thread class through the Thread class provided by the JDK. Then when the thread starts, the contents of the run method body will be executed. The code is as follows:
package com.kingh.thread.create; /** * 继承Thread类的方式创建线程 * * @authorThe running results are as follows
当前运行的线程名为: main 当前运行的线程名为: MyThread 当前运行的线程名为: main 当前运行的线程名为: MyThread 当前运行的线程名为: MyThread 当前运行的线程名为: mainIt should be noted here that when starting a thread, the run method of the thread class is not called, but the start of the thread class is called. method. So can we call the run method? The answer is yes, because the run method is a publicly declared method, so we can call it, but if we call the run method, then this method will be called as an ordinary method and will not start the thread. This actually uses the template method pattern in the design pattern. The Thread class serves as the template, and the run method is changing, so it is implemented in a subclass.
1. Create multiple threads
In the above example, in addition to the one thread we created, there is actually a main thread Also executing. So besides these two threads, are there any other threads executing? In fact, there are. For example, theGarbage Collection Threadthat we cannot see is also executing silently. Here we do not consider how many threads are executing. We have created a thread ourselves above, so can we create a few more and execute them together? The answer is yes. A Thread class is a thread object, so create several Thread classes and call their start method to start multiple threads. The code is as follows
package com.kingh.thread.create; /** * 创建多个线程同时执行 * * @authorThe running result is as follows
当前运行的线程名为: main 当前运行的线程名为: MyThread-02 当前运行的线程名为: MyThread-01 当前运行的线程名为: main 当前运行的线程名为: MyThread-01 当前运行的线程名为: MyThread-02 当前运行的线程名为: main2. Specify the thread name
You can see that by creating multiple Thread class, and its start method is called to start multiple threads. Each thread has its own name. In the above code, the names MyThread-01 and MyThread-02 are assigned to the created threads, and then the thread name is assigned to the thread name by calling the setName method of the parent class in the constructor method. If you do not specify a thread name, the system will specify the thread name by default, and the naming rule is in the form of Thread-N. However, in order to facilitate troubleshooting, it is recommended to specify a reasonable thread name when creating a thread. The following code is what it looks like without using the thread name
package com.kingh.thread.create; /** * 创建多个线程同时执行,使用系统默认线程名 * * @authorThe running result is as follows:
当前运行的线程名为: main 当前运行的线程名为: Thread-1 当前运行的线程名为: Thread-0 当前运行的线程名为: main 当前运行的线程名为: Thread-1 当前运行的线程名为: Thread-0The second way: implement the Runnable interface
Implementing the Runnable interface is also a common way to create threads. Using the interface can reduce the coupling of our programs. There is only one method defined in the Runnable interface, which is run. Let's take a look at the code of the Runnable interface.
package java.lang; @FunctionalInterface public interface Runnable { public abstract void run(); }In fact, Runnable is a thread task. The thread task and thread control are separated. This is the decoupling mentioned above.If we want to implement a thread, we can use the Thread class. The tasks to be performed by the Thread class can be handled by classes that implement the Runnable interface.This is the essence of Runnable!
Runnable is a @FunctionalInterface functional interface, which means that you can use the lambda method provided by JDK8 to create thread tasks. The following code will show readers how to use it.
The steps to use Runnable to implement the above example are as follows:
- 定义一个类实现Runnable接口,作为线程任务类
- 重写run方法,并实现方法体,方法体的代码就是线程所执行的代码
- 定义一个可以运行的类,并在main方法中创建线程任务类
- 创建Thread类,并将线程任务类做为Thread类的构造方法传入
- 启动线程
1. 创建线程任务
线程任务就是线程要做的事情,这里我们让这个线程每隔1s中打印自己的名字
package com.kingh.thread.create; /** * 线程任务 * * @author2. 创建可运行类
在这里创建线程,并把任务交给线程处理,然后启动线程。
package com.kingh.thread.create; /** * 创建线程 * * @author线程任务和线程的控制分离,那么一个线程任务可以提交给多个线程来执行。这是很有用的,比如车站的售票窗口,每个窗口可以看做是一个线程,他们每个窗口做的事情都是一样的,也就是售票。这样我们程序在模拟现实的时候就可以定义一个售票任务,让多个窗口同时执行这一个任务。那么如果要改动任务执行计划,只要修改线程任务类,所有的线程就都会按照修改后的来执行。相比较继承Thread类的方式来创建线程的方式,实现Runnable接口是更为常用的。
3. lambda方式创建线程任务
这里就是为了简化内部类的编写,简化了大量的模板代码,显得更加简洁。如果读者看不明白,可以读完内部类方式之后,回过来再看这段代码。
package com.kingh.thread.create; /** * 创建线程with lambda * * @author这并不是一种新的实现线程的方式,只是另外的一种写法。比如有些情况我们的线程就想执行一次,以后就用不到了。那么像上面两种方式(继承Thread类和实现Runnable接口)都还要再定义一个类,显得比较麻烦,我们就可以通过匿名内部类的方式来实现。使用内部类实现依然有两种,分别是继承Thread类和实现Runnable接口。代码如下:
package com.kingh.thread.create; /** * 匿名内部类的方式创建线程 * * @author可以想象一下,我能不能既基于接口,又基于子类呢?像下面的代码会执行出什么样子呢?
package com.kingh.thread.create; /** * 匿名内部类的方式创建线程 * * @author运行结果如下:
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来改写上面的基于接口的形式的代码,如下
// 使用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来实现定时任务。
1. 指定时间点执行
package com.kingh.thread.create; import java.text.SimpleDateFormat; import java.util.Timer; import java.util.TimerTask; /** * 定时任务 * * @author2.间隔时间重复执行
package com.kingh.thread.create; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * 定时任务 * * @author关于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 task = new FutureTask<>(call); // 开启线程,执行线程任务 new Thread(task).start(); // ==================== // 这里是在线程启动之后,线程结果返回之前 System.out.println("这里可以为所欲为...."); // ==================== // 为所欲为完毕之后,拿到线程的执行结果 Integer result = task.get(); System.out.println("主线程中拿到异步任务执行的结果为:" + result); } }执行结果如下:
这里可以为所欲为.... 线程任务开始执行了.... 主线程中拿到异步任务执行的结果为:1Callable中可以通过范型参数来指定线程的返回值类型。通过FutureTask的get方法拿到线程的返回值。
我们知道,线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。当然了,线程池也不需要我们来实现,jdk的官方也给我们提供了API。
代码如下:
package com.kingh.thread.create; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池 * * @author执行结果如下:
当前运行的线程名为: 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线程池的内容还有非常多,这里不再详细地讲解。
更多编程相关知识,请访问:编程教学!!
The above is the detailed content of Several ways to implement multithreading in java. For more information, please follow other related articles on the PHP Chinese website!