文章目录
一、线程是什么,相关概念?
讲解线程之前我们先了解一下其它相关的概念:
程序:是为了完成特定任务,用某种语言编写的一组指令的集合,简单来说就是,我们写的代码
示例:
//判断号码的格式是否正确
public boolean isValidMobileNumber(String str){
if (str.length() !=11 ){
return false;
}
for (int i=0; i<str.length();i++ ){
char a = str.charAt(i);
if (a<'0' || a>'9'){
return false;
}
}
return true;
}
在此之上:
进程:是程序的一次执行过程,或是正在运行的一个程序,是动态过程,有它自身的产生,存在和消亡的过程 。你打开一个程序就是打开了一个进程
线程:线程是由进程产生的,是进程的一个实体,一个进程可以产生多个线程。示例:打开百度网盘下载多个资源。
单线程:同一时刻,只允许执行一个线程执行
多线程:同一时刻,只允许执行多个线程执行
并发:同一时刻,多个任务交替执行,单核CPU实现的多任务
并行:多任务同时执行,多核CPU实现
示例:当一个人一边开车一边打电话相当于是一个并发,而当两个人一个开车,另一个打电话相当于并行。
这里提到一个法可以获取直接电脑的处理核心数:
public class CpuNum {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
//获取当地电脑的核心数量
int cpuNum = runtime.availableProcessors();
System.out.println(cpuNum);
}
}
Runtime类是Java中与运行时环境相关的类,主要用于获取当前JVM的运行状态、内存使用情况、执行系统命令等操作。其中,availableProcessors()方法可以获取当前计算机的CPU核心数量,该方法返回一个int类型的值。 这段代码使用Java中的Runtime类获取当前运行时环境,然后调用其getRuntime()方法获取Runtime实例。接着通过调用availableProcessors()方法获取当前电脑的CPU核心数量,并将其赋值给变量cpuNum。最后通过调用System.out.println()方法将cpuNum输出到控制台。
二、创建线程的四种方式
1.继承Thread类
继承Thread类:通过继承Thread类,重写其run()
方法,然后创建线程对象并调用start()
方法来启动线程。
class MyThread extends Thread {
public void run() {
// 线程执行的代码
}
}
// 创建线程对象并启动线程
MyThread thread = new MyThread();
thread.start();
优点::编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this即可获取当前线程
缺点:因为线程类已经继承了Thread类,Java语言是单继承的,所以就不能再继承其他父类了。
2.实现Runnable接口
代码如下(示例):
class MyRunnable implements Runnable {
public void run() {
// 线程执行的代码
}
}
// 创建Runnable对象
Runnable myRunnable = new MyRunnable();
// 创建Thread对象,并将Runnable对象传递给Thread的构造函数
Thread thread = new Thread(myRunnable);
// 启动线程
thread.start();
3.实现Callable接口(JDK>=1.5)
代码如下(示例):
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
// 线程执行的代码
return result;
}
}
// 创建Callable对象
Callable<Integer> myCallable = new MyCallable();
// 创建ExecutorService对象
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交Callable任务并获取Future对象
Future<Integer> future = executor.submit(myCallable);
// 获取线程返回结果
Integer result = future.get();
// 关闭ExecutorService
executor.shutdown();
两个实现接口的方法:
优点:线程类只是实现了Runnable或者Callable接口,还可以继承其他类。这种方式下,多个线程 可以共享一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。
缺点:编程稍微复杂一些,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法
4.线程池方式创建
示例代码:
// 创建线程池对象
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交Runnable任务给线程池执行
executor.execute(new Runnable() {
public void run() {
// 线程执行的代码
}
});
// 关闭线程池
executor.shutdown();
线程池创建线程的优点包括:
- 提高系统性能:线程池可以复用线程,减少线程的创建和销毁开销,提高系统的性能。
- 资源管理:线程池可以控制线程的数量,避免线程数量过多导致系统资源耗尽。
- 提高代码可读性:使用线程池可以更好地实现任务和线程的分离,提高代码的可读性和可维护性。
线程池创建线程的缺点包括:
- 需要额外学习:使用线程池需要理解线程池的工作原理和使用方式,需要一定的学习成本。
- 可能会浪费资源:如果线程池的大小设置不合理,可能会浪费系统资源或者导致任务等待时间变长。
- 可能会引起死锁:如果线程池中的任务依赖于其他任务的结果,可能会发生死锁现象。
三、Thread local的用法
ThreadLocal是Java中的一个线程局部变量,它可以让每个线程在访问同一个变量时,都有自己独立的副本。ThreadLocal通常用于解决多线程并发访问共享变量的线程安全问题。
ThreadLocal的基本用法是,首先创建一个ThreadLocal对象,然后通过调用它的set()方法来设置当前线程的局部变量值,通过调用get()方法来获取当前线程的局部变量值。
public class UserContext {
private static final ThreadLocal<String> userThreadLocal = new ThreadLocal<>();
public static void setUser(String username) {
userThreadLocal.set(username);
}
public static String getUser() {
return userThreadLocal.get();
}
}
在上述代码中,我们首先创建了一个ThreadLocal对象userThreadLocal,它的泛型参数为String类型。然后我们定义了两个静态方法,分别用于设置和获取当前线程的用户名。在setUser()方法中,我们调用ThreadLocal的set()方法来设置当前线程的用户名;在getUser()方法中,我们调用ThreadLocal的get()方法来获取当前线程的用户名。
使用ThreadLocal的好处在于,每个线程都有自己独立的副本,它们之间互不干扰,不需要加锁或者使用同步机制,从而避免了线程安全问题。例如,在上述代码中,如果多个线程同时访问UserContext.setUser()方法,每个线程都会有自己独立的副本,不会相互干扰,从而保证了线程安全。
需要注意的是,ThreadLocal虽然可以解决线程安全问题,但过多的使用ThreadLocal也会导致内存泄漏和性能问题,因为每个ThreadLocal都会占用一定的内存,而且ThreadLocal的使用也会增加线程上下文切换的开销。因此,在使用ThreadLocal时需要注意控制数量和生命周期,避免出现内存泄漏和性能问题。
总结
本章内容主要讲了线程的几个基本概念,以及创建线程的几种方式,这几种方式的优缺点,最后讲了ThreadLocal的用法。