多线程基础
多线程实现-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 | 当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的机锁,使得其他线程可以访问,用户可以用notify、notifyAll或者指定睡眠时间来唤醒当前等待池中的线程,注意:wait、notify、notifyAll必须放在synchronized block中,否则会抛出异常 |
sleep | 该函数是Thread的静态函数,作用是使当前线程进入睡眠状态,因为其是静态方法,所以不会改变对象机锁,即使睡眠也持有对象锁,其他对象无法访问这个对象 |
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);
}