线程的引入
在线程未提出之前,操作系统中都是以进程作为能拥有资源和独立运行的基本单位的。但是为了提高系统内程序并发执行的程度,减少程序在并发执行时所付出的时空开销,从而可进一步提高系统的吞吐量,人们引入了线程的概念。为什么?这是因为线程只拥有一些必要的基本资源,做到“轻装上阵”。
线程又被称为轻型进程,是CPU调度和分派的最小单位。一个进程中至少还有线程,比如在 Android 操作系统中,一个应用就是一个进程,而一个应用至少含有一个线程,即我们平常所说的主线程、UI 线程。而且线程可以共享进程资源,比如内存、打开的文件。
线程的状态
和进程一样,在各线程之间也存在着共享资源和相互合作的制约关系,致使线程在运行是也具有下述三种基本状态。
- 执行状态,表示线程正获得CPU而执行。
- 就绪状态,指线程已具备了各种执行条件,一旦获得CPU便可执行的状态。
- 阻塞状态,指线程在执行中因某事件而受阻,处于暂停执行时的状态。
Java中线程的创建(下文关于线程的阐述都是基于Java)
一、 继承 Thread 类创建线程类
1.创建一个 Thread 的子类,重写该类的 run() 方法,run() 方法就是线程要完成的任务。
2.创建 Thread 子类的对象
3.调用线程对象的 start() 方法来启动该线程。
class MyThread extends Thread{
@Override
public void run() {
System.out.println(getClass().getSimpleName() + " 正在执行");
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
二、 实现 Runnable 接口
1.定义 runnable 接口的实现类,并重写该接口的 run() 方法,把要执行逻辑写在 run() 里面
2.创建 runnable 实现类的对象,并把它提交给一个 Thread 的构造器创建一个 Thread 对象。
3.调用 Thread 对象的 start 方法来启动该线程。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(getClass().getSimpleName() + "正在执行");
}
}
public class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.start();
}
}
三、 通过Callable 和 Future 创建线程
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FuturaTask 类来包装 Callback 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象(本质上是 runnable)作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值,调用 get() 方法会阻塞线程。
class MyCallable implements Callable{
@Override
public String call() throws Exception {
System.out.println(getClass().getSimpleName() + "正在执行");
return "this is " + getClass().getSimpleName();
}
}
public class ThreadTest {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
try {
System.out.println("返回值:" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程的常用方法
线程让步 Thread.yield()
Thread.yield() 的调用时对线程调度器(Java线程机制的一部分,可以将 CPU 从一个线程转移到另一个线程中)的一种建议,它在声明:“我已经执行完生命周期中最重要的部分了,此刻正是切换给其他任务执行一段时间的大好时机”。这完全是选择性的,但是这里使用它在这些示例中产生更加有趣的输出:你更有可能会看到任务换进换出的证据。因为线程调度机制是非确定性的。
线程休眠 Thread.sleep()
class SleepTask implements Runnable{
private int count = 5;
@Override
public void run() {
try {
while (count-- > 0){
System.out.println(getClass().getSimpleName() +":count = "+ count);
//Thread.sleep(1000);
TimeUnit.MILLISECONDS.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SleepingTest {
public static void main(String[] args) {
new Thread(new SleepTask()).start();
}
}
可以看出,线程休眠只需调用 sleep() 方法,值得注意的是对 sleep() 的调用可以抛出 InterruptException 异常,它在 run 中被捕获,因为异常不能跨线程传播回 main(),所以你必须在本地处理所有在任务内部产生的异常。
线程加入 join(),实例方法,非静态方法
一个线程可以在其他线程之上调用 join() 方法,其效果是阻塞起来直到第二个线程结束才继续执行。如果某个线程在另一个线程 t 上调用 t.join(),此线程将被挂起,直到目标线程 t 结束才恢复(即 t.isAlive 返回为假)。
也可以在调用 join()时带上一个超时参数,这样如果目标线程在这段时间到期还没有结束的话,join()方法总能返回。
我们首先看下没有加 join()方法的例子:
class JoinRunnable implements Runnable{
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) {
Thread.sleep(1000);
System.out.println(getClass().getSimpleName() + " i="+ i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class JoinTest {
public static void main(String[] args) {
Thread t1 = new Thread(new JoinRunnable());
t1.start();
/* try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println("This is main thread");
}
}
输出结果:
不出所料,我们创建了一个子线程执行操作,并不会影响主线程的继续执行。但是当我们把上面代码中的注释去掉,输出结果就大不一样,如下图所示:
是的,主线程阻塞在那里直到子线程执行完才继续执行。至于加一个超时参数也就很好理解了,比如超时参数的时长是 1s ,那就是主线程会阻塞在那里等 1s 后就继续执行了。同理,把主线程替换成其他子线程也是同样的效果。
参考资料
[1] (美)艾克尔(Eckel,B.).Java编程思想[M]. 北京 : 机械工业出版社 , 2007.6.
[2]Java 创建线程的3种方式