现代的操作系统都是每运行一个程序就会为其创建一个进程(Process),进程具有独立性,动态性和并发性的特点。,在操作系统中可以支持多个进程并发地执行,比如一边编程,一边听歌…
多线程则是对多进程的扩展,线程被称作轻量级进程(Light Weight Process),一个进程可以拥有多个线程,这些线程拥有独立的程序计数器,堆栈和局部变量等等,可以并发执行。因此,线程是现代操作系统调度的最小单元。
优势
- 更大程度地利用计算机处理器
一个线程在一个时刻只能运行在一个处理器核心上,现代计算机处理器核心数量越来越多,如果采用多线程技术,将程序的计算逻辑分配到多个处理器核心上,会显著减少程序的处理时间。 - 缩短程序响应时间
针对数据一致性不是很强的业务,可以将部分逻辑交给线程处理,需要立即响应给用户的程序可以很快的执行完成,缩短了响应时间,提高了程序的体验感 - 更好的编程模型
java 内置了对多线程的支持,提供了良好的,考究并且一致的编程模型,极大地简化了多线程的开发。
创建线程
- 继承 Thread 类创建线程类
public class ThreadExample extends Thread {
/**
* ①重写 run 方法
**/
@Override
public void run() {
System.out.println("执行线程");
}
public static void main(String[] args) {
// ②创建子类的实例对象
ThreadExample threadExample = new ThreadExample();
// ③调用 start 方法启动线程
threadExample.start();
}
}
- 实现 Runnable 接口创建线程类
public class RunnableExample implements Runnable {
/**
* ①实现 Runnable 接口的 run 方法
*/
@Override
public void run() {
System.out.println("执行线程");
}
public static void main(String[] args) {
// ②创建实现实例
RunnableExample runnableExample = new RunnableExample();
// ③将实例作为 Thread 的 target 来创建 Thread 对象
Thread thread = new Thread(runnableExample);
// ④调用 start 方法启动线程
thread.start();
}
}
- 使用 Callable 和 Future 创建线程
callable 接口提供了 call 方法作为线程的执行体,该方法可以具有返回值,可以声明和抛出异常
public class FutureExample implements Callable<String> {
/**
* ①实现 Callable 接口的 call 方法
*/
@Override
public String call() throws Exception {
// 执行线程逻辑
System.out.println("执行线程");
//返回结果
return "a";
}
public static void main(String[] args) {
// ②创建 Callable 对象
FutureExample futureExample= new FutureExample();
// ③使用 FutureTask 类包装 Callable 对象
FutureTask<String> futureTask = new FutureTask<>(futureExample);
// ④使用 FutureTask 对象作为 target 创建 Thread
Thread thread = new Thread(futureTask);
// ⑤调用 start 方法启动线程
thread.start();
try {
// ⑥获取返回值
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程的状态
- 新建状态 :通过 new 关键字创建的线程,还未被调用 start 方法时处于新建状态;
- 运行状态:线程开始执行run方法或者call方法的线程执行体,处于运行状态;
- 阻塞状态:当线程遇到等待阻塞于锁、调用了 sleep 方法等等情况时,线程就处于阻塞状态;
- 就绪状态(等待或者超时等待状态):线程调用了start方法却未执行 run 方法,或者线程在等待一个锁的释放,获取锁,这时候线程处于就绪状态;
- 终止状态:线程执行完毕,则线程进入终止状态。
线程通信
在实际开发过程中,不可能让所有的线程都可以孤立地运行,线程之间通过相互配合来完成工作,这样才更有价值。
volatile 和 synchronized 关键字
java 支持多个线程同时访问一个对象,由于每个线程本地都有这个变量的拷贝,所以在执行的过程中,每个线程所获取到的变量不一定是最新的。
使用关键字 volatile 修饰的变量,在任一线程中被访问时,规定必须从共享内存中获取;在任一线程中被更新时,规定必须同步刷新回共享内存。
关键字 synchronized 可以修饰方法或者以同步代码块的形式使用,确保了多个线程在同一时刻,只能有一个线程处于同步方法或者同步代码块中,保证了线程对变量访问的可见性和排他性。
等待/通知机制
方法名称 | 描述 |
---|---|
notify() | 通知一个在对象上等待的线程,使其从wait 方法返回,继续执行线程体 |
notifyAll() | 通知在对象上等待的所有线程 |
wait() | 线程进入就绪状态(会释放对象的锁) |
wait(long l) | 超时等待一段时间,没有收到通知就超时返回 |
wait(long l, int i) | 超时等待(控制等待时间的单位【纳秒,毫秒等等】) |
等待/通知指的是,一个线程A调用了一个对象的 wait 方法,进入等待就绪状态,另一个线程B在调用了同一个对象的notify()或者notifyAll()方法后线程A接收到通知从 wait方法返回,进而执行后续操作。
代码示例
public class NotifyExample {
static boolean flag = true;
static Object obj = new Object();
public static void main(String[] args) {
// 启动两个线程
Thread waitThread = new Thread(new Wait());
waitThread.start();
Thread notifyThread = new Thread(new Notify());
notifyThread.start();
}
static class Wait implements Runnable{
@Override
public void run() {
// 获取 obj 的锁
synchronized(obj) {
while(flag) {
// 满足条件进入等待
System.out.println("开始等待");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 不满足条件开始工作
System.out.println("wait 线程工作中");
}
}
}
static class Notify implements Runnable{
@Override
public void run() {
// 获取 obj 的锁
synchronized(obj) {
// 修改条件并发起通知
flag = false;
System.out.println("通知其他线程");
obj.notifyAll();
}
// 再次获取锁
synchronized(obj) {
System.out.println("再次获取锁");
}
}
}
}
运行结果有两种:
- 使用wait(),notify()或者nitifyAll()前需要对对象加锁;
- 在wait()调用完之后,线程状态由运行状态转换为就绪状态并被放置到对象的等待队列;
- notify()或者nitifyAll()调用完之后,等待线程不会立即从wait()返回,而是等到调用notify()或者nitifyAll()的线程释放锁之后,才有机会返回;
- notify()将等待队列中的一个线程移动到同步队列,notifyAll()将等待队列中的所有线程移动到同步队列,被移动的线程进入阻塞状态;
- 从wait()方法返回的前提是获得了调用对象的锁。
join
thread.join() 方法可以控制当前线程等待thread线程终止之后再继续执行thread.join()后面的代码。
线程变量
线程变量 ThreadLocal 是一个以 ThreadLocal 对象为键、任意对象为值的存储结构。这个结构附带在线程上,线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
代码示例:
public class ThreadLocalExample {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>();
public static final void countBegin() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long countResult() {
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws Exception {
ThreadLocalExample.countBegin();
TimeUnit.SECONDS.sleep(1);
System.out.println("运行程序总共花费" + ThreadLocalExample.countResult() + "毫秒");
}
}
运行结果