概念
进程:系统的实际运作单位
线程:进程工作的基本单位
并发:单核CPU时,多线程交互使用CPU时间片(并不固定)
并行:多核CPU时,多线程同时使用CPU时间片
同步:程序由代码顺序来执行,要等待响应才能继续执行
异步:程序由系统事件来驱动执行,可以在响应之前执行其它任务,响应之后再回来继续执行
线程生命周期及状态图
线程使用
新建:三种创建线程的方法
Thread
static class ThreadEx extends Thread{
public void run(){
System.out.println("Thread");
}
}
public static void thread(){
Thread thread = new ThreadEx();
thread.start();
}
Runable:可以多一次继承类的机会
public static void runnable(){
class RunS implements Runnable{
public void run() {
System.out.println("implements Runnable");
}
}
Thread thread = new Thread(new RunS());
thread.start();
}
FutureTask:执行完后可以有返回值
public static void futureTask() {
FutureTask<Object> futureTask = new FutureTask<Object>(new Callable<Object>() {
public Object call() throws Exception {
System.out.println("FutureTask");
return "数据返回";
}
});
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
}catch (Exception e) {
System.out.println(e.getMessage());
}
}
就绪:调用启用方法之后,线程就可以抢CPU时间片段
执行:拥有CPU时间片段的的线程就执行Thread.run()方法
销毁:Thread.run()方法执行结束销毁该线程
阻塞:线程在达成某些条件之前会暂停执行,直至达成条件之后重新进入就绪状态
Thread线程方法(thread表示对象方法,Thread表示静态方法)
thread.start():使调用该方法的线程进入就绪状态
Thread.sleep(n):使当前线程进入阻塞状态,等待时间为传入的n毫秒
Thread.currentThread():获取当前线程引用
thread.join():使当前线程进入阻塞状态,直到调用线程执行结束
Thread.yield():使当前线程重新回到就绪状态
thread.interrupt():中断调用线程
public class Main {
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new Runnable() {
public void run() {
for(int x = 0; x < 10; x++){
/* 让出线程,重新进入就绪状态,多进行几次显示效果 */
Thread.yield();
Thread.yield();
Thread.yield();
Thread.yield();
Thread.yield();
threadTest(x);
}
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
for(int x = 10; x < 30; x++){
threadTest(x);
}
}
});
Thread thread3 = new Thread(new Runnable() {
public void run() {
try {
/* 使当前线程等待thread2线程执行结束后再执行 */
thread2.join();
}catch (Exception e){
System.out.println(e.getMessage());
}
System.out.println("B线程执行结束");
}
});
thread1.setName("线程A---");
thread2.setName("线程B---");
thread1.start();
thread2.start();
thread3.start();
}
/**
* synchronized 线程同步锁,同一时间只允许一个线程运行该方法,static方法锁住的是Main类
* @param x
*/
public static synchronized void threadTest(int x){
/* 获取当前线程引用再获取线程名 */
System.out.println(Thread.currentThread().getName() + x);
try {
/* 线程进入等待状态,1000毫秒之后再进入就绪状态 */
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
Object线程方法
Object.wait():使用当前线程释放锁并进入等待状态(等待的是Object这个对象锁),直到另一个线程调用notify()方法
Object.notify():唤醒等待Object这个对象锁的一个线程
public class Main2 {
static Object object = new Object();
public static void main(String[] args) throws Exception {
Thread thread1 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
System.out.println("A------当前线程进入阻塞状态,等待object锁");
synchronized (object){
System.out.println("A------thread2线程调用了wait方法,5S之后调用notify方法让thread2线程继续执行");
Thread.sleep(10000);
object.notify();
}
}catch (Exception e){
}
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
try {
synchronized (object){
System.out.println("当前线程获取object锁,2S之后再释放锁并进入等待状态");
Thread.sleep(5000);
object.wait();
System.out.println("继续执行,演示结束");
}
}catch (Exception e){
}
}
});
thread2.start();
thread1.start();
}
}
锁机制
volatile:轻量级的同步机制,实现了数据单个的读写操作的原子性、数据改变之后的可见性、防止重排序。各个线程会将共享变量从主内存中拷贝到工作内存,然后执行引擎会基于工作内存中的数据进行操作处理。而对被volatile修饰的数据进行操作时会立刻提交到主内存中,被其他线程感知到
synchronize:重量级的同步机制
lock:实现同步机制的类
类型 | synchronize | lock |
存在层次 | jvm层面 | JDK层面 |
锁的释放 | 1、被锁的代码块执行结束 2、发生异常,JVM让线程释放 | 必须在finally代码块中释放,无法自动释放 |
锁的获取 | 上一个线程只要没有释放锁,下一个线程一直阻塞 | 获取锁的策略很多,可以自定义,不用一直阻塞 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入 不可中断 非公平 | 可重入 可中断 可公平(两者皆可) |
性能 | 少量同步 | 大量同步 |
悲观锁
悲观锁认为每次去拿数据都会被其它线程修改,所以每次去拿数据时都会上锁。悲观锁的实现通常依赖数据库的提供的锁机制,否则就算当前当前系统实现加锁机制也无法保证外部系统不会修改数据
乐观锁
乐观锁认为每次去拿数据都不会被其它线程修改,所以不会上锁。只有在更新数据时会判断其它线程是否有更新过这个数据,乐观锁的实现机制通常为版本号机制和CAS算法
- 版本号机制:在数据表中添加一个version字段,在更新数据时会判断读取到的version是否与数据库数据一致,如果一致则version值加一,否则重复更新操作直到成功
- CAS算法:CAS比较并交换,有三个操作数,内存地址V ,预期值B,要替换得到的目标子A,在更新数据时判断V是否与B相等,若相等将A赋给B,若不相等则重复更新操作(比较与赋值操作是原子操作)
公平锁:线程按申请顺序获取锁
非公平锁:所有线程公平竞争都有机会获取锁,获取锁的顺序不固定
独占锁/互斥锁:任何时候只能有一个线程执行系统资源操作,具体实现ReentrantLock
共享锁/读写锁:多线程可同时对数据进行读取操作,但只能有一个线程对数据进行写操作,具体实现ReadWriteLock
可重入锁:该线程获取了该锁之后,可以无限次的进入该锁锁住的代码
自旋锁:尝试获取锁的线程不会立刻进入阻塞状态,而是利用循环再次尝试获取锁
线程池
作用
- 减少线程创建时间,提高响应速度
- 重复利用线程池中的线程,不需要每次都创建销毁,降低资源消耗
- 方便线程管理
核心参数
- corePoolSize(核心线程数):即使没有任务,核心线程也会一直存在
- queueCapacity(任务队列容量):核心线程都在运行时,此时再有任务进来会进入任务队列,等待线程执行
- maxPoolSize(最大线程数):核心线程都在运行且任务队列容量已满,会创建新的线程来执行任务,表示线程池中最大线程数
- keepAliveTime(线程空闲时间):当线程空间时间超出keepAliveTime时,会关闭线程直到线程数等于核心线程数
- allowCoreThreadTimeout(允许核心线程超时):核心线程超时会关闭
- rejectedExecutionHandler(任务拒绝策略):当线程数量达到最大线程数,且任务队列已满,有任务进来时采取任务拒绝策略
-
直接丢弃(DiscardPolicy)
-
丢弃队列中最老的任务(DiscardOldestPolicy)
-
抛异常(AbortPolicy)
-
将任务分给调用线程来执行(CallerRunsPolicy)
-
常用的四种线程池
Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程
Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行