线程:依赖于进程,是程序执行一个单一的顺序控制流程,程序执行流的最小单位,处理器调度和分派的基本单位。
进程:系统进行资源分配和调用的独立单位。
区别:一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间
同步:调用者必须等到方法调用返回才能进行后续行为。
并发:多个任务交替执行
阻塞:一个线程占用资源,其他需要这个资源的线程必须等待,等待会导致线程挂起。
死锁:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。多个线程被阻塞
同步锁:保证线程同步互斥,指并发执行的多个线程。java使用synchronized关键字获取一个对象的同步锁
线程实现
继承thread类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread.java");
}
}
Thread thread = new MyThread();
thread.start();
实现Runnable接口
public class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("MyThread2.java ");
}
}
MyThread2 thread2 = new MyThread2();
Thread t = new Thread(thread2);
t.start();
实现Callable接口
public class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
return "MyThread3.java";
}
}
try {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<String> future = threadPool.submit(new MyThread3());
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
什么是线程安全和线程不安全?
加锁的就是是线程安全的,不加锁的就是是线程不安全的
线程安全
就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全
不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
锁
乐观锁:读多写少,每次拿数据的时候认为不会修改,所以不会上锁。在更新的时候会判断在此期间别人有没有区更新这个数据,采取在写是先读出版本号,然后加锁。
悲观锁:写多,遇到并发写的可能性高,每次在读写数据的时候都会上锁。Java中悲观锁就是synchronized,aqs框架下的锁则是先尝试cas乐观锁去获取锁,获取不利,则转换为悲观锁,如retreenlock。
自旋锁:当线程A想要获取一把自旋锁而该锁又被其它线程锁持有时,线程A会在一个循环中自旋以检测锁是不是已经可用了。
注意:
- 自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在那里自旋,这就会浪费CPU时间
- 持有自旋锁的线程在sleep之前应该释放自旋锁以便其它线程可以获得自旋锁。
自旋锁优缺点:
自旋锁尽可能的减少线程阻塞
but 造成cpu的浪费
synchronized同步锁:非null对象当作锁,独占式的悲观锁,属于可重入锁。
synchronized作用范围:
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
synchronized核心组件:
- wait set:调用wait方法被阻塞的线程被放在这里
- contention list:竞争队列
- entry list:有资格成为候选资源的线程
- ondeck:只有一个线程正在竞争锁资源
- owner:获取到锁资源的线程
- !owner:释放锁的线程
synchronized同步原理:只是java的一个关键字,使用时并没有看到显示的加锁或解锁的过程,所以有必要通过javap命令,查看相应的字节码文件。
特性:
- 原子性:一个操作不可中断。即使是多个线程一起执行的时候,一个操作一旦开始就不会被其它线程干扰。
- 可见性:一个线程修改了某一个共享变量的值,其它线程是否能立即知道这个修改。
- 有序性:线程一旦加锁,必须等到其他线程将锁释放
reentrantlock与synchronized
reentrantlock通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized被jvm自动解锁机制不同,它需要手动解锁。
reentrantlock相比synchronized可中断、公平锁、多个锁。
线程池
java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池
线程复用;控制最大并发数;管理线程
线程复用:thread的类有start方法,调用start启动线程是java虚拟机会调用该类的run方法(runnable对象的run)。重写tread类,在start方法中添加runnable对象。获取runnable用queue实现。
线程池组成:
- 线程池管理器:用于创建并管理线程池
- 工作线程:线程池中的线程
- 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
- 任务队列:用于存放待处理的任务,提供一种缓存机制
常见线程池:
- newSingleThreadExecutor
单个线程的线程池,单线程串行执行 - newFixedThreadExecutor(n)
固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,进入等待队列直到前面的任务完成才继续执行 - newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲的线程,当有任务来时,又智能的添加新线程来执行。 - newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程
线程池工作过程:
- 线程池创建,任务队列作为参数传进来。
- 调用execute()方法添加一个任务
运行的线程数量小于corepoolsize,创建线程运行任务
大于等于,放入队列
队列满了,运行的线程数量小于maximumpoolsize,创建非核心线程运行这个任务
队列满了,运行的数量大于等于,抛出异常 - 线程完成任务,从队列中取下一个任务
- 线程闲,线程池会根据当前运行线程数大于corepoolsize,线程停掉
Java阻塞队列
队列没有数据,消费者端的所有线程被自动阻塞,知道有数据进入队列
队列中填满数据,生产者端的所有线程都会被自动阻塞,直到队列有空的位置,线程被自动唤醒
volatile
稍弱的同步机制,确保将变量的更新操作通知到其他的线程。
变量可见性:该变量对所有线程可见,指修改值其他线程可立即获取
禁止重排序:禁止指令重排
比synchronized更轻量级的同步锁:访问volatile变量时不会执行加锁操作,不会使执行线程阻塞
适合场景:一个变量被多个线程共享,线程直接给变量复赋值
在并发环境的线程安全的条件:
对变量的写操作不依赖于当前值
该变量没有包含在具有其他变量的不变式中,不同的volatile变量之间,不能相互依赖
synchronized和reentrantlock的区别
相同点:
- 对视用来协调线程对共享对象、变量的访问
- 都是可重入锁,同一线程可以多次获得锁
- 保证了可见性和互斥性
不同点: - synchronized隐式获得释放锁,reentrantlock显式获得释放锁
- synchronized不可相应中断,reentrantlock可相应中断可轮回
- synchronized是jvm级别,reentrantlock是api级别
- reentrantlock可以实现公平锁
- reentrantlock通过condition可以绑定多个条件
- synchronized是同步阻塞,悲观并发,lock是同步非阻塞,乐观并发策略
- lock是一个接口,synchronized是java关键字,synchronized是内置的语言实现
- lock可以提高多个线程进行读写操作
- lock可以知道有没有成功获取锁
- lock可以让等待锁的线程中断
- synchronized发生宜昌市释放占有锁,不会导致死锁,lock需要在finally块中释放锁
JVM线程调度(抢占式调度)
优先级分配cpu时间片运行
CAS
比较并交换-乐观锁机制-锁自旋
ABA问题: