本文主要讲述了进程和线程发展简史,对于JVM来讲的进程和线程又是什么,Thread的start()方法的原生调用发生了什么,从而理解start()方法和run()方法有什么不同,另外,还介绍了三种处理线程执行完成后的返回值的方法,其实FutureTask和线程池获取线程执行结束的返回值更加常用。另外,介绍了线程的六种状态,还有sleep和wait的区别,notify和notifyAll的区别,yield函数的作用,以及如何优雅的中断线程等问题。
进程是资源分配的最小单位,线程是CPU调度的最小单位 所有与进程相关的资源,都被记录在PCB中,进程是抢占处理机的调度单位;线程属于某个进程,共享其资源。线程只由堆栈寄存器、程序计数器和TCB组成。
线程不能看做独立应用,而进程可看做独立应用;进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径;线程没有独立的地址空间,多进程的程序比多线程程序健壮。进程的切换比线程的切换开销大。
Java对操作系统提供的功能进行封装,包括进程和线程。运行一个程序会产生一个进程,进程包含至少一个线程。每个进程对应一个JVM实例,多个线程共享JVM里的堆,Java采用单线程编程模型,程序会自动创建主线程,主线程可以创建子线程,原则上要后于子线程完成执行。
调用start()方法会创建一个新的子线程并启动,run()方法只是Thread的一个普通方法的调用。
javapublic class ThreadTest {
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()); //main
}).run();
new Thread(()->{
System.out.println(Thread.currentThread().getName()); //Thread-1
}).start();
}
}
start()点进去其实调用了start0(),而start0()是一个native方法,通过查看源码我们得知:
其实调用start0(),在底层就是创建了一个线程并且让新的线程执行这个run方法
1、主线程等待法
这个方法比较简单,但是需要自己实现循环等待的逻辑:
javapackage thread_study;
import java.util.concurrent.TimeUnit;
public class CycleWait implements Runnable {
private String value;
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = "Hello Thread";
}
public static void main(String[] args) throws InterruptedException {
CycleWait cycleWait = new CycleWait();
Thread thread = new Thread(cycleWait);
thread.start();
while(cycleWait.value == null){
TimeUnit.SECONDS.sleep(1);
}
System.out.println(cycleWait.value);
}
}
2、使用Thread类的join()阻塞当前线程以等待子线程处理完毕,能够实现比我们自己循环等到更为精细的控制
javapackage thread_study;
import java.util.concurrent.TimeUnit;
public class JoinWait implements Runnable {
private String value;
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = "Hello Thread";
}
public static void main(String[] args) throws InterruptedException {
JoinWait cycleWait = new JoinWait();
Thread thread = new Thread(cycleWait);
thread.start();
thread.join();
System.out.println(cycleWait.value);
}
}
3、通过Callable接口实现:通过FutureTask Or 线程池获取 在JDK5之前,线程是没有返回值的,通常为了能够获取线程的返回值而颇费周折
1、通过FutureTask来完成
MyCallable.java
javaimport java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
String value = "test";
System.out.println("Ready to work");
Thread.sleep(5000);
System.out.println("Task done");
return value;
}
}
FutureTaskDemo.java
javapackage thread_study;
import java.util.concurrent.FutureTask;
public class FutureTaskDemo {
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
if(!futureTask.isDone()){
System.out.println("Task is not finished, please wait!");
}
System.out.println("task return: " + futureTask.get());
}
}
2、通过线程池来完成
还是依旧沿用MyCallable.java的代码,下面是ThreePoolDemo.java
javapackage thread_study;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreePoolDemo {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
Future<String> futureTask = cachedThreadPool.submit(new MyCallable());
if(!futureTask.isDone()){
System.out.println("Task is not finished, please wait!");
}
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}finally {
cachedThreadPool.shutdown();
}
}
}
那么等待和阻塞式什么关系呢?不都是停下来吗?其实两者都表示线程当前暂停执行的状态,而两者的区别,基本可以理解为:进入 waiting 状态是线程主动的,而进入 blocked 状态是被动的。更进一步的说,进入 blocked 状态是在同步(synchronized)代码之外,而进入 waiting 状态是在同步代码之内(然后马上退出同步)。
sleep和wait最主要的本质区别:Thread.sleep只会让出CPU,不会导致锁行为的改变;Object.wait不仅让出CPU,还会释放已经占有的同步资源锁。这也就是wait必须写在synchronized里面的原因,因为我只有获取到锁了,我才可能释放锁嘛。
锁池EntryList:
假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池
等待池WaitSet:
假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程,不会去竞争该对象的锁。
notify和notifyAll的区别:
javapublic class YieldDemo {
public static void main(String[] args) {
Runnable runnable = () -> {
for (int i = 0; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
if(i == 5){
Thread.yield();
}
}
};
new Thread(runnable, "A").start();
new Thread(runnable, "B").start();
}
}
从运行结果来看,yield会让当前线程放弃CPU执行权,让给其他的线程,但是这也是不可控的,调用此方法可能没效果,比如下面这样:
需要注意的是yield不会使当前线程放弃持有的锁
已经被弃用的方法:stop(),这个方法不仅暴力,而且不安全, 可能使一些清理性的工作得不到完成。还可能对锁定的内容进行解锁,容易造成数据不同步的问题。
使用interrupt方法中断线程:调用interrupt(),即是通知线程应该中断了 ①如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。 ②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
需要被调用的线程配合中断 ①在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。 ②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
javapackage thread_study;
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Runnable task = ()->{
int i = 0;
try{
while (!Thread.currentThread().isInterrupted()){
Thread.sleep(100);
i++;
System.out.println(Thread.currentThread().getName() + " (" +
Thread.currentThread().getState()+") loop " + i);
}
}catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " (" +
Thread.currentThread().getState()+") catch InterruptedException");
}
};
Thread t1 = new Thread(task, "T1");
System.out.println(t1.getName() + " (" + t1.getState() + ") is new.");
t1.start();
System.out.println(t1.getName() + " (" + t1.getState() + ") is started.");
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted.");
Thread.sleep(300);
System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted now.");
}
}
本文作者:Tim
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!