多线程基础

多线程实现-Thread和Runnable

通常使用如下代码启动一个新的线程:

private void startNewThread1() {
    new Thread() {
        @Override
        public void run() {
            //耗时操作,此时target为空
        }
    }.start();
}

private void startNewThread2() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            //耗时操作,此时target不为空
        }
    }).start();
}

Thread也是一个Runnable,它实现了Runnable接口,在Thread类中有一个Runnable类型的target字段,代表要被执行在这个子线程中的任务:

public class Thread implements Runnable {
    private Runnable target; //线程所属的ThreadGroup
    private ThreadGroup group; //要执行的目标任务

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                        long stackSize, AccessControlContext acc,
                        boolean inheritThreadLocals) {
        ...
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup(); //group参数为null时获取当前线程的线程组
            }
        }
        ...
        this.group = g;
        ...
        this.target = target;
        ...
    }

    public synchronized void start() {
        ...
        group.add(this); //将thread对象添加到添加到ThreadGroup中
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            ...
        }
    }
}

实际上最终被线程执行的任务是Runnable,Thread只是对Runnable的包装,并且通过一些状态对Thread进行管理与调度

Runnable接口定义了可执行的任务,它有一个无返回值的run()函数:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

当启动一个线程时,如果Thread的target不为空,则会在子线程中执行这个target的run函数,否则虚拟机就会执行该线程自身的run函数

线程的wait、sleep、join和yield函数

wait notify函数

wait与nofity机制,通常用于等待机制的实现,当条件未满足时调用wait进入等待状态,一旦条件满足,调用notify或notifyAll唤醒等待的线程继续执行

下面来看下wait和notify,notifyAll的运用:

    private static Object sLockObject = new Object();

    static void waitAndNotifyAll() {
        System.out.println("主线程  运行");
        Thread thread = new WaitThread();
        thread.start();
        long startTime = System.currentTimeMillis();
        try {
            synchronized (sLockObject) {
                System.out.println("主线程  等待");
                sLockObject.wait();
            }
        } catch (Exception e) {
        }
        System.out.println("主线程  继续 --> 等待耗时 : " + (System.currentTimeMillis() - startTime) + " ms");
    }

    static class WaitThread extends Thread {
        @Override
        public void run() {
            try {
                synchronized (sLockObject) {
                    Thread.sleep(3000);
                    sLockObject.notifyAll();
                }
            } catch (Exception e) {
            }
        }
    }

waitAndNotifyAll函数中,先创建子线程并休眠三秒,主线程调用wait函数后进入等待状态,子线程休眠三秒结束后调用notifyAll函数,唤醒正在等待中的主线程

join函数

join函数的原始解释为"Blocks the current Thread(Thread.currentThread()) until the receiver finishes its execution and dies",意思就是阻塞当前调用join函数时所在的线程,直到接收完毕后再继续

    static void joinDemo() {
        Worker worker1 = new Worker("work-1");
        Worker worker2 = new Worker("work-2");

        worker1.start();
        System.out.println("启动线程1");
        try {
            worker1.join();
            System.out.println("启动线程2");
            worker2.start();
            worker2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程继续执行");
    }

    static class Worker extends Thread {

        public Worker(String name) {
            super(name);
        }

        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("work in " + getName());
        }
    }

在join函数中,首先创建两个子线程,启动worker1后调用worker1的join函数,此时主线程进入阻塞状态直到worker1执行完毕后继续执行,因为Worker的run函数会睡眠两秒,因此每次调用join函数都会阻塞两秒

yield函数

yield函数官方解释为"Causes the calling Thread to yield execution time to another Thread that is ready to run",意为使调用该函数的线程让出执行时间给其他已就绪状态的线程,由于线程的执行是有时间片的,每个线程轮流占用CPU固定的时间,执行周期到了之后就让处执行权给其他线程,而yield的功能就是主动让出线程的执行权给其他线程,其他线程能否得到优先执行就得看各个线程的状态了

    static class YieldThread extends Thread {
        public YieldThread(String name) {
            super(name);
        }

        public synchronized void run() {
            for (int i = 0; i < MAX; i++) {
                System.out.printf("%s ,优先级为 : %d ----> %d\n", this.getName(), this.getPriority(), i);
                // i整除4时,调用yield
                if (i == 2) {
                    Thread.yield();
                }
            }
        }
    }

    static void yield() {
        YieldThread t1 = new YieldThread("thread-1");
        YieldThread t2 = new YieldThread("thread-2");
        YieldThread t2 = new YieldThread("thread-2");
        YieldThread t2 = new YieldThread("thread-2");
        t1.start();
        t2.start();
    }

可以看到YieldThread内有一个执行5次的循环,当循环到i=2时调用yield函数,理论上在i=2时线程会让出执行权让其他线程优先执行,但有时并不一定能产生效果,因为它仅仅可以使一个线程从running状态变成Runnable状态,而不是wait或者blocked状态,并且不能保证正在执行的线程立刻变成Runnable状态

与多线程相关的方法 Runnable、Callable、Future和FutureTask

Runnable:既能运用在Thread中又能运用在线程池中

Callable、Future、FutureTask:只能运用在线程池中

Callable

Callable与Runnable功能大致类似,不同的是Callable是一个泛型接口,它有一个泛型参数V,该接口中有一个返回值(类型为V)的call()函数

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Future

Future为线程池制定了一个可管理的任务标准,提供了对Runnable或Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作,分别对应cancel、isDone、get(阻塞直到任务返回结果)、set(在FutureTesk中定义)函数

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;
}

FutureTask

FutureTask是Future的实现类,它实现了RunnableFuture<V>,而RunnableFuture实现了Runnable又实现了Future<V>这两个接口,因此FutureTask既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行,并且还可以直接通过get()函数获取执行结果,该函数会阻塞直到结果返回

public class FutureTask<V> implements RunnableFuture<V> {
    ...
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

FutureTask会像Thread包装Runnable那样对Runnable和Callable<V>进行包装,Runnbale与Callable由构造函数注入:

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

如果注入的是Runnable则会被Executors.callable()函数转换为Callable类型,因此FutureTask最终都是执行Callable类型的任务

Runnable、Callable、FutureTask运用

    // 线程池
    static ExecutorService mExecutor = Executors.newSingleThreadExecutor();

    private static void futureWithRunnable() throws InterruptedException, ExecutionException {
        // 向线程池提交runnable则没有返回值, future没有数据
        Future<?> result = mExecutor.submit(new Runnable() {

            @Override
            public void run() {
                fibc(20);
            }
        });

        System.out.println("future result from runnable : " + result.get());
    }

    private static void futureWithCallable() throws InterruptedException, ExecutionException {
        //向线程池提交Callable, 有返回值, future中能够获取返回值
        Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return fibc(20);
            }
        });

        System.out.println("future result from callable : "
                + result2.get());
    }

    private static void futureTask() throws InterruptedException, ExecutionException {
        FutureTask<Integer> futureTask = new FutureTask<Integer>(
                new Callable<Integer>() {
                    @Override
                    public Integer call() throws Exception {
                        return fibc(20);
                    }
                });
        // 提交futureTask
        mExecutor.submit(futureTask);
        System.out.println("future result from futureTask : "
                + futureTask.get());
    }

    // 效率底下的斐波那契数列, 耗时的操作
    private static int fibc(int num) {
        if (num == 0) {
            return 0;
        }
        if (num == 1) {
            return 1;
        }
        return fibc(num - 1) + fibc(num - 2);
    }
10-07 12:48