线程
线程与进程
进程是操作系统分配资源的最小单位,而线程是程序执行的最小单位,他们都是可以并发执行的。一个进程至少有一个线程,这些线程共享进程的资源空间。
线程简介
每个线程都有一个优先级,高优先级的线程比低优先级的线程先执行。优先级的取值范围是1到10的整数,默认是5。每个线程有可能被标记为一个守护线程。当一个线程创建另外一个新的线程对象,新的线程的优先级等于创建他的线程的优先级;如果新的线程对象是一个守护线程当且仅当创建他的线程是一个守护线程。
线程分类
Java线程分为守护线程(Daemon Thread)和用户线程(User Thread)。守护线程和用户线程基本上是一样的,唯一的区别是如果用户线程全部退出运行了,不管有没有守护线程虚拟机都会退出。守护线程的作用是为其他的线程的运行提供服务,最典型的守护线程就是GC(垃圾回收期)。
创建线程
创建线程的方式
创建一个线程类有三种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
Thread
Thread简介
Thread是创建线程最关键的一个类,这个词本身也代表线程,Thread类实现了Runnable接口。
代码示例
public class ThreadDemo extends Thread {
public void run() {
for (int i = 0; i < 60; i++) {
System.out.println(getName() + ":" + i);
}
}
}
public class Demo{
public static void main(String[] args) {
ThreadDemo t1 = new ThreadDemo();
ThreadDemo t2 = new ThreadDemo();
t1.start();
t2.start();
}
}
Runnable
Runnable简介
Runnable是提供线程的接口,有一个抽象方法public abstract void run()
。实现了这个接口的类必须实现它的run
方法。
代码示例
public class Runnable implements Runnable{
public void run() {
public void run() {
for (int i = 0; i < 60; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class Demo{
public static void main(String[] args) {
RunnableDemo run = new RunnableDemo();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
}
}
Callable和Future
Callable和Future简介
Thread和Runnable创建线程不能获取线程的返回值。从Java1.5开始,就提供了Callable和Future,通过他们可以在任务执行完毕之后得到任务执行结果。
- Callable接口:可以返回一个结果或者抛出一个异常的一个任务,实现者定义一个没有参数的call方法。区别于Thread和Runnable的run方法,Calllable任务执行的方法是call。
- Future接口:Future接口代表了异步计算的结果,提供了一些方法用于检查计算结果是否完成,获取计算结果等。FutureTask类提供了Future接口的实现,并且实现了Runnable接口。
代码案例
public class MyCallable implements Callable {
public Integer call() {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return new Integer(sum);
}
}
public class Demo{
public static void main(String[] args) {
MyCallable callable = new MyCallable();
FutureTask result = new FutureTask(callable);
new Thread(result).start();
try {
Integer value = result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程生命周期
线程状态
在Thread类中有一个内部枚举类State代表了线程的状态,一个线程从创建到销毁就是一个完整的生命周期。
public enum State {
/**
* 线程被创建,还没有开始运行
*/
NEW,
/**
* 线程运行状态,运行状态的线程是正在被Java虚拟机执行,但是也可能正在等待操作系统的其他资源例如处理器
*/
RUNNABLE,
/**
* 线程阻塞状态,等待监视器锁。处于阻塞状态线程是在等待监视器锁为了:进入同步代码块/方法或者被调用后重 * 新进入同步代码/方法
*/
BLOCKED,
/**
* 线程等待状态,一个线程处于等待状态由于调用了以下这几种方法:Object.wait;Thread.join;LockSupp
* ort.park。处于等待的线程正在等待另一个线程执行一个特定的操作。
*/
WAITING,
/**
* 线程超时等待状态,一个线程处于超时等待状态在一个特定的等待时间,由于调用了以下几个方法Thread.slee
* p;Object.wait(long);Thread.join(long);LockSupport.parkNanos;LockSupport.parkUntil。
*/
TIMED_WAITING,
/**
* 线程结束状态,线程已经执行完成了。
*/
TERMINATED;
}
线程状态转换
线程状态转换图
线程从创建后就在几个状态中切换。下面是一个线程状态转换图,调用不同的方法就可以切换线程线程的状态。
运行状态&无限等待
调用Object.wait();Thread.join();LockSupport.park()方法可以让线程从运行状态进入到无限等待状态。
wait方法
是属于Object类的,对象调用wait方法后会让当前持有对象锁的线程释放当前对象锁并进入等待队列。对象调用notify从等待队列随机选择一个线程唤醒去竞争对象锁,对象调用notifyall会唤醒等待队列中的所有线程去竞争对象锁。public class Demo { public static void main(String[] args) { Demo demo = new Demo(); Thread t1 = new Thread(() -> { synchronized (demo) { System.out.println("t1 start"); try { demo.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 end"); } }); Thread t2 = new Thread(() -> { synchronized (demo) { System.out.println("t2 start"); System.out.println("t2 end"); demo.notify(); } }); t1.start(); t2.start(); } }
join方法
是属于Thread类的,join方法是阻塞调用此方法的线程,当线程a调用线程b的b.join(long),线程a会阻塞直到线程b执行完成。public class Demo { public static void main(String[] args) throws Exception { System.out.println("main start"); Thread t1 = new Thread(() -> { System.out.println("t1 start"); System.out.println("t1 end"); }); t1.start(); t1.join(); System.out.println("main end"); } }
park方法
是属于LockSupport类的,LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以使用park方法来阻塞线程,使用unpart来唤醒线程。public class Demo { public static void main(String[] args) { System.out.println("main start"); Thread t1 = new Thread(() -> { System.out.println("t1 start"); LockSupport.park(); System.out.println("t1 end"); }); t1.start(); LockSupport.unpark(t1); System.out.println("main end"); } }
运行状态&超时等待
调用Object.wait(long);Thread.join(long);LockSupport.park(long)方法可以让线程从运行状态进入到等待状态,直到到达等待时间或者主动唤醒。
wait(long)方法
是属于Object类的,当对象调用wait(long)后会让当前持有对象锁的线程释放掉当前对象锁进入等待队列,直到到达等待时间或者对象调用notify或者notifyall从等待队列中唤醒线程,线程又重新开始竞争锁。public class Demo { public static void main(String[] args) { Demo demo = new Demo(); Thread t1 = new Thread(() -> { synchronized (demo) { for (int i = 0; i < 1000; i++) { if (i == 500) { try { demo.wait(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("------t1------: " + i); } } }); Thread t2 = new Thread(() -> { synchronized (demo) { for (int i = 0; i < 1000; i++) { System.out.println("------t2------: " + i); } } }); t1.start(); t2.start(); } }
join(long)方法
是属于Thread类的,join(long)方法是阻塞调用此方法的线程,当线程a调用线程b的b.join(long),线程a会阻塞直到到达阻塞时间或者线程b执行完成。public class Demo { public static void main(String[] args) throws Exception { System.out.println("main start"); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { System.out.println("----t1----: " + i); } }); t1.start(); t1.join(1); System.out.println("main end"); } }
parkUntil(long)和parkNanos(long)
是属于LockSupport类的,LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以使用parkUntil(long)和parkNanos(long)方法来阻塞线程。parkNanons是阻塞long时间,parkUntil是阻塞截止到long时间。public class Demo { public static void main(String[] args) { System.out.println("main start"); Thread t1 = new Thread(() -> { System.out.println("t1 start"); LockSupport.parkNanos(3000000000L); System.out.println("t1 end"); }); t1.start(); System.out.println("main end"); } }
public class Demo { public static void main(String[] args) throws Exception{ System.out.println("main start"); Thread t1 = new Thread(() -> { System.out.println("t1 start"); String dateTimeStr = "2021-04-04 14:57:00"; DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr, df); LockSupport.parkUntil(dateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli()); System.out.println("t1 end"); }); t1.start(); System.out.println("main end"); } }
无限等待&阻塞状态
对象调用wait方法后线程会进入无限等待状态,当对象调用notify或者notifyAll时,线程将从无限等待状态进入阻塞状态。
阻塞状态到运行状态
线程处于阻塞状态,如果获取到锁对象,就进入运行状态。