Java (七) 多线程(2) —— 线程的创建和启动

本文详细介绍Java中三种创建线程的方式:继承Thread类、实现Runnable接口和实现Callable接口,并对比各自优缺点;同时介绍了Thread类常用方法及线程生命周期状态,最后探讨了线程同步的三种方式。

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

一 线程的创建和启动
方式1:继承Thread类
编程简单,但是Java是单继承的.无法再继承其他类.

1.自定义一个类继承Thread类,这个类就是多线程类.
2.[重写]Thread类里面的run方法,把需要执行的自定义线程代码放入这个run方法.
3.创建线程类对象
4.开启线程,使用start()方法

先创建一个桌子类,继承自Thread

public class DeskThread extends Thread{
    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            System.out.println("Desk" + i);
        }
    }
}

再创建另一个椅子类,也继承自Thread

public class ChairThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Chair" + i);
        }
    }
}

在主线程中将上述两个线程启动

public class TestThread {
    public static void main(String[] args) {
        System.out.println("Strat");
        Thread desk = new DeskThread();
        desk.setName("ChairThread");
        desk.start();
        Thread chair = new ChairThread();
        chair.start();
        System.out.println("End");
    }
}

运行结果:

Strat
Desk0
Desk1
Chair0
Chair1
Chair2
Chair3
Chair4
Chair5
Chair6
Chair7
Chair8
Chair9
End
Desk2
Desk3
Desk4
Desk5
Desk6
Desk7
Desk8
Desk9

Process finished with exit code 0

可以发现运行结果中,桌子线程 椅子线程交替出现, End字符串也并不一定是最后出现,说明了主线程 椅子线程 桌子线程 三个线程互相竞争CPU资源,也就是并行处理。如果将start()改为run(),就只剩主线程一个线程了,就是串行处理了。
只用重写run方法,而不要重写start方法

方式2:实现Runnable接口
Runnable接口只有一个抽象方法:run(),这样的创建线程的方式风险性更低,也更解耦.
而且由于是函数式接口,可以使用Lambda表达式.

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
1.自定义一个类,[实现]Runnable接口
2.重写Runnable接口中唯一的方法run(),把线程的代码写入到run()
3.创建Thread类对象,并且把[实现]Runnable接口的自定义类对象作为参数传入到Thread构造方法中
 这里可以看下Thread类的构造方法接受那些参数,一般用这两种:Thread(Runnable target),Thread(Runnable target, String name)
4.调用Thread类对象的start方法,开启线程
[优点]: 可以继承其他类,更方便多个线程共享一个资源 ,只有一个抽象方法,使用风险小.
[缺点]: 编程稍微复杂.需要将定义的实现类作为参数传入到Thread的构造方法中.

首先编写一个实现Runnable接口的类,桌子线程

public class DeskTask implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "::第" + i + "个");
        }
    }
}

再编写另一个类做对比,椅子线程

public class ChairTask implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "::第" + i + "个");
        }
    }
}

在主线程里面,要创建Thread类对象,将上述两个线程分别“注入”到线程当中.最后启动线程

public class TestRunnable {
    public static void main(String[] args) {
        System.out.println("Start");
        Thread t1 = new Thread(new DeskTask(), "桌子线程");
        t1.start();
        Thread t2 = new Thread(new ChairTask(),"椅子线程");
        t2.start();
        System.out.println("End");
    }
}

结果:

Start
桌子线程::第0个
End
桌子线程::第1个
椅子线程::第0个
桌子线程::第2个
椅子线程::第1个
桌子线程::第3个
桌子线程::第4个
桌子线程::第5个
椅子线程::第2个
椅子线程::第3个
椅子线程::第4个
椅子线程::第5个
椅子线程::第6个
椅子线程::第7个
椅子线程::第8个
椅子线程::第9个
桌子线程::第6个
桌子线程::第7个
桌子线程::第8个
桌子线程::第9个

Process finished with exit code 0

创建方式3:实现Callable接口

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

JDK1.5 之后的新特性
需要导入包

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
1.自定义一个类,[实现]Callable接口
2.实现Callable接口中唯一方法call().
3.call()方法的特点:有返回值,抛出异常,支持泛型,如果没有返回值,如果没有抛出检查异常,建议使用方式1或2
4.创建一个FutureTask对象,将上面的实现类对象作为参数传入FutureTask类对象,创建Thread类对象,将FutureTask类对象作为参数传入Thread类对象.
5.调用Thread类对象的start方法,开启线程

FutureTask 实现了RunnableFuture接口,
RunnableFuture 继承了 Runnable 接口和 Future 接口

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

二 Thread 常用方法

Thread();            无参构造方法,
Thread(Runnable target);    分配新的Thread对象
Thread(Runnable target, String name);    分配新的Thread对象
Thread(String name);    分配新的Thread对象
Thread(ThreadGroup group, Runnable target);     分配新的Thread对象,指定所属线程组
Thread(ThreadGroup group, Runnable target, String name);  分配新的Thread对象,以便将target作为运行对象.指定所属线程组,并指定名称
Thread(ThreadGroup group, String name);  
run();                  线程体
start();				开启自定义线程,执行线程中的run方法
setName(String name);   修改线程的名字
getName();			    获取线程的名字
static sleep();			通过Thread类名调用,这里需要处理一些异常.要求当前线程停止固定毫秒
static currentThread(); 返回当前的线程对象,返回值:[线程名,优先级,线程组名]
getPriority();			返回当前线程的优先级CPU执行的优先级,不是绝对的
setPriority();			设置线程的优先级
	[注]: 线程优先级等级越高,执行概率越高

三 线程的生命周期
线程的生命周期:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。
这里写图片描述
这张图还是挺重要的,能够帮助我们理解JVM处理Thread的过程.其中 t 是主线程 Thread 是我们新建的线程, o 是同步监听器对象.

四 线程同步
方式1 同步代码块
使用synchronized关键字:

synchronized(同步监视器) {
	需要同步的代码块
}

1.同步监视器必须是引用数据类型,不能是基本数据类型,也最好不是String和Integer.
2.被锁住的对象可以改变值,但不会改变引用,所以建议用final修饰.
3.sleep()不会释放锁对象,不会开锁.
4.线程安全,但开销大.效率低,甚至出现“死锁”的现象.

方式2 同步代码
直接对方法进行同步

public void run() {
	method();
}

public synchronized void method() {
	需要同步的代码
}

1.不要将run()置为同步方法,否则就变成串行了
2.此时同步监视器是 this. 所以一个同步方法被锁定,当前类的所有同步方法都被锁定

方式3 Lock
JDK1.5的新特性,具体方法在另一篇博客中有介绍https://www.cnblogs.com/dolphin0520/p/3923167.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值