一、进程与线程
- 进程简介:进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位,进程可以被看做程序的主体,也是线程的容器。
- 线程简介:一个进程里面,运行了很多子任务,这些子任务有的加载网页,有的处理缓存,有的进行下载,这些子任务都是线程,是操作系统调度的最小单元,也叫作轻量级进程。一个进程中可以创建多个线程,这些线程都拥有各自的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。
二、线程的状态
- 新建状态:创建线程对象但未调用start()方法。
- 就绪状态:线程调用了start()方法或者解除堵塞状态,等待获取CPU的使用权。
- 运行状态:线程获取CPU,执行run()方法。
- 堵塞状态:线程堵塞分为等待堵塞(调用wait()方法)、同步堵塞(获取同步锁)、其他堵塞(调用sleep()方法、join()方法)。
- 死亡状态:线程中断或结束,不能再次启动。
线程对象方法 | 功能描述 |
---|---|
void start() | 线程启动进入就绪状态 |
void sleep(long mills) | 线程进入休眠,参数为毫秒 |
void setPriority(int priority) | 设置线程优先级,等级越高CPU资源越多 |
void interrupt() | 线程终止运行,不建议使用 |
boolean isAlive() | 线程是否处于就绪或者运行状态 |
void join() | 插队,其它线程进入堵塞,待该线程执行完成,再执行其它线程 |
void yield() | 礼让,线程从运行状态转为就绪状态,让CPU重新调度 |
三、线程的创建
继承 Thread 类
public class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}
MyThread thread = new MyThread();
thread.start();
实现 Runnable 接口,优点(比较 Thread):实现资源共享,如线程模拟火车抢票
public class MyRunnable implements Runnable {
private AtomicLong ticket;
@Override
public void run() {
if(ticket.get()>0) ticket.decrementAndGet();
}
}
Runnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();
thread2.start();
thread3.start();
实现 Callable 接口(推荐使用),优点(比较Runnable):Callable 可在任务接受后提供一个返回值,Callable 中的call()方法可以抛出异常,Callable 可以获取一个Future对象,Future对象表示异步计算的结果,它提供检查计算是否完成的方法。
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public String call() throws Exception {
return "返回线程执行结果";
}
}
FutureTask futureTask = new FutureTask<>(new MyCallable());
1、使用线程
Thread thread = new Thread(futureTask);
thread.start();
2、使用线程池
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(futureTask);
executor.shutdown();
3、打印执行结果
System.out.println(futureTask.get());
四、线程的使用
线程同步(两种方法),每个Java对象都有一个锁,线程可以调用同步方法或者同步代码块来获得锁。
创建重入锁对象
Lock mLock = new ReentrantLock();
使用锁
mLock.lock();
解锁
mLock.unlock();
创建读写锁对象
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
使用读锁后,可以继续申请读锁,不可以申请写锁
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();
使用写锁后,不管再申请读锁还是写锁,都需要等写锁释放后才能获取
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock();
基于CAS创建自旋锁
public class SpinLock {
private final AtomicReference atomicReference = new AtomicReference<>();
public void lock() {
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null, thread)) {
期望值为空,则赋值形成锁,反之不断循环
}
}
public void unlock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
}
}
创建自旋锁
SpinLock spinLock = new SpinLock();
使用锁
spinLock.lock();
解锁
spinLock.unlock();
基于CAS实现乐观锁,每次值更新都触发stamp递增,避免发生ABA问题
AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>("a", 1);
atomicStampedReference.compareAndSet("a", "b", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
atomicStampedReference.compareAndSet("b", "a", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
加法计数器,调用await()方法实现递增,满足次数后执行CyclicBarrier对象的方法体。
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
System.out.println("达到触发条件!");
});
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
try {
cyclicBarrier.await();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
减法计数器,调用countDown()方法实现递减,调用await()方法实现堵塞等待结束。
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 1; i <= countDownLatch.getCount(); i++) {
final int index = i;
new Thread(() -> {
countDownLatch.countDown();
System.out.println("执行第" + index + "次");
}).start();
}
countDownLatch.await();
System.out.println("等待执行完毕!");
信号量,通过构造函数定义限流量,调用acquire()方法获取信号,调用release()方法释放信号。
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
synchronized 关键字(同步悲观锁,保证原子性、可见性,不保证有序性)
简介:Java类可以创建多个不同的对象,但类对象只有一个,可修饰变量、方法、类。
对象锁:修饰某个对象,相同的类可以有多个不同的对象锁。
类锁:修饰某个类,相同的类只有一的类锁。
1、两个线程同时访问一个对象的同步方法:先后访问。
2、两个线程同时访问两个对象的同步方法:同时访问。
3、两个线程同时访问同一个静态同步方法:先后访问。
4、两个线程同时访问同步方法和非同步方法:同时访问。
5、两个线程同时访问同对象的两个同步方法:先后访问。
6、两个线程同时访问静态和非静态同步方法:同时访问。
7、方法抛出异常后,锁自动释放。
public synchronized void lock(){} -> 对象锁,作用在方法上
synchronized (obj){} -> 对象锁,作用在代码块上
public static synchronized void lock(){} -> 类锁,作用在方法上
synchronized (HelloWorld.class){} -> 类锁,作用在代码块上
volatile 关键字(保证可见性,有序性,不保证原子性)
简介:有时候读写一个或者多个实例域使用同步的话,开销大,而volatile关键字为实例域的同步访问提供了免锁的机制,如果声明一个域为volatile,那么编译器和虚拟机就知道该域的可能被另一个线程并发更新。当一个变量被volatile修饰后,就具备两个含义,一个是线程修改变量的值时,变量的新值对其他线程立即可见,另一个是禁止使用指令重排序,仅能修饰变量。
正确使用:volatile 修饰的变量不能在含有自增自减操作和变量不在具有其他变量的不等式中。
- 共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
- 原子数据类型:java.util.concurrent.atomic.*,采用乐观锁高效更新变量的值,利用CAS(CompareAndSwap)来实现原子性操作,CAS包含三个操作数[内存位置、预期原值、新值]。如果内存位置的值与预期原值相匹配,处理器会将该位置的值更新,反之处理器不做任何操作,Java并发包(java.util.concurrent)中大量使用了CAS操作,涉及到并发的地方都调用了sun.misc.Unsafe类方法进行CAS操作。
线程安全性:可见性、原子性、有序性。
- 原子性:执行一个或多个操作,要么同时执行,要么通知不执行,类似数据库的事务管理,经典案例是银行账户转账,确保在执行过程中不会丢失任何信息。
- 可见性:多个线程同时访问同一个变量时,其中一条线程修改了这个变量的值,其他线程能立刻看得到这个变量修改后的值。
- 有序性:程序执行时按照代码的先后顺序运行,JVM在执行代码的时候为了提高程序运行的效率,会对代码进行优化,它不保证程序中每个语句的执行先后顺序和原代码的编写先后顺序一致,但是它能确保程序最终执行的结果和原代码编写顺序的结果一致,这个过程称为重排序,重排序遵循不对存在数据依赖关系的代码行重排序,如:a=1;b=a;则不会重排序,如:a=1;b=2;c=a+b;则可能发生重排序:b=2;a=1;c=a+b;,c存在对a和b的依赖不会重排序。重排序不会影响单线程执行的结果,但会影响多线程执行的结果,因为重排序发生在线程内部。
五、线程池
固定大小的线程池,每次提交任务都会创建线程,直到线程池里的线程达到上限,当任务数大于线程数时,任务会放在队列中,等待线程空闲,按先后顺序依次执行,创建后的线程不会被销毁,空闲的线程处于待命状态,活动的线程处理提交的任务。
ExecutorService service = Executors.newFixedThreadPool(nThreads); -> 定义线程池大小
service.execute(() -> System.out.println("thread"));
缓存的线程池,将创建的线程放进缓存区,线程数不足时创建新线程,已创建线程在空闲时会被重用,提高程序性能,线程池会回收空闲时间达到60秒的线程,将其从缓存区移除。
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> System.out.println("thread"));
单线程的线程池,线程按先后顺序依次执行所有提交的任务,如果线程异常结束,则会创建新的线程来接替工作
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(() -> System.out.println("thread"));
延迟或周期的线程池,可以定时或延时执行提交的任务
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); -> 单线程
ScheduledExecutorService service = Executors.newScheduledThreadPool(nThreads); -> 定义线程池大小
service.execute(() -> System.out.println("thread")); -> 普通执行任务
service.schedule(() -> System.out.println("thread"), 1, TimeUnit.SECONDS); -> 延迟1秒执行任务
service.scheduleAtFixedRate(() -> System.out.println("thread"),1,2,TimeUnit.SECONDS); -> 延迟1秒,周期2秒
service.scheduleWithFixedDelay(() -> System.out.println("thread"),1,2,TimeUnit.SECONDS); -> 延迟1秒,间隔2秒
线程池相关方法详解
service.execute(runnable); -> 执行任务不返回结果
service.submit(futureTask); -> 执行任务并返回结果
service.isShutdown(); -> 线程池是否关闭
service.shutdown(); -> 停止接收任务并执行已提交任务至完成时关闭线程池任务执行完成
service.isTerminated(); -> 线程池内所有任务是否执行完成
service.shutdownNow(); -> 立即停止线程池并返回未执行完成的任务列表
service.awaitTermination(1, TimeUnit.SECONDS); -> 返回指定时间后所有任务是否已执行完成
注意:shutdown()需要在awaitTermination()前调用,反之会导致死锁
线程池使用案例
ExecutorService service = ThreadPools.newExecutorService(String.valueOf(System.currentTimeMillis()), 10);
for (Object object : objectArray) {
service.execute(() -> {
执行方法
});
}
service.shutdown();
while (!service.isTerminated()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ThreadPoolExecutor的创建方式,使用7个形参的构造函数,(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue
workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler),其中corePoolSize表示核心线程数,maximumPoolSize表示最大线程数,但如果使用的是无界的阻塞队列,该参数将失效,keepAliveTime表示非核心线程空闲时的存活时间,超出时长线程将被销毁,unit表示线程存活时间的单位,workQueue表示存放线程的阻塞队列,分为有界和无界,threadFactory表示创建线程的工厂,handler表示多余的线程处理器,也称为拒绝策略,用于线程池任务饱和时提供另一种方式处理新进来的任务。
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(16); -> 阻塞队列
ThreadFactory threadFactory = Executors.defaultThreadFactory(); -> 创建线程的工厂
ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy(); -> 拒绝策略
ExecutorService executorService = new ThreadPoolExecutor(50, 100, 5,
TimeUnit.MINUTES, arrayBlockingQueue, threadFactory, abortPolicy);
拒绝策略 | 功能描述 |
---|---|
AbortPolicy | 直接抛出异常,该策略也为默认策略 |
CallerRunsPolicy | 在调用者线程中执行该任务 |
DiscardOldestPolicy | 丢弃阻塞队列最前面的任务,并执行当前任务 |
DiscardPolicy | 直接丢弃任务 |
阻塞队列 | 功能描述 |
---|---|
ArrayBlockingQueue | 由数组结构组成的有界阻塞队列 |
LinkedBlockingQueue | 由链表结构组成的有界阻塞队列 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列 |
DealyQueue | 使用优先级队列实现的无界阻塞队列 |
SynchronousQueue | 不存储元素的阻塞队列 |
LinkedTransferQueue | 由链表结构组成的无界阻塞队列 |
LinkedBlockingDeque | 由链表结构组成的双向阻塞队列 |
功能 | 抛出异常 | 返回值 | 堵塞等待 | 超时等待 |
---|---|---|---|---|
底部添加 | boolean add(String s) | boolean offer(String s) | void put() | boolean offer(String s,long mills,TimeUnit unit) |
头部移除 | String remove() | String poll() | String take() | String poll(long mills,TimeUnit unit) |
获取队首元素 | String element() | String peek() | / | / |
线程池相比线程的优势:
- 复用已创建的线程,减少线程创建和销毁的性能开销。
- 有效控制线程的最大并发数量,避免过多的资源竞争堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
线程池处理任务流程
- 核心线程是否上限,如果否,使用核心线程处理任务,如果是,往下判断。
- 堵塞队列是否上限,如果否,往堵塞队列里添加任务,如果是,往下判断。
- 最大线程数是否上限,如果否,创建临时线程处理任务,如果是,使用拒绝策略。
六、Object类中的wait()、notify()、notifyAll()
- 特点:被定义final,子类不可重写该方法。必须在同步代码块里面调用方法,证明需要获得锁。必须用try和catch捕捉异常,避免发生异常中断。
- wait:正在运行的线程释放CPU资源,进入等待状态。
- notify:随机唤醒一条等待状态的线程,重新争夺CPU资源。
- notifyAll:唤醒所有等待状态的线程,重新争夺CPU资源。
public interface Storage {
void produce(int num);
void consume(int num);
}
public static class CustomStorage implements Storage {
private final int MAX = 100;
private LinkedList