Java多线程_02 创建线程的五种方式


继承Thread类

通过继承Thread并且重写其run()方法,run()方法中定义需要执行的任务。
创建后的子类通过调用start()方法即可执行线程方法。
通过继承Thread实现的线程类,多个线程间无法共享线程类的实例变量。创建的是不同的Thread对象,自然不共享资源。

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/03/22 20:32
 * @Description: 
 *  1.定义MyThread类继承Thread
 *  2.重写run方法
 *  3.创建MyThread对象
 *  4.调用start()方法启动线程
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+" is running :"+i);
        }
    }
}
public class MyThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
    }
}

在这里插入图片描述

根据结果我们可以发现,多线程之间(调用start()方法启动)是并行的关系,或者说同时执行,并不是先执行t1线程在执行t2线程,执行的顺序是个概率问题。

实现Runnable接口

需要先定义一个类实现Runnable接口并重写该接口的run()方法,此run()方法是线程执行体。接着创建Runnable实现类的对象,作为创建Thread对象的参数target,此Thread对象才是真正的线程对象。

利用实现Runnable接口的线程类创建对象,可以实现线程之间的资源共享。

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/03/22 20:47
 * @Description:
 * 1.定义一个类UserRun实现Runnable接口
 * 2.重写run()方法
 * 3.创建UserRun类对象
 * 4.创建Thread对象,把UserRun对象作为构造方法的参数
 * 5.启动线程
 */
public class UserRun implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " is running :" + i);
        }
    }
}
public class UserRunTest {
    public static void main(String[] args) {
        UserRun userRun = new UserRun();
        new Thread(userRun).start();
        new Thread(userRun).start();
    }
}

Runnable属于功能性接口,所以我们还可以通过匿名内部类以及lambda表达式的方式实现:

public class UserRunTest {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + " is running :" + i);
            }
        }).start();
    }
}

实现Callable接口

Callable接口如同Runnable接口的升级版,其提供的call()方法将作为线程的执行体,同时允许有返回值

Callable对象不能直接作为Thread对象的target,因为Callable接口不是Runnable接口的子接口。
对于这个问题的解决方案,就引入Future接口,此接口可以接受call()的返回值,RunnableFuture接口是Future接口和Runnable接口的子接口,可以作为Thread对象的target。

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/03/23 10:50
 * @Description:
 * 1.定义类MyCallable实现Callable接口
 * 2.实现call()方法
 * 3.创建MyCallable的对象
 * 4.创建RunnableFuture接口的子类FutureTask的对象,构造函数的参数的对象是MyCallable对象
 * 5.创建Thread类的对象,构造函数的参数是FutureTask的对象
 * 6.启动线程
 */
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+" is called!");
        return "学习";
    }
}

public class MyCallableTest {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
使用匿名内部类:

public class MyCallableTest {
    public static void main(String[] args) {
        new Thread(new FutureTask<>(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                System.out.println(Thread.currentThread().getName()+" is " +
                        "called");
                return "学习";
            }
        })).start();
    }
}

继承TimerTask

TimerTimerTask可以作为实现线程的另一种方式。
Timer是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或定期重复执行,可以看成一个定时器,可以调度TimerTask
TimeTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力。

/**
 * @Author: Forward Seen
 * @CreateTime: 2022/03/23 13:45
 * @Description:
 * 步骤:
 * 1.定义类UserTask,继承抽象类TimerTask
 * 2.创建UserTask类的对象
 * 3.创建Timer类的对象,设置任务的执行策略
 */
public class MyTask extends TimerTask {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" is running:"+new Date());
    }
}
public class MyTaskTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" is running:"+new java.util.Date());
        Timer timer = new Timer();
        timer.schedule(new MyTask(),5000,3000);
    }
}

在这里插入图片描述
根据结果我们可以发现,每隔三秒定时调度这个Timer-0的线程。

通过线程池启动多线程

什么是线程池
首先我们应该了解什么是线程池,其实线程池就像一个池子,池子里面放着固定数量的线程。如图,该线程池中有7个线程,它们不会自动创建和销毁,当需要用到线程时,直接从线程池中调取一个线程,当这个线程用完之后再放回到线程池中。当一次性来了两个任务,那么就从线程池中拿出两个线程来执行,执行完后直接放回池中。当一次性来了10个任务,而线程池中一共就7个线程,那么剩下3个任务就等待,当有线程空闲下来才会去处理。
在这里插入图片描述

线程池的优点:

提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行。
降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁的消耗。
方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。

Java中可以通过Executors的工具类可以创建线程池。

  • FixThreadPool固定大小的线程池
public class FixThreadPoolTest {
    public static void main(String[] args) {
//        1.创建固定大小的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);
//        2.使用线程池执行任务
        for (int i = 0; i < 5; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName()+
                                ":"+j);
                    }
                }
            });
        }
//        3.关闭线程池
        pool.shutdown();
    }
}

在这里插入图片描述
根据结果,我们发现,5个任务,只有3个线程在执行,因为线程池限制了线程的数量,并且每个线程执行完一个任务才去执行下一个任务。

  • SingleThreadExecutor单线程池
public class SingleThreadExecutorTest {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 3; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 4; j++) {
                        System.out.println(Thread.currentThread().getName() +
                                ":" + j);
                    }
                }
            });
        }
        pool.shutdown();
    }
}

在这里插入图片描述
对于单线程的结果,我们发现,它是按照任务提交顺序执行的,执行完当前任务才去执行下一个任务。

  • CachedThreadPool缓存线程池
    缓存线程池中的线程数量不是固定的,而是动态的。它给到了最大值,也就是int类型所能表示的最大值。当线程池中没有可用的线程时,它会自动创建一个线程,并且该线程池中的线程可以缓存、重复利用和回收,回收的默认时间为1min。
public class CachedThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 3; j++) {
                        System.out.println(Thread.currentThread().getName()+
                                " : "+j);
                    }
                }
            });
        }
        pool.shutdown();
    }
}

在这里插入图片描述
根据结果,我们有5个任务,所以该线程池创建了5个线程。

  • ScheduledThreadPool周期性线程池,支持定时及周期性执行任务。
    参数是核心线程数,核心线程数指的是线程池中最小的线程数量,也就是说,核心线程创建后不会被回收。

ScheduledThreadPool有个scheduledAtFixedRate()方法,有4个参数,第一个参数是线程对象,第二个参数是首次执行任务的延迟时间,第三个参数表示每隔多长时间执行run()中的任务,最后一个参数是时间单位。

public class ScheduledThreadPoolTest {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        pool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":"+new Date());
            }
        },5,3, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

  • WorkStealingPool任务窃取线程池。
    之前的线程池中的线程都是共享任务队列的,而WorkStealingPool是每个线程都有自己的任务队列。

假如有一个任务队列,其中有一个很大的任务,剩下的任务都很小,由线程池分配任务给线程,那么拿到这个大的任务的线程压力就会很大,这样就会导致cpu负载不均衡。而WorkStealingPool是每个线程都有自己的任务队列,一旦线程发现自己的任务执行完了,他就会到别的线程中获取任务来执行,所以叫“窃取”。WorkStealingPool可以更加合理得使用CPU,非常适用于很耗时间的任务中,也可以充分利用多核CPU的优势。

public class WorkStealingPoolTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("--start--");
        ExecutorService pool = Executors.newWorkStealingPool();
        for (int i = 0; i < 10; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        //让主线程等待子线程执行完毕
        Thread.sleep(2000);
        System.out.println("--end--");
    }
}

在这里插入图片描述
如果其中的某个任务很大,该线程池会把这个大任务拆分成小任务分配到空闲的线程中,等这些小任务执行完成后会将这些结果合并。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值