多线程与高并发(二)Java创建多线程

前言

该节主要介绍Java创建线程的三种方式。

  • 继承Thread类
  • 实现Runable接口
  • 实现Callable接口

此外还介绍一种与线程有关的技术:线程池。

继承Thread类

我们若用继承Thread类来实现多线程的话,我们还需要重写其run()方法,并且用start()来启动该线程。

public class ThreadTest extends Thread{
    public static void main(String[] args) {
        Thread t1 = new ThreadTest();
        Thread t2 = new ThreadTest();
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println("芜湖"+i);
        }
    }
}

这就是一个简单的线程,并且我还写了一个简单的测试。这里给出前50项输出结果。

芜湖0
芜湖1
芜湖2
芜湖3
芜湖4
芜湖5
芜湖6
芜湖7
芜湖8
芜湖9
芜湖10
芜湖11
芜湖12
芜湖13
芜湖14
芜湖15
芜湖16
芜湖17
芜湖18
芜湖19
芜湖20
芜湖21
芜湖22
芜湖23
芜湖24
芜湖25
芜湖26
芜湖27
芜湖28
芜湖29
芜湖30
芜湖31
芜湖0
芜湖1
芜湖2
芜湖3
芜湖4
芜湖5
芜湖6
芜湖7
芜湖8
芜湖32
芜湖33
芜湖9
芜湖10
芜湖11
芜湖12
芜湖13
芜湖14
芜湖15
芜湖16

我们可以很明显的看到出现了输出乱序,假如我们是单线程的话,那我们应该先执行100次循环,在执行下一次100次循环。

同时我们还可以知道,因为线程运行的不确定性,那么但我们再次执行main方法时,输出不一定与上一次一致。

这就是简单的继承Thread实现多线程。

实现Runable接口

public class RunableTest implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        Thread t1=new Thread(new RunableTest());
        Thread t2=new Thread(new RunableTest());
        t1.start();
        t2.start();
    }
}

与Thread类似,也是重写Run方法,不过我们这个时候需要将实现Runable接口的对象当做参数传入Thread的构造函数中。

运行结果:

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

Process finished with exit code 0

实现Callable接口

public class CallableTest implements Callable {
    @Override
    public Object call() throws Exception {
        Random generator = new Random();
        Integer randomNumber = generator.nextInt(5);
        Thread.sleep(randomNumber*1000);
        return randomNumber;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableTest t1=new CallableTest();
        FutureTask<Integer> result = new FutureTask<>(t1);
        new Thread(result).start();

        Integer num=result.get();
        System.out.println(num);
    }
}

实现Callable接口我们需要重写 call() 方法,并且其方法带有返回值,可以通过 FutureTask 来操控Callable类。

运行结果(不唯一):

4

Process finished with exit code 0

其中我们可以看到重写了call()方法之后,我们是创建了一个FutureTask类,然后创建一个新线程将该FutureTask类作为参数传入构造函数。
而我们获取线程处理结果的时候,是使用该FutureTask对象获取,那么该对象便是十分重要的一个对象。

FutureTask类详解

概念:FutureTask类主要用来实现一个可取消的异步计算,实现Future的主要方法,可以查询是否完成计算,同时在计算完之后查询计算的结果。同时如果计算已经完成,那么就不能再次启动或者取消。

FutureTask类的重要常量
/**
     * The run state of this task, initially NEW.  The run state
     * transitions to a terminal state only in methods set,
     * setException, and cancel.  During completion, state may take on
     * transient values of COMPLETING (while outcome is being set) or
     * INTERRUPTING (only while interrupting the runner to satisfy a
     * cancel(true)). Transitions from these intermediate to final
     * states use cheaper ordered/lazy writes because values are unique
     * and cannot be further modified.
     *
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL(正常完成)
     * NEW -> COMPLETING -> EXCEPTIONAL(出现异常)
     * NEW -> CANCELLED(被取消)
     * NEW -> INTERRUPTING -> INTERRUPTED(被中断)
     */
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

这几个都是FutureTask类的运行状态,也可以被称作FutureTask类的生命周期,也是其能够实现cancel()方法的关键。

其中的生命走向主要与方法的执行顺序有关,不过多赘述。

其中比较重要的方法有set方法,用来设置返回值。

 protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

这个的判断我们先不管,我们可以看到,在一个判断成功之后,我们将会把v赋值给outcome,outcome便是get()所获取的值。

public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

get()方法先判断该对象处于哪个生命周期,然后如果还可以获取,那么调用report()方法,后面就很明显了,如果阿紫NORMAL阶段,那么就可以成功获取,否则抛出异常。

但是又有一个新的问题,我们并没有显式的在我们的业务处理中进行调用set()方法,我们一般只会重写call()方法,那么我们是怎么将其set()的呢?

我在这个类里面,寻找哪个地方调用了set()方法,但是我只在run()里面找到了调用。让我们一起来看看。

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
        	//获取线程对象
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
               		//获取线程返回
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

噢懂了,我们将会在run的时候获取实际Callable对象,然后得到call()方法返回的值,以此我们可以得到返回值。
FutureTask的其他留作以后补充。

线程池创建多线程

线程池,从字面上理解就是将所有线程放到一个池中,这样做的好处有如下。

  1. 有效控制线程数目,防止线程过多
  2. 提高线程的利用率,避免频繁销毁线程
  3. 线程使用方法更加灵活,而且可以有更灵活的拒绝线程方法。

我们可以看到,阿里开发手册中说,不要显式的创建线程,而是因为用线程池来创建,所以我们后面应该多使用线程池创建线程。
在这里插入图片描述
一个测试Demo

public class ExecutorTest {
    public static void main(String[] args) {
        BlockingQueue<Runnable> queue=new ArrayBlockingQueue<Runnable>(10);
        ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(2,10,60, TimeUnit.SECONDS,queue);
        for(int i=0;i<100;i++){
            poolExecutor.execute(new TdTest());
        }
        poolExecutor.shutdown();
    }
}

class TdTest implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName());
        }
    }
}

因为线程池的创建过程可以较为复杂,我写一篇单独的博客介绍线程池的具体底层。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值