Java 多线程
线程创建,
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示:
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
4)使用线程池例如用Executor框架
在此重点介绍第三种。
使用Callable和Future创建线程
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
-
call()方法可以有返回值
-
call()方法可以声明抛出异常
/*
返回结果并可能抛出异常的任务。
实现者定义了一个不带参数的方法,称为call。
Callable接口与Runnable接口类似,两者都是为其实例可能由另一个线程执行的类设计的。 然而,Runnable不返回结果,也不能抛出检查异常。
Executors类包含将其他常见形式转换为Callable类的实用方法。
自: 1.5
参见: Executor
作者: Doug Lea
类型参数: <V> -方法调用的结果类型
*/
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。
在Future接口里定义了几个公共方法来控制它关联的Callable任务。
/*
Future表示异步计算的结果。
提供了一些方法来检查计算是否完成、等待其完成以及检索计算的结果。
结果只能在计算完成时使用get方法进行检索,必要时阻塞直到准备就绪。
取消由cancel方法执行。
提供了其他方法来确定任务是正常完成还是被取消。
一旦计算完成,计算就不能取消。
如果为了可取消性而使用Future但不提供可用的结果,可以声明Future<?>和返回null作为底层任务的结果。
示例用法(注意,下面的类都是虚构的。)
interface ArchiveSearcher { String search(String target); }
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target)
throws InterruptedException {
Future<String> future
= executor.submit(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}
FutureTask类是实现Runnable的Future的实现,因此可以由Executor执行。 例如,上述带有submit的结构可被替换为:
FutureTask<String> future =
new FutureTask<String>(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
executor.execute(future);
内存一致性影响: 异步计算所采取的动作发生在另一个线程中对应的Future.get()后面的动作之前。
Since: 1.5
See Also: FutureTask, Executor
Author: Doug Lea
Type parameters: <V> – 这个Future的get方法返回的结果类型
*/
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;
}
- boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务
- V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
- V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
- boolean isDone():若Callable任务完成,返回True
- boolean isCancelled():如果在Callable任务正常完成前被取消,返回True
-
创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
-
使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
-
使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
-
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
实例:
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
// 等凉菜
Callable ca1 = new Callable(){
@Override
public String call() throws Exception {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "凉菜准备完毕";
}
};
FutureTask<String> ft1 = new FutureTask<String>(ca1);
new Thread(ft1).start();
// 等包子 -- 必须要等待返回的结果,所以要调用join方法
Callable ca2 = new Callable(){
@Override
public Object call() throws Exception {
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "包子准备完毕";
}
};
FutureTask<String> ft2 = new FutureTask<String>(ca2);
new Thread(ft2).start();
System.out.println(ft1.get());
System.out.println(ft2.get());
long end = System.currentTimeMillis();
System.out.println("准备完毕时间:"+(end-start));
}
Callable接口和Runnable接口
一、源代码角度分析两接口间的区别
首先给出他们俩的源代码:
@FunctionalInterface
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;
}
以及:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
由他们本身的接口定义我们就能够看出它们的区别:
- 如上面代码所示,callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值
- call方法可以抛出异常,但是run方法不行
- 因为runnable是java1.1就有了,所以他不存在返回值,后期在java1.5进行了优化,就出现了callable,就有了返回值和抛异常
- callable和runnable都可以应用于executors。而thread类只支持runnable
它们的相同点:
- 两者都是接口
- 两者都需要调用Thread.start启动线程
二、从使用场景来分析两接口间区别:
2.1 Runnnable接口的使用场景
1)传递给线程对象执行任务;
2)作为线程池方法execute()
的入口参数来执行任务;
具体的实现又可以细分,具体如下面代码块所示:
public class TheWayOfUsingRunnable {
public static void main(String[] args) {
//1)lambda表达式形式传递给线程构造器
Runnable runnable1 = () -> {
System.out.println("我是使用lambda表达式实现的Runnable对象实现 version1");
};
Thread thread1 = new Thread(runnable1);
thread1.start();
Thread thread1_1 = new Thread(() -> {
System.out.println("我是使用lambda表达式实现的Runnable对象实现 version2");
});
thread1_1.start();
//2)匿名内部类的方式实现Runnable对象的传入Thread构造器
new Thread(
new Runnable() {
@Override
public void run() {
System.out.println("我是使用匿名内部类的方式实现的Runnable对象实现");
}
}
).start();
//3)使用实现了Runnable接口的对象实例传入Thread构造器
RunnableTask runnableTask = new RunnableTask();
new Thread(runnableTask).start();
//4)Runnable类以及其子类作为线程任务提交给线程池,通过线程池维护的工作者线程来执行。
Runnable runnable2 = () -> {
System.out.println("我是使用lambda表达式实现的Runnable对象实现 不同的是:用于线程池的实现");
};
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(runnable2);
executor.shutdown();
}
/**RunnableTask
* Runnable接口子类的实现类:RunnableTask
*/
private static class RunnableTask implements Runnable{
@Override
public void run() {
System.out.println("实现了Runnable接口的子类的对象实现");
}
//额外写一个方法:get
public String get(){
return "I am Fisherman.";
}
}
}
2.2 Callable接口的使用场景
callable对象实例可以作为线程池的submit()
方法入口参数
public class TheWayOfUsingCallable {
public static void main(String[] args) {
//callable对象实例可以作为线程池的submit()方法入口参数
ExecutorService executor = Executors.newCachedThreadPool();
IntegerCallableTask integerCallableTask = new IntegerCallableTask();
Future<Integer> future = executor.submit(integerCallableTask);
executor.shutdown();
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class IntegerCallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
Thread.sleep(1000);
for (int i = 0; i < 101; i++) {
sum += i;
}
return sum;
}
}
从上述代码块的执行结果来看,main线程会一直等到执行完call()
方法中的所有代码才会继续执行main线程中接下来的代码(等待发生在方法:future.get()
)。但是Runnable接口和Callable接口在线程池上的应用实际上是十分类似的。
wait和sleep
-
这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。
sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
-
sleep方法没有释放锁,而wait方法释放了锁
sleep不出让系统资源;
wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。
一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。
sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
-
使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
-
sleep 和wait方法都会抛出一个异常 InterruptedException
源码如下:
public static native void sleep(long millis) throws InterruptedException;
public final void wait() throws InterruptedException {wait(0);}
总结
- 两者都可以暂停线程的执行。
- 对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
- Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
- sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。
- 在调用sleep()方法的过程中,线程不会释放对象锁。
- 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。线程不会自动苏醒。
run和star
- 调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
- 一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。
测试代码
测试 run() 方法
public class TestThreadRunStart {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
//休眠3秒
try {
Thread.sleep(3000);
System.out.println("休眠3秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread running...");
}
};
testRun(t);
// testStart(t);
}
private static void testRun(Thread t) {
t.run();
//休眠1秒
try {
Thread.sleep(1000);
System.out.println("休眠1秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void testStart(Thread t) {
t.start();
//休眠1秒
try {
Thread.sleep(1000);
System.out.println("休眠1秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
休眠3秒
Thread running...
休眠1秒
测试 start() 方法
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
//休眠3秒
try {
Thread.sleep(3000);
System.out.println("休眠3秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread running...");
}
};
// testRun(t);
testStart(t);
}
休眠1秒
休眠3秒
Thread running...
线程相关方法
-
start()与run()
start() 启动线程并执行相应的run()方法
run() 子线程要执行的代码放入run()方法 -
getName()和setName()
getName() 获取此线程的名字
setName() 设置此线程的名字 -
join()和yield()
yield() 暂停当前方法,释放自己拥有的CPU,线程进入就绪状态。
它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;
但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;
也有可能是当前线程又进入到“运行状态”继续运行
yield方法也不会释放同步锁
join() 加入线程(当前执行的线程是A线程,调用join()方法得是B线程) 当前线程阻塞
执行B线程 B执行结束之后A线程才能执行
放弃当前线程的执行
-
interrupt()中断线程
由运行状态到死亡状态
-
中断线程操作实质上是修改了一下中断标示位为true
-
当前线程正在运行,仅仅修改标示位,不在做其他的事
-
当前线程正在阻塞,修改标识位,如果是join,sleep,yield,则会抛出Interrup异常,修改标示位为false
方法源码:
/* 中断线程。 除非当前线程中断自身(这总是被允许的),否则将调用该线程的checkAccess方法,这可能导致抛出SecurityException。 如果这个线程被阻塞的调用等(),等待(长),或等待(长,int)方法的对象类,或者加入(),加入(长),加入(长,int),睡眠(长),或睡眠(长,int),这个类的方法,那么它的中断状态将被清除,它将接收一个InterruptedException。 如果这个线程在一个InterruptibleChannel的I/O操作中被阻塞,那么该通道将被关闭,线程的中断状态将被设置,并且该线程将收到一个java.nio.channels.ClosedByInterruptException异常。 如果这个线程在java.nio.channels.Selector中被阻塞,那么线程的中断状态将被设置,并且它将立即从选择操作中返回,可能带有一个非零值,就像调用了选择器的唤醒方法一样。 如果前面的条件都不满足,那么这个线程的中断状态将被设置。 中断一个非活动的线程不需要有任何影响。 抛出: 如果当前线程不能修改这个线程 */ public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
-
-
setDaemon()设置守护线程
setDaemon(true) 设置当前线程为守护线程
- 守护线程和用户线程最主要区别是守护线程脱离终端
- 当用户 线程不在,只存在守护线程 ,,JVM就不在了,JVM不在了之后守护线程也就不在了
/* 将该线程标记为守护线程或用户线程。 当运行的线程都是守护线程时,Java虚拟机将退出。 必须在线程启动之前调用此方法。 参数: On -如果为true,则将该线程标记为守护线程 抛出: IllegalThreadStateException -如果这个线程是活动的 SecurityException -如果checkAccess确定当前线程不能修改该线程 */ public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; }
-
getPriority()
getPriority() 获取线程优先级 1~10 默认值为5 优先级越高被优先调用的频率越高