1、线程创建
创建线程有三种方式
继承Thread类
①定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
②创建Thread子类的实例,即创建了线程对象。
③调用线程对象的start()方法来启动该线程。实现Runnable接口
①定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
②创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
③调用线程对象的start()方法来启动该线程。实现Callable接口(jdk1.5新增,在java.util.concurrent包)
①创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
②创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
③使用FutureTask对象作为Thread对象的target创建并启动新线程。
④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
示例代码
public class ThreadLearning {
//------------------------继承Thread类-----------------------
//匿名内部类的形式
@org.junit.Test
public void generateThread_1() {
Thread thread = new Thread() {
@Override
public void run() {
//do something
System.out.println("1");
}
};
thread.start();
}
//正常形式
@org.junit.Test
public void generateThread_2() {
Thread1 thread1 = new Thread1();
thread1.start();
}
class Thread1 extends Thread{
@Override
public void run() {
//do something
System.out.println("2");
}
}
//------------------------实现Runnable接口-----------------------
//匿名内部类的形式
@org.junit.Test
public void generateThread_3() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//do something
System.out.println("1");
}
});
thread.start();
}
//正常形式
@org.junit.Test
public void generateThread_4() {
Thread2 thread2 = new Thread2();
Thread thread = new Thread(thread2);
thread.start();
}
class Thread2 implements Runnable{
@Override
public void run() {
//do something
System.out.println("2");
}
}
//------------------------实现Callable接口-----------------------
@org.junit.Test
public void generateThread_5() {
Thread3 thread3 = new Thread3();
FutureTask<String> futureTask = new FutureTask<String>(thread3);
new Thread(futureTask).start();
try {
String s = futureTask.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
class Thread3 implements Callable<String>{
@Override
public String call() throws Exception {
return "我是实现Callable接口的线程类";
}
}
}
2、线程状态分类
/**
* 一个线程在给定的某个时刻,只能处在下面的6中状态中的一种。
*
* 这些状态是相对于虚拟机而言的,并不能反映出任何操作系统中线程状态。
*
*/
public enum State {
/**
*
* 新建状态
*
* 创建后还没有启动的线程处在NEW的状态;
*
* 而启动线程只有start()方法。也就是说还未调用start()方法的线程处在NEW的状态
*/
NEW,
/**
*
* 运行状态
*
* 处在此状态的线程有可能正在运行,也有可能正在等待CPU为它分配执行时间
* 对于虚拟机而言,处在RUNNABLE状态的线程就是正在执行。
*
*/
RUNNABLE,
/**
*
* 阻塞状态
*
* 处在阻塞状态的线程正在等待一个锁
*
* 在程序等待进入同步区域的时候,线程将进入这种状态。
*
*
*/
BLOCKED,
/**
*
* 无限期等待状态
*
* 线程进入无限期等待状态的原因是调用了下面三种方法之一:
* ①没有设置Timeout参数的Object.wait()方法
* ②没有设置Timeout参数的Thread.join()方法
* ③LockSupport.park()方法
*
*
*/
WAITING,
/**
*
* 限期等待状态
*
* 线程进入限期等待状态的原因是调用了下面五种方法之一:
*
* ①设置Timeout参数的Object.wait()方法
* ②设置Timeout参数的Thread.join()方法
* ③LockSupport.parkNanos()方法
* ④LockSupport.parkUntil
* ⑤Thread.sleep()方法
*
*/
TIMED_WAITING,
/**
*
* 结束状态
*
* 线程已经完成执行。
*/
TERMINATED;
}
3、状态切换图
4、各个方法的解释
先看属于线程Thread的方法
public static native void yield();
该方法向线程调度器提示,当前线程(调用该方法的线程对象代表的线程)愿意让出处理器的使用权,但是线程调度器可能会忽视该提示。
很少有适合使用该方法的地方。它可能在调试或者测试的时候,帮助重现由于竞争条件出现的bug。它还可能在设计并发控制结构的时候提供帮助,比如java.util.concurrent.locks包中的一些类。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException;
该方法会让当前线程(调用该方法的线程对象代表的线程)睡眠指定的一段时间(暂时停止执行),但当前线程不会失去锁的拥有权(还会继续占用锁)。
上述两个方法只是指定的睡眠时间精度不同。
public synchronized void start()
该方法会让当前线程(调用该方法的线程对象代表的线程)开始执行。JVM虚拟机会调用该线程的run()方法。对一个线程只能start()一次。
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
public final void join() throws InterruptedException
该方法会让当前线程(调用该方法的线程,注意,这里并不是thread.join()
中thread所表示的线程,而是方法thread.join()
所在的线程)等待指定时间(如果为0,表示一直等到thread执行完),让join()方法的所属线程thread执行。
join()方法内部是调用wait()方法实现的。
再看Object中的方法
public final native void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void wait() throws InterruptedException
该方法会让当前线程(该方法调用所在的线程)进入等待状态,直到被该对象(wait()
方法的this
对象)的notify()
或者notifyAll()
唤醒,或者等待时间结束(wait(long timeout)
中的timeout
结束)。
当前线程必须拥有该对象的monitor。
当前线程会将自己放置在等待集中(该对象的等待集),并放弃所有对该对象的同步声明。该线程就会不可用并保持休眠,直到发生下面的4种事件中的任意一件:
①其他线程触发了该对象的notify方法,那么该线程就可能会被唤醒(并不一定会唤醒,可能很多线程都wait该对象);
②其他线程触发了notifyAll方法;
③被其他线程中断(interrupt()方法)
④设定的等待时间已到。如果是wait(0)
或者wait()
则会一直等待直到唤醒。
上述事件之一发生后,该线程就会从该对象的等待集中移除,重新加入线程调度。此后,它就会和其他线程一样去争夺该对象的锁,一旦获取了该对象的控制权,所有该对象的同步声明就会重新加载。
正确使用wait()的方式如下:(等待应该总是发生在轮询中:waits should always occur in loops
)
synchronized (obj) {
while (condition does not hold)
obj.wait(timeout);
... // Perform action appropriate to condition
}
如果当前线程在等待之前或者等待期间被其他线程打断(Thread.interrupt()),就会抛出InterruptedException异常。
该方法只应该被拥有对象monitor的线程调用。
public final native void notify();
public final native void notifyAll();
notify()
唤醒等待该对象monitor的单个线程。如果多个线程都在等待该对象monitor,那么被唤醒的线程是不固定的、任意的。被唤醒的线程并不会立即执行,而是等到当前线程放弃了该对象的锁。被唤醒的线程会和其他线程去竞争对该对象的同步操作。也就是说,被唤醒的线程仅仅是被唤醒去参与竞争,并不是唤醒了就开始执行。
该方法只应该被该对象的monitor的所有者线程调用。一个线程成为该对象的monitor的所有者有以下三种途径:
①执行该对象的同步的实例方法
②执行一个同步块,并且该同步了该对象
③对Class类型的对象,执行该类的一个同步的静态方法
为什么要使用多线程
①充分利用计算机CPU资源
假如CPU为4核,使用一个线程,那这个线程只会在一个CPU核心工作,其他三个CPU核心就浪费了。
②避免阻塞
在执行一件耗时的任务(TASK)的时候(比如下载文件),假如只有一个线程,就只能等任务执行完成才能执行另外的任务。如果多线程,只需要让一个线程去执行下载任务,另一个线程继续处理你其他任务,比如继续刷网页。
③并行处理任务
比如有很多任务需要执行(比如10个人同时要下载文件),如果只有一个线程,只能逐一去下载,排在第一个的人很高兴,但是后面的人需要等到前面的人下载完才能轮到自己,会很不乐意。使用多线程(假如有10个线程),每个线程对应一个下载任务,CPU就会同时(只是看上去同时)执行这些线程,这样每个人都可以以基本相同的速度下载完文件。
一个实际的应用就是B/S架构的应用,并行处理大量请求。
④就想到这么多。。。
内容参考
Java多线程学习(二)---线程创建方式
jdk1.8
《深入理解Java虚拟机》
《Java并发编程的艺术》