思考:Java中实现线程有几种方式
Java中实现线程的方式本质上只有一种:由于java中的线程依赖于操作系统线程的创建,因此,不管使用什么方式实现线程,最后都是通过操作系统的pthread_create方法来创建
但可以通过不同的场景对任务有不同的使用方式
// 创建线程对象
Thread t = new Thread() {
public void run() {
// 要执行的任务
}
};
// 启动线程
t.start();
把【线程】和【任务】(要执行的代码)分开
Runnable runnable = new Runnable() {
public void run(){
// 要执行的任务
}
};
// 创建线程对象
Thread t = new Thread(runnable);
// 启动线程
t.start();
class CallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt();
}
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());
new Thread(() ‐> System.out.println(Thread.currentThread().getName())).start();
本质上Java中实现线程只有一种方式,都是通过new Thread()创建线程,调用Thread#start启动线程最终都会调用Thread#run方法
思考:Java线程执行为什么不能直接调用run()方法,而要调用start()方法
由于Java的线程创建需要依赖于操作系统,因此直接调用run()方法其实只是简单的方法调用,而调用start()方法是告诉操作系统按照我提供的java线程对象创建一个线程A,并将它加入就绪列表,参与cpu调度,如果线程A获取到cpu的执行权之后,就会通过jvm调用java线程对象的run方法,完成对Java线程对象run方法的异步调用
对于线程创建的详情,查看Thread#start()源码分析
基于操作系统原生线程模型来实现。Sun JDK,它的Windows版本和Linux版本都使用一对一的线程模型实现,一条Java线程就映射到一条轻量级进程之中
它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式分两种,分别是协同式线程调度和抢占式线程调度
线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里
每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞
希望系统能给某些线程多分配一些时间,给一些线程少分配一些时间,可以通过设置线程优先级来完成。Java语言一共10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两线程同时处于ready状态时,优先级越高的线程越容易被系统选择执行。但优先级并不是很靠谱,因为Java线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统
例如:
/**
* 一个简单卖票程序:多个窗口卖票
*/
public class SellTicketDemo implements Runnable {
//车票
private int ticket;
public SellTicketDemo() {
this.ticket = 1000;
}
@Override
public void run() {
while (ticket > 0) {
synchronized (this) {
if (ticket > 0) {
try {
// 线程进入暂时的休眠
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取到当前正在执行的程序的名称,打印余票
System.out.println(Thread.currentThread().getName() + ":正在执行操作,余票:" + ticket--);
}
}
Thread.yield();
}
}
public static void main(String[] args) {
SellTicketDemo demo = new SellTicketDemo();
Thread thread1 = new Thread(demo, "thread1");
Thread thread2 = new Thread(demo, "thread2");
Thread thread3 = new Thread(demo, "thread3");
Thread thread4 = new Thread(demo, "thread4");
//priority优先级默认是5,最低1,最高10
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
thread3.setPriority(Thread.MIN_PRIORITY);
thread4.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
Java 语言中线程共有六种状态,分别是:
在操作系统层面,Java 线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态,即前面我们提到的休眠状态。也就是说只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权
从JavaThread的角度,JVM定义了一些针对Java Thread对象的状态(jvm.h)
从OSThread的角度,JVM还定义了一些线程状态给外部使用,比如用jstack输出的线程堆栈信息中线程的状态(osThread.hpp)
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景
例如:
public class ThreadJoinDemo {
public static void main(String[] sure) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("t begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t finished");
});
long start = System.currentTimeMillis();
t.start();
//主线程等待线程t执行完成
t.join();
System.out.println("执行时间:" + (System.currentTimeMillis() - start));
System.out.println("Main finished");
}
}
思考:如何正确优雅的停止线程
Java线程类型提供了停止线程的方法stop(),但是stop会有任务未执行完,强行停止的问题,一次,我们会借助中断标志位来进行优雅的停止线程
stop()方法已经被jdk废弃,原因就是stop()方法太过于暴力,强行把执行到一半的线程终止
public class ThreadStopDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "获取锁");
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "执行完成");
}
});
thread.start();
Thread.sleep(2000);
// 停止thread,并释放锁
thread.stop();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "等待获取锁");
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "获取锁");
}
}
}).start();
}
}
stop会释放对象锁,可能会造成数据不一致
Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止
中断机制简单应用示例:
/**
* 中断机制
*/
public class ThreadInterruptTest {
static int i = 0;
public static void main(String[] args) {
System.out.println("begin");
Thread t1 = new Thread(() -> {
do {
i++;
System.out.println(i);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.interrupted() 清除中断标志位
//Thread.currentThread().isInterrupted() 不会清除中断标志位
if (Thread.interrupted()) {
System.out.println("=========");
}
} while (i != 10);
});
t1.start();
//不会停止线程t1,只会设置一个中断标志位 flag=true
t1.interrupt();
}
}
对于借助中断标志位来中断线程的方式如下:
public class ThreadStopDemo2 implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置线程中断状态为true
Thread.currentThread().interrupt();
}
}
System.out.println("线程停止: stop thread");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ThreadStopDemo2());
thread.start();
Thread.sleep(5);
thread.interrupt();
}
}
注意:使用中断机制时一定要注意是否存在中断标志位被清除的情况
针对上面的代码示例,我们可以看出,一般阻塞的线程都是需要捕获中断异常,一旦修改当前线程的中断标志位,当前线程就会收到中断信号,随即抛出中断异常,而我们的中断业务就需要在异常中进行处理
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信
示例如下:
public class VolatileDemo {
private static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (flag) {
System.out.println("run on");
flag = false;
}
}
}).start();
new Thread(() -> {
while (true) {
if (!flag) {
System.out.println("run off");
flag = true;
}
}
}).start();
}
}
public class WaitDemo {
private static final Object lock = new Object();
private static volatile boolean flag = true;
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock){
while (flag){
try {
System.out.println("wait start .......");
//等待
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("wait end ....... ");
}
}).start();
new Thread(() -> {
if (flag){
synchronized (lock){
if (flag){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通知
lock.notifyAll();
System.out.println("notify .......");
flag = false;
}
}
}
}).start();
}
}
public class LockSupportTest {
public static void main(String[] args) {
Thread parkThread = new Thread(new ParkThread());
parkThread.start();
System.out.println("唤醒parkThread");
//为指定线程parkThread提供“许可”
LockSupport.unpark(parkThread);
}
static class ParkThread implements Runnable {
@Override
public void run() {
System.out.println("ParkThread开始执行");
// 等待“许可”
LockSupport.park();
System.out.println("ParkThread执行完成");
}
}
}
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现:
PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符
public class PipedTest {
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive;
try {
while ((receive = System.in.read()) != -1) {
out.write(receive);
}
} finally {
out.close();
}
}
static class Print implements Runnable {
private final PipedReader in;
public Print(PipedReader in) {
this.in = in;
}
@Override
public void run() {
int receive;
try {
while ((receive = in.read()) != -1) {
System.out.print((char) receive);
}
} catch (IOException ex) {
}
}
}
}
join可以理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,最后join的实现其实是基于等待通知机制的
详细示例查看上面的ThreadJoinDemo
协程,英文Coroutines, 是一种基于线程之上,但又比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行),具有对内核来说不可见的特性。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。而协程的调用和子程序不同。协程在子程序内部是可中断的,然后转而执行别的子程序,在适当的时候再返回来接着执行
例如以下代码:
def A():
print '1'
print '2'
print '3'
def B():
print 'x'
print 'y'
print 'z'
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:1 2 x y 3 z
协程的特点在于是一个线程执行,与多线程对比的优势:
注意: 协程适用于被阻塞的,且需要大量并发的场景(网络io)。不适合大量计算的场景