Java并发编程
当一个程序启动后,操作系统就会为这个程序创建一个进程并分配内存空间。如果这个程序是一个Java程序,那么它的内存空间会分为堆区、栈区、元数据区、本地方法栈、程序计数器
等。
当进程创建完后,栈中的主线程也就是main方法就会开始执行。在并发编程的需求下,需要创建子线程,首先在堆中创建一个线程对象,然后启动它。
- 进程,是对运行时程序的封装,是操作系统进行资源调度和分配的基本单位,实现了操作系统的并发。
- 线程,是进程的子任务,是CPU调度和分派的基本单位,实现了进程内部的并发。
一、线程的创建
创建Java线程有三种方式
1. 继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "第" + i + "次执行");
}
}
}
//创建MyThread对象
MyThread t1=new MyThread();
MyThread t2=new MyThread();
//设置线程的名字
t1.setName("T1线程");
t2.setName("T2线程");
//启动线程
t1.start();
t2.start();
2. 实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {//sleep会发生异常要显示处理
Thread.sleep(20);//暂停20毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "第" + i + "次执行");
}
}
}
//创建MyRunnable类
MyRunnable mr = new MyRunnable();
//创建Thread类的有参构造,并设置线程名
Thread t1 = new Thread(mr, "T1线程");
Thread t2 = new Thread(mr, "T2线程");
//启动线程
t1.start();
t2.start();
3. 使用FutureTask类
FutureTask是RunnableFuture的实现类,RunnableFuture又是Runnable和Future的子接口
FutureTask类的构造方法可以传入Callable接口实现类的实例,也可以传入Runnable接口的实例与线程执行后的返回值
这种创建线程的方法可以在主线程中获取子线程的返回值以及抛出的异常
public class CallerTask implements Callable<String> {
public String call() throws Exception {
return "task done";
}
}
//创建异步任务
FutureTask<String> task = new FutureTask<String>(new CallerTask());
//启动线程
new Thread(task).start();
try {
//等待执行完成,并获取返回结果
String result = task.get();
//String result = task.get(5,TimeUnit.MINUTES);
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
//通过调用getCause获取线程抛出的异常
Throwable cause = e.getCause();
e.printStackTrace();
} catch (TimeoutException e) {
//如果调用带有参数的get方法,当线程超时后会抛出此异常
e.printStackTrace();
}
-
run()
方法和start()
方法有什么区别?-
run()
:封装线程执行的代码,直接调用相当于调用普通方法。 -
start()
:启动线程,然后由JVM 调用此线程的run()
方法。
-
二、线程的状态
在java.lang.Thread类中的内部枚举,定义了Java线程的六种状态。
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
1. NEW(新建状态)
处于NEW状态的线程此时尚未启动。这里的尚未启动指的是还没调用Thread实例的start()方法。
private void testStateNew() {
Thread thread = new Thread(() -> {});
System.out.println(thread.getState()); // 输出 NEW
}
2. RUNNABLE(可运行状态)
Java线程的RUNNABLE状态包括了传统操作系统线程的ready和running两个状态的。
处于RUNNABLE状态的线程在Java虚拟机中运行,也有可能在等待CPU分配资源。
Thread源码里对RUNNABLE状态的定义:
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
//可运行的线程状态。处于可运行状态的线程正在 Java 虚拟机中执行,也可能正在等待来自操作系统(如处理器)的其他资源。
3. BLOCKED(阻塞状态)
处于BLOCKED状态的线程正等待锁的释放以进入同步区。
4. WAITING(等待状态)
处于等待状态的线程变成RUNNABLE状态需要其他线程唤醒。
调用如下3个方法会使线程进入等待状态:
- Object.wait():使当前线程处于等待状态直到另一个线程唤醒它;
- Thread.join():等待线程执行完毕,底层调用的是Object实例的wait方法;
- LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度。
5. TIMED_WAITING(超时等待状态)
线程等待一个具体的时间,时间到后会被自动唤醒。
调用如下方法会使线程进入超时等待状态:
- Thread.sleep(long millis):使当前线程睡眠指定时间;
- Object.wait(long timeout):线程休眠指定时间,等待期间可以通过notify()/notifyAll()唤醒;
- Thread.join(long millis):等待当前线程最多执行millis毫秒,如果millis为0,则会一直执行;
- LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间;
- LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;
6. TERMINATED(终止状态)
此时线程执行完毕。