线程创建的3种方法:
1、继承Thread类并重写run方法
Thread类方法:
Thread Thread.currentThread() :获得当前线程的引用。获得当前线程后对其进行操作。
Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() :返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
int Thread.activeCount():当前线程所在线程组中活动线程的数目。
void dumpStack() :将当前线程的堆栈跟踪打印至标准错误流。
int enumerate(Thread[] tarray) :将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
Map<Thread,StackTraceElement[]> getAllStackTraces() :返回所有活动线程的堆栈跟踪的一个映射。
boolean holdsLock(Object obj) :当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
boolean interrupted() :测试当前线程是否已经中断。
void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) :设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
void sleep(long millis) :休眠指定时间
void sleep(long millis, int nanos) :休眠指定时间
void yield() :暂停当前正在执行的线程对象,并执行其他线程。意义不太大
void checkAccess() :判定当前运行的线程是否有权修改该线程。
ClassLoader getContextClassLoader() :返回该线程的上下文 ClassLoader。
long getId() :返回该线程的标识符。
String getName() :返回该线程的名称。
int getPriority() :返回线程的优先级。
StackTraceElement[] getStackTrace() :返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
Thread.State getState() :返回该线程的状态。
ThreadGroup getThreadGroup() :返回该线程所属的线程组。
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() :返回该线程由于未捕获到异常而突然终止时调用的处理程序。
void interrupt() :中断线程。
boolean isAlive() :测试线程是否处于活动状态。
boolean isDaemon() :测试该线程是否为守护线程。
boolean isInterrupted():测试线程是否已经中断。
void join() :等待该线程终止。
void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。
void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
void run() :线程启动后执行的方法。
void setContextClassLoader(ClassLoader cl) :设置该线程的上下文 ClassLoader。
void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。
void start():使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
String toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
interrupt() 并不能真正的中断线程,只是起到标记作用,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就需要这样做:
- 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
- 在调用阻塞方法时正确处理InterruptedException异常。(例如:catch异常后就结束线程。)
实现案例:
public class ExtendThreadTest extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + " : run " + i);
}
}
public static void main(String[] args) {
Thread thread1 = new ExtendThreadTest();
Thread thread2 = new ExtendThreadTest();
thread1.start();
thread2.start();
for (int i = 0; i < 3; i++) {
System.out.println("main : run " + i);
}
}
}
2、实现Runable接口并重写run方法。Runable接口只有一个run方法。
public class ImplementsRunnableTest implements Runnable {
private int tick = 10;
public void run() {
while (true) {
if(tick > 0){
System.out.println(Thread.currentThread().getName() + "..." + tick--);
}
}
}
public static void main(String[] args) {
ImplementsRunnableTest t = new ImplementsRunnableTest();
new Thread(t).start();
new Thread(t).start();
}
}
3、通过实现Callable和Future接口创建
public interface Callable<V> {
V call() throws Exception; //类型参数V即为异步方法call的返回值类型。
}
Future可以对具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成以及获取结果。可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。get方法获取到call方法返回的数据。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled(); //表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
boolean isDone(); //任务是否已经完成,若任务完成,则返回true
V get() throws InterruptedException, ExecutionException; //获取执行结果,这个方法会阻塞
V get(long timeout, TimeUnit unit) //在指定时间内,还没获取到结果,就直接返回null
throws InterruptedException, ExecutionException, TimeoutException;
}
java.util.concurrent包已经有自带的Future实现类FutureTask<V>,直接用就可以了,FutureTask实现了RunnableFuture<V>接口,RunnableFuture<V>又实现了Runnable接口和Future接口。
实现案例:
public class CallableTest implements Callable<String> {
//重写Callable接口方法
public String call() throws Exception {
return Thread.currentThread().getName();
}
public static void main(String[] args) {
// 创建callable实现类实例
CallableTest t = new CallableTest();
// 使用FutureTask类来包装Callable对象
FutureTask<String> ft1 = new FutureTask<String>(t);
FutureTask<String> ft2 = new FutureTask<String>(t);
new Thread(ft1).start();
new Thread(ft2).start();
try {
System.out.println(ft1.get());
System.out.println(ft2.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
对比
- 通过继承Thread类的线程,一个类只能继承一个父类,使得该方式具有一定的局限性。
- 实现接口的类,再结合Thread类来实现的线程,只是实现了接口类,还可以继承其他类,相对较灵活,且call()方法是一个有返回值而且抛异常的方法。实现接口创建的线程可以放入线程池来管理,而继承Thread类创建的线程不可以放入线程池。
- 三种方式最终都是通过调用start()方法来实现多线程。切记不能直接调用Thread类或Runnable对象的run()方法,因为直接调用run()方法,只会执行同一个线程中的任务,而不会启动新线程。调用start()方法将会创建一个执行run()方法的线程。
start()方法用了synchronized关键字修饰且内部调用start0()
//native方法,JVM创建并启动线程,并调用run方法
private native void start0();