Thread的几种常用方法

本文详细介绍了Java中线程的三种创建方式:继承Thread类、实现Runnable接口和使用Callable/Future,以及Executor框架的使用,涵盖了线程池管理、线程中断、线程等待(wait/notify机制)和线程同步。重点讲解了线程中断的正确处理和线程池的优点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程的创建

1)继承Thread类创建线程

通过继承Thread类来创建并启动多线程的一般步骤如下

1】d定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

2】创建Thread子类的实例,也就是创建了线程对象

3】启动线程,即调用线程的start()方法
 

2)实现Runnable接口创建线程

通过实现Runnable接口创建并启动线程一般步骤如下:

1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

3】第三部依然是通过调用线程对象的start()方法来启动线程
 

3)使用Callable和Future创建线程

1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
 

4)使用线程池例如用Executor框架

1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象。

    Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

    Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

    ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

线程中断

线程中断就是处于运行状态的线程被强制打断。线程中断总体来说有三种方法:正常退出、stop暴力停止、interrupt异常停止。其中使用stop()方法是不安全的,可能导致数据不同步或者资源无法回收,目前stop()方法已经被标注为作废方法。一般使用interrupt停止线程,这里有几个与之相关的方法:

public void interrupt() {} // 中断线程
 
public boolean isInterrupted(){} // 查看线程中断状态

打印的线程状态有所变化,但是线程好像又没有中断,这是为什么呢?实际上,interrupt并不能真正的停止线程,只是更换了线程状态标志。在Core Java中有这样一句话:"没有任何语言方面的需求要求一个被中断的程序应该终止。中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断 "。这句话启发了我们如何正确的中断一个线程,中断一个线程的本质就是中断run()方法的执行,也就是说run()不再执行后,这个线程就结束了。所以,通过中断状态标志位来控制run()方法中逻辑代码的运行,就可以很好的保证线程的中断。

class MyThread7 extends Thread {
 
	@Override
	public void run() {
		try {
			Thread.sleep(20000); // 子线程睡眠
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
	}
}
 
public class Interrupt {
 
	public static void main(String[] args) throws InterruptedException {
 
		MyThread7 thread = new MyThread7();
 
		thread.start();
		thread.interrupt(); // 由主线程中断了一个子线程
	}
}


补充:interrupted()方法,作用是测试当前线程是否已经中断,线程的中断状态也是由该方法清除。主线程被打断,使用interrupted()方法查看中断状态,发现标志位改为了true;再次使用interrupted()查看中断状态,发现标志位改为false。在第一次使用interrupted()方法时,它返回标志位,同时清除标志位(置为false),导致下一次的返回值变为false。所以interrupted()方法有清除标志位的功能。
 

线程等待

线程通知与等待wait()/notify()/notifyAll()
Object类是所有类的父类,鉴于继承机制,Java把所有类都需要的方法放在了Object中,现在要用到的通知与等待系列函数也在其中。

wait()函数
当一个线程调用一个**共享变量的wait()**方法时,该调用线程会被阻塞挂起。

wait()方法是属于共享变量的!!!

线程想要申请一个共享变量,则需要等待共享变量空闲或者满足某种程序员需要的条件才可以申请到。如果共享变量是非空闲的状态或不满足条件,那么需要给共享变量加监视锁(监视到什么时候空闲或满足某种条件)并释放自己在当前变量上的其他锁以便别的线程使用(在其他变量上的锁是不会被释放的),等待其他占用线程释放该变量并唤醒该锁。

挂起线程什么时候被取下来开始返回进入运行状态呢?满足如下条件之一即可:

其他线程调用了该共享对象的notify()/notifyAll()方法唤醒。
其他线程调用了该线程的interrupt()方法中断,该线程抛出InterruptedException异常返回(需要进行异常处理)。
等待超时
:如果调用wait()之前没有获取该对象的监视器锁,就会抛出IllegalMonitorStateException异常。
获取监视器锁
synchronized同步代码块
使用该共享变量作为参数:

共享变量方法,方法使用synchronized修饰
synchronized void add(int a, int b) {
    //do something
}
虚假唤醒
在不满足上述三个唤醒条件的情况下,也可能会从挂起变为运行状态,这就是所谓的虚假唤醒。为了避免这种情况发生,应该不停测试线程被唤醒的条件是否满足,不满足就继续等待(while)。

synchronized (obj) {
    while(条件不满足) {
        obj.wait();
    }
}
举例:消费者&生产者
唤醒函数
notify()函数
一个线程在调用共享对象的notify()方法后,会唤醒一个在共享变量上wait等待的线程,如果有多个线程在等待,具体唤醒哪一个则是随机的。

被唤醒线程不会马上返回,必须在获取了共享对象的监视器锁才能返回(唤醒他的线程释放了他的监视器锁之后,但被唤线程不一定能获得此锁,得竞争)。

同时,只有当前进程获取到监视器锁才能调用notify()方法,否则会抛异常。

notifyAll()函数
唤醒共享变量上的所有wait挂起的线程。

等待线程执行终止join
有些场景需要等待某些事情完成后才能继续往下执行:多个线程加载资源等…

wait()等待通知方法是Object类中的方法

join()方法是Thread类直接提供的,无参,返回值void。

亲测:join()方法是阻塞,使得主线程等待join()线程执行完毕再往下执行。

CountDownLatch

睡眠sleep
sleep方法是Thread类中的一个静态方法。

如果线程调用sleep方法,则线程会暂时让出指定时间的执行权,在这期间不参与CPU调度,但是所持监视器资源(锁等)不会让出。

睡眠时间到后,函数正常返回,线程处于就绪状态,参与CPU调度,获得CPU资源后可以继续运行。

如果睡眠期间,其他线程调用interrupt()中断该线程,则该线程在调用sleep处抛出异常InterrupetException并返回。

让出CPU执行权yield()
yield()是Thread类中的一个静态方法。

线程休眠

sleep 线程休眠

sleep是让当前线程进入休眠状态,让出CPU资源,但是不会释放锁。
使用场景:延时执行
例如,Android中的版本更新逻辑,一般我们是在进入主页面几秒后去请求接口获取版本更新信息,就可以用sleep实现。

我们先来看下源码:

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "线程开始执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + "任务执行完毕");
        }, "work");

        thread.start();

        System.out.println("main线程执行");
        
    }

 这里有一个小坑,需要注意下:
我们看到了源码是一个static修饰的方法,那也就是说我们也可以通过对象去调用,下面我们通过对象调用看看是什么效果。

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "线程开始执行");
            System.out.println(threadName + "任务执行完毕");
        }, "work");


        thread.start();
        try {
            thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main线程执行");

    }

也就是说,sleep方法的调用只跟当前线程有关,在哪个线程调sleep,哪个线程就会休眠。

下面我们多加点打印看下是不是这样:

    public static void main(String[] args) {

        Thread thread = new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "线程开始执行");
            try {
                System.out.println(threadName + "开始休眠");
                Thread.sleep(3000);
                System.out.println(threadName + "休眠完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + "任务执行完毕");
        }, "work");


        thread.start();

        String threadName = Thread.currentThread().getName();
        try {
            System.out.println(threadName + "开始休眠");
            thread.sleep(2000);
            System.out.println(threadName + "休眠完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(threadName + "线程执行完毕");

    }

<think>嗯,用户想了解C++中使用std::thread创建线程的几种方法。好的,首先我得回忆一下std::thread的基本用法。我记得创建线程的时候通常需要传入一个可调用对象,比如函数指针、函数对象、lambda表达式之的。可能还有成员函数和带参数的情况需要考虑。 首先,用户可能是一个刚开始学习多线程编程的C++开发者,对线程创建的具体方式不太清楚。他可能需要一些具体的例子来理解不同的方法。所以,我应该分点说明,每种方法都给出示例,这样更容易理解。 然后,我要考虑覆盖不同的情况。比如普通函数、带参数的函数、lambda表达式、成员函数,可能还有静态成员函数。每种情况都需要不同的语法,我应该详细说明。比如,成员函数需要传递对象实例作为参数,而静态成员函数可能和普通函数似。 另外,线程的参数传递需要注意,参数是按值传递的,如果需要传递引用,必须用std::ref来包装。这一点用户可能会遇到问题,所以应该提到。还有线程的join和detach方法,虽然用户的问题是关于创建方法,但可能需要在例子中简单提一下,避免线程在析构时没有join或detach导致的问题。 可能用户在使用过程中会遇到参数传递的错误,比如引用传递的问题或者对象生命周期的问题,所以在例子中给出正确的用法很重要。比如在传递临时变量时,要确保线程执行期间变量仍然有效。 另外,lambda表达式作为线程函数的情况在现代C++中很常见,尤其是捕获局部变量的时候。需要说明值捕获和引用捕获的区别,以及可能引发的悬垂引用问题。 总结下来,我应该分几个部分来介绍: 1. 普通函数(无参数) 2. 带参数的函数 3. Lambda表达式 4. 成员函数 5. 静态成员函数 6. 函数对象(重载了operator()的) 每种情况都给出一个简单的例子,并说明注意事项,比如参数传递、对象的生命周期、join的必要性等。同时,要提醒用户在使用成员函数时,需要传入对象的指针或引用,并且对象必须在线程执行期间保持有效。 可能还需要提到线程不能复制,只能移动,以及如何处理线程的返回值(比如使用std::future和std::promise,但可能超出当前问题的范围,所以暂时不展开)。 后,检查是否有遗漏的情况,确保覆盖了主要的几种方法,并且例子正确,不会有编译错误。</think>在 C++ 中,使用 `std::thread` 创建线程可以通过多种方式实现,以下是几种常见方法及示例: --- ### 1. **普通函数(无参数)** 直接将函数指针传递给 `std::thread` 构造函数: ```cpp #include <iostream> #include <thread> void thread_func() { std::cout << "Thread running!" << std::endl; } int main() { std::thread t(thread_func); t.join(); // 等待线程结束 return 0; } ``` --- ### 2. **带参数的函数** 通过构造函数传递参数(参数默认按值传递): ```cpp #include <thread> #include <iostream> void print_sum(int a, int b) { std::cout << a + b << std::endl; } int main() { std::thread t(print_sum, 3, 4); // 传递参数 3 和 4 t.join(); return 0; } ``` --- ### 3. **Lambda 表达式** 直接定义匿名函数作为线程任务: ```cpp #include <thread> #include <iostream> int main() { std::thread t([]() { std::cout << "Lambda thread!" << std::endl; }); t.join(); return 0; } ``` 若需捕获局部变量(注意生命周期): ```cpp int value = 42; std::thread t([value]() { // 按值捕获 std::cout << "Value: " << value << std::endl; }); ``` --- ### 4. **成员函数** 需传递对象实例作为参数(使用 `&名::函数名` 语法): ```cpp #include <thread> #include <iostream> class MyClass { public: void task() { std::cout << "Member function thread!" << std::endl; } }; int main() { MyClass obj; std::thread t(&MyClass::task, &obj); // 传递对象指针 t.join(); return 0; } ``` --- ### 5. **静态成员函数** 静态函数无需对象实例,直接传递函数指针: ```cpp class MyClass { public: static void static_task() { std::cout << "Static member thread!" << std::endl; } }; int main() { std::thread t(&MyClass::static_task); t.join(); return 0; } ``` --- ### 6. **函数对象(仿函数)** 通过重载 `operator()` 的创建线程: ```cpp #include <thread> #include <iostream> class Functor { public: void operator()() { std::cout << "Functor thread!" << std::endl; } }; int main() { Functor func; std::thread t(func); t.join(); return 0; } ``` --- ### 关键注意事项: 1. **参数传递** - 默认按值传递,若需传递引用,使用 `std::ref` 包装: ```cpp void modify(int& x) { x = 10; } int a = 0; std::thread t(modify, std::ref(a)); // 传递引用 ``` - 确保参数生命周期覆盖线程执行时间。 2. **线程管理** - 必须调用 `join()` 或 `detach()`,否则线程析构时调用 `std::terminate`。 - 使用 `join()` 等待线程完成,或 `detach()` 分离线程(失去控制权)。 3. **错误处理** - 线程函数若抛出异常需在内部捕获,否则可能导致程序终止。 通过灵活组合这些方法,可以适应不同场景的线程需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个学编程的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值