什么是线程
线程:又称轻量级进程。程序中的一个顺序控制流程,同时也是CPU的基本调度单位。进程由多个线程组成,彼此间完成不同的工作,交替运行,称为多线程。
Java虚拟机(JVM)是一个进程,当中默认包含主线程(Main),可通过代码创建多个独立线程,与Main并发执行。
线程的组成
任何一个线程都具有基本的组成部分:
- CPU时间片:操作系统(OS)会为每个线程分配执行时间。
- 运行数据:
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
- 线程的逻辑代码
创建线程
- 创建线程的第一种方式:
- 继承Thread类
- 覆盖run()方法
- 创建子类对象
- 调用start()方法
public class ThreadDemo1 {
public static void main(String[] args) {
// 3、创建子类对象
MyThread myThread = new MyThread();
// 4、调用start()方法
myThread.start();
}
}
// 1、继承Thread类
class MyThread extends Thread{
// 2、覆盖run()方法
public void run(){
for (int i = 0; i <= 10; i++) {
System.out.println("MyThread:" + i);
}
}
}
- 创建线程的第二种方式:
- 实现Runnab接口
- 覆盖run()方法
- 创建实现类对象
- 创建线程对象
- 调用start()方法
public class ThreadDemo2 {
public static void main(String[] args) {
// 3、创建实现类对象
MyRunnable myRunnable = new MyRunnable();
// 4、创建线程对象
Thread t1 = new Thread(myRunnable);
// 5、调用start()方法
t1.start();
}
}
// 1、实现Runnable接口
class MyRunnable implements Runnable{
// 2、覆盖run()方法
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.println("MyThread:" + i);
}
}
}
线程的状态(基本)
- new 初始状态:线程对象被创建,即为初始状态。只在堆中开辟内存,与常规对象无异。
- Ready 就绪状态:调用start()方法后,进入就绪状态。等待OS选中,并分配时间片。
- Running 运行状态:获得时间片后,进入运行状态,如果时间片到期,则回到就绪状态。
- Terminated 终止状态:主线程main()或独立线程run()结束,进入终止状态,并释放持有的时间片。
常见方法
- 休眠:
- public static void sleep(long millis)
- 当前线程主动休眠millis毫秒
- 放弃:
- public static void yield()
- 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
- 结合:
- public final void join()
- 允许其他线程加入到当前线程中,并优先执行
线程安全问题
- 同步代码块:
synchronized(临界资源对象){ // 对临界资源对象加锁
// 代码(原子操作)
}
注意:
- 每个对象都有一个互斥锁标记,用来分配给线程的
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
- 线程退出同步代码块时,会释放相应的互斥锁标记
经典问题
- 死锁:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
- 生产者、消费者:
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费。显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品也不允许生产者向一个满的缓冲区中放入产品。
线程通信
- 等待:
- public final void wait()
- public final void wait(long timeout)
- 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程回释放其拥有的所有锁标记。同时此线程因obj处在无限期等待的状态中。释放锁,进入等待队列。
- 通知:
- public final void notify()
- public final void notifyAll()
- 必须在对obj加锁的同步代码块中。从obj的waiting中释放一个后全部线程。对自身没有任何影响。
线程池
线程池的概念
- 现有问题:
- 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出
- 频繁的创建和销毁线程会增加虚拟机回收频率、资源开销。造成程序性能下降
- 线程池:
- 线程容器,可设定线程分配的数量上限。
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象
- 避免频繁的创建和销毁
线程池原理
- 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。
获取线程池
- 常用的线程池接口和类(所在包java.util.concurrent):
- Executor:线程池的顶级接口。
- ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码
- Executors工厂类:通过此类可以获得一个线程池
- 通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量
- 通过 newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,没有上限
public class ThreadDemo3 {
public static void main(String[] args) { // 主线程
// 线程池(引用)---> Executors工具类(工厂类)
ExecutorService es = Executors.newFixedThreadPool(3);
MyTask myTask = new MyTask();
es.submit(myTask);
}
}
class MyTask implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "MyTask" + i);
}
}
}
Callable接口
public interface Callable<V>{
public V call() throws Exception;
}
- JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
- Callable具有泛型返回值、可以声明异常
public class CallableDemo {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
Callable task = new MyCallable();
es.submit(task);
es.submit(task);
}
}
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
}
Future接口
- 概念:异步接收ExecutorService.submit()所返回的状态结果当中包含了call()的返回值
- 方法:V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
- 应用:并发下的统计工作
线程的同步和异步
- 同步:
- 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回才能继续。
- 异步:形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。
Lock接口
- JDK5加入,与synchronized比较显式定义结构更灵活。
- 提供更多实用性方法,功能更强大、性能更优越
- 常用方法:
- void lock() //获取锁,如锁被占用,则等待
- boolean tryLock() // 尝试获取锁(成功返回true,失败返回false,不阻塞)
- void unlock() // 释放锁
重入锁
- ReentrantLock:Lock接口的实现类与synchronized一样具有互斥锁的功能。
读写锁
- ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁
- 支持多次分配读锁,使多个读操作可以并发执行
- 互斥规则:
- 写——写:互斥,阻塞
- 读——写:互斥,读阻塞写、写阻塞读。
- 读——读:不互斥、不阻塞
- 在读操作远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。