java.util.concurrent java.util工具包
业务 : 普通的线程代码 Thread
Runnable 没有返回值, 效率比Callable相对较低 ! 一般使用Callable
进程 : 一个程序, QQ.exe 程序的集合.
一个进程可以包含多个进程, 至少包含一个.
java默认有两个线程 main GC
线程 : 开了一个进程 Typora, 写字, 自动保存 (线程负责)
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线 ;
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多 ;
Java是无法开启线程的, 开始线程是调用底层C++开启的
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//本地方法, 底层的C++, Java无法直接操作硬件
private native void start0();
并发 ( 多线程操作同一资源 )
并行 ( 多个人一起行走 )
并发是两个队列交替使用一台咖啡机,并行是两个队列同时使用两台咖啡机,如果串行,一个队列使用一 台咖啡机,那么哪怕前面那个人便秘了去厕所呆半天,后面的人也只能死等着他回来才能去接咖啡,这效率无疑是最低的。
并发是不是一个线程,并行是多个线程?
答:并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)cpu执行,如果可以就说明是并 行,而并发是多个线程被(一个)cpu 轮流切换着执行
并发编程的本质 : 充分利用CPU的资源
操作系统有五种 : 新建 就绪 运行 阻塞 死亡
Java有六种 : 新建 运行 阻塞 等待 超时等待 终止
1))NEW:初始状态,线程被构建,但是还没有调用 start 方法;
2)RUNNABLED:运行状态,Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3)BLOCKED:阻塞状态,表示线程进入等待状态, 也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况 :
等待阻塞:运行的线程执行了 Thread.sleep 、wait()、 join() 等方法JVM 会把当前线程设置为等待状态,当 sleep 结束、join 线程终止或者线程被唤醒后,该线程从等待状态进入到阻塞状态,重新抢占锁后进行线程恢复;
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么jvm会把当前的线程放入到锁池中 ;
其他阻塞:发出了 I/O请求时,JVM 会把当前线程设置为阻塞状态,当 I/O处理完毕则线程恢复;
4)WAITING:等待状态,没有超时时间,要被其他线程或者有其它的中断操作;
执行wait()、join()、LockSupport.park();
5)TIME_WAITING:超时等待状态,超时以后自动返回;
执行 Thread.sleep(long)、wait(long)、join(long)、LockSupport.park(long)、LockSupport.parkNanos(long)、LockSupport.parkUntil(long)
6)TERMINATED:终止状态,表示当前线程执行完毕 。
来自不同的类
wait => Object
sleep => Thread
锁的释放
wait会释放锁
sleep不会释放锁
使用的范围
wait 必须在同步代码块中使用
sleep 可以在线程的任何地方
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
/*
线程就是一个单独的资源类, 没有任何附属的操作
属性 方法
*/
public class SaleTicket {
public static void main(String[] args) {
//并发 : 多线程操作同一个资源
Ticket ticket=new Ticket();
//@FunctionalInterface 函数式接口 可以使用Lambda表达式
new Thread(()->{
for (int i=1;i<60;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i=1;i<60;i++){
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=1;i<60;i++){
ticket.sale();
}
},"C").start();
}
}
//资源类 OOP
class Ticket{
//属性 方法
private int number=50;
//卖票的方式
//synchronized本身是锁,
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余 :"+number);
}
}
}
公平锁 : 十分公平, 可以先来后到
非公平锁 : 十分不公平, 可以插队 ( 默认 )
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
public class SaleTicket2 {
public static void main(String[] args) {
//并发 : 多线程操作同一个资源
Ticket2 ticket=new Ticket2();
new Thread(()->{ for (int i=1;i<60;i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i=1;i<60;i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i=1;i<60;i++) ticket.sale(); },"C").start();
}
}
//1. new ReentrantLock();
//2. lock.lock();//加锁
//3. finally=>lock.unlock();//解锁
class Ticket2{
//属性 方法
private int number=50;
Lock lock=new ReentrantLock();
//卖票的方式
public void sale(){
lock.lock();//加锁
try {
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余 :"+number);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();//解锁
}
}
}
Synchronize 是内置的java关键字, Lock 是一个接口
Synchronize 无法判断获取锁的状态, Lock 可以判断是否获取到锁.
Synchronize 会自动释放锁, Lock 需要手动unLock. 如果不手动就会造成死锁.
Synchronize 如果线程1获取锁后阻塞了, 线程2只能一直等. Lock锁就不一定会等待下去.
tryLock 是防止死锁的一个重要方式。
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。不会像synchronized一样,一个线程获取锁之后,其他锁只能等待那个线程释放之后才能有获取锁的机会。
Synchronize 可重入锁, 不可以中断的, 非公平; Lock, 可重入锁, 可以判断锁, 非公平 ( 可以自己设置 )
Synchronize 适合锁少量的代码同步问题, Lock 适合锁大量的同步代码 !
所谓生产者-消费者问题,实际上主要是包含了两类线程,一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据,为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。但是,这个共享数据区域中应该具备这样的线程间并发协作的功能:
Synchronize版
/*
线程的通信问题 : 生产者和消费者问题! 等待唤醒, 通知唤醒
线程交替执行 A B 操作同一变量 num=0
A num+1
B num-1
*/
public class A {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.increment();
}catch (Exception e){
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.decrement();
}catch (Exception e){
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待,业务,通知
class Data{//数字 资源类
private int num=0;
//+1
public synchronized void increment() throws InterruptedException {
if (num!=0){
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (num==0){
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我-1完毕了
this.notifyAll();
}
}
虚假唤醒
虚假唤醒 : 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用的
public class A {
public static void main(String[] args) {
Data data=new Data();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.increment();
}catch (Exception e){
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.decrement();
}catch (Exception e){
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.increment();
}catch (Exception e){
e.printStackTrace();
}
}
},"D").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.decrement();
}catch (Exception e){
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待,业务,通知
class Data{//数字 资源类
private int num=0;
//+1
public synchronized void increment() throws InterruptedException {
if (num!=0){
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (num==0){
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我-1完毕了
this.notifyAll();
}
}
//A=>1
//C=>0
//D=>1
//A=>2
//D=>3
//B=>2
//B=>1
//B=>0
//C=>-1
//C=>-2
//C=>-3
//C=>-4
//C=>-5
//C=>-6
//C=>-7
//C=>-8
//C=>-9
//B=>-10
//B=>-11
//B=>-12
//B=>-13
//B=>-14
//B=>-15
//B=>-16
//D=>-15
//A=>-14
//D=>-13
//A=>-12
//D=>-11
//A=>-10
//D=>-9
//A=>-8
//D=>-7
//A=>-6
//D=>-5
//A=>-4
//D=>-3
//A=>-2
//D=>-1
//A=>0
解决方案 : if 改为 while 判断即可
通过Lock 找到 Condition
public class B {
public static void main(String[] args) {
Data2 data=new Data2();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.increment();
}catch (Exception e){
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.decrement();
}catch (Exception e){
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.increment();
}catch (Exception e){
e.printStackTrace();
}
}
},"D").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
try {
data.decrement();
}catch (Exception e){
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待,业务,通知
class Data2{//数字 资源类
private int num=0;
Lock lock =new ReentrantLock();
Condition condition=lock.newCondition();
//+1
public void increment(){
lock.lock();
try {
while (num!=0){
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我+1完毕了
condition.signalAll();//唤醒
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement(){
lock.lock();
try {
while (num==0){
//等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程我-1完毕了
condition.signalAll();//唤醒
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class C {
public static void main(String[] args) {
Data3 data3=new Data3();
new Thread(()->{
for (int i = 0; i <10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num=1; // 1A 2B 3C 信号量
public void printA(){
lock.lock();
try {
while(num!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"AAAAAA");
//唤醒B
num=2;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(num!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"BBBBB");
//唤醒B
num=3;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(num!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"CCCCCC");
//唤醒B
num=1;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1、标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话 12
* 1、sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话 21
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
//锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者!、
// 两个方法用的是同一个锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 3、 增加了一个普通方法后!先执行发短信还是Hello? 普通方法
* 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
*/
public class Test2 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
// synchronized 锁的对象是方法的调用者!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
// 这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话? 发短信
* 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话? 发短信
*/
public class Test3 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone3唯一的一个 Class 对象
class Phone3{
// synchronized 锁的对象是方法的调用者!
// static 静态方法
// 类一加载就有了!锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 1、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话? 打电话
* 2、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话? 打电话
*/
public class Test4 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone3唯一的一个 Class 对象
class Phone4{
// 静态的同步方法 锁的是 Class 类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 普通的同步方法 锁的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
主要看哪个先拿到, 然后在判断锁的是不是同一资源.
如果锁的是同一资源就要等先拿到的执行完再执行另外的线程.
如果锁的不是同一资源就不需要等先拿到的执行.
synchronized 锁的对象的方法的调用者
static synchronized 锁的对象是类
多个线程调用的时候, list, 读的时候是固定的, 但是写入的时候可能会覆盖前面的值, 可能会出现java.util.ConcurrentModificationException 并发修改异常
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
/**并发下ArrayList是不安全的
*Vector是安全的,就是ArrayList加了synchronized
*
* 解决方案
* 1. List list= new Vector<>();
* 2. List list= Collections.synchronizedList(new ArrayList<>());
* 3. List list= new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 写入时复制,复制给调用者写完了再创建 COW 计算机程序设计领域的一种优化策略
//多个线程调用的时候, list, 读的时候是固定的, 但是写入的时候可能会覆盖前面的值
//在写入的时候避免覆盖, 造成数据问题.
//CopyOnWrite使用的是Lock锁,读操作没有锁,而且不需要扩容 CopyOnWrite更快
// Vector使用的是synchronized, 所有操作都上锁,而且需要扩容
List<String> list= new ArrayList<>();
for (int i = 1; i <=10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
CopyOnWrite写操作 : 向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
CopyOnWrite使用的是ReentrantLock锁, 读操作没有锁, 而且不需要扩容 CopyOnWrite更快
Vector使用的是synchronized, 所有操作都上锁, 而且需要扩容
//java.util.ConcurrentModificationException 并发修改异常
public class SetTest {
public static void main(String[] args) {
/**
* 解决方案:
* 1. Set set= Collections.synchronizedSet(new HashSet<>());
* 2. Set set= new CopyOnWriteArraySet<>();
*/
Set<String> set= new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
解决方案:
Set set= Collections.synchronizedSet(new HashSet<>());
Set set= new CopyOnWriteArraySet<>();
HashSet底层是什么? 就是HashMap HashMap底层是数组+链表+红黑树
public HashSet() {
map = new HashMap<>();
}
//add 本质就是map.put key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//不变的值
//java.util.ConcurrentModificationException 并发修改异常
public class MapTest {
public static void main(String[] args) {
// 默认等价于什么? new HashMap<>(16,0.75); //容量,负载因子
/**
*解决方案:
* 1. Map map=new ConcurrentHashMap<>();
*/
Map<String,String> map=new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
ConcurrentHashMap是如何保证线程安全的 ?
jdk1.7中是采用Segment + HashEntry + ReentrantLock的方式进行实现的,Segment是继承ReentrantLock充当锁的角色, 每一个ConcurrentHashMap都包含了一个Segment数组,在Segment数组中每一个Segment对象则又包含了一个HashEntry数组 , 一个Segment守护若干个HashEntry, 从而保证线程安全 !
jdk1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现。用Volatile来保证可见性, 所以get()方法不用加锁 !
put操作 : 先判断hash映射的位置是否为null , 如果为null,直接通过CAS自旋操作,插入元素成功,则直接返回 . 如果映射的位置值为MOVED(-1),则直接去协助扩容 . 排除以上条件后,尝试对链头Node节点f加Synchronized锁 ,加锁成功后,链表通过尾插遍历,进行插入或替换。红黑树通过查询遍历,进行插入或替换。之后如果当前链表节点数量大于阈值,则调用treeifyBin函数,转换为红黑树最后通过调用addCount,执行CAS操作,更新数组大小,并且判断是否需要进行扩容
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new MyThread()).start(); //class MyThread implements Runnable
//new Thread(new FutureTask()).start();
//new Thread(new FutureTask(Callable)).start();
MyThread myThread=new MyThread();
FutureTask futureTask = new FutureTask(myThread); //适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
Integer i = (Integer) futureTask.get(); //获取Callable返回结果 可能会阻塞
System.out.println(i);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()");
//耗时的操作
return 123;
}
}
如果创建了两个线程, 使用了同一个FutureTask, FutureTask内部维护了一个state, 在线程B执行run方法的时候,futureTask内部的state已经不是new状态,直接return ! ( 结果只输出了一次call() )
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
//计数器
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//总数是10 10个人打王者
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <=10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"加载完成");
countDownLatch.countDown();//-1
},String.valueOf(i)).start();
}
//等10个人全部加载完成了才能开启游戏
countDownLatch.await();//等待计数器归零再向下执行!
System.out.println("开始游戏");
}
}
countDownLatch.countDown(); // -1
countDownLatch.await(); //等待计数器归零再向下执行!
每次有线程调用countDown() 计数器数量就会-1, 等计数器变为0, await()就会被唤醒, 继续执行!
字面意思回环栅栏,通过它可以实现让一组线程相互等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
一组线程直到多少个线程或者任务等待至barrier状态再同时执行后续任务!
当7个线程都到达barrier状态后,会从7个线程中选择一个线程去执行Runnable。 ( 即哪个线程抢到资源就哪个线程先执行! )
public class CyclicBarrierTest {
public static void main(String[] args) {
//集齐七颗龙珠召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
//召唤神龙线程,要7个线程才可以
System.out.println("召唤神龙成功");
});
for (int i = 1; i <=7; i++) {
final int temp=i;//要定义为常量才能在内部类里使用
new Thread(()->{
System.out.println("收集了"+temp+"星龙珠");
try {
cyclicBarrier.await();//等待
//共同等待7颗龙珠收集完成后,先召唤神龙,再随机许7次愿望
System.out.println("第"+temp+"次许愿");
//哪个线程先抢到就运行哪个线程的许愿
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
public class SemaphoreTest {
public static void main(String[] args) {
//线程数量 : 停车位! 限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
semaphore.acquire();//获取许可
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);//停两秒
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放许可
}
},String.valueOf(i)).start();
}
}
}
下面对上面说的三个辅助类进行一个总结:
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
ReadWriteLock
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCache myCache = new MyCache();
MyCacheLock myCacheLock=new MyCacheLock();
//写入
for (int i = 1; i <=5; i++) {
final int tmp=i;
new Thread(()->{
myCacheLock.put(tmp+"",tmp);
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <=5; i++) {
final int tmp=i;
new Thread(()->{
myCacheLock.get(tmp+"");
},String.valueOf(i)).start();
}
}
}
//自定义缓存 不加锁会造成写的时候又有另外的线程插进来写
class MyCache{
private volatile Map<String,Object> map=new HashMap<>();
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完成");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功");
}
}
//加锁的
class MyCacheLock{
private volatile Map<String,Object> map=new HashMap<>();
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();//读写锁 更加细粒度的控制
public void put(String key,Object value){
readWriteLock.writeLock().lock();//写,写的时候上锁,只允许一个线程写
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key){
readWriteLock.readLock().lock();//读锁,实现读写分离,读写互斥,防止读的时候其他线程写
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
readWriteLock.writeLock().lock();//写,写的时候上锁,只允许一个线程写
readWriteLock.readLock().lock();//读锁,实现读写分离,读写互斥,防止读的时候其他线程写
读读共享, 读写互斥, 写写互斥!
独占锁 ( 写锁 ) 一次只能被一个线程占用
共享锁 ( 读锁 ) 多个线程可以同时占用
阻塞队列 :
应用场景 : 多线程并发处理, 线程池 !
四组API
方式 | 抛出异常 | 有返回值, 不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(“元素”,2,TimeUnit.SECONDS) |
移除 | remove() | poll() | take() | poll(2,TimeUnit.SECONDS) |
判断队列头 | element() | peek() |
add 和remove
public static void test1(){
//队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//IllegalStateException: Queue full 满了再添加就会抛出异常
//System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//NoSuchElementException 没有元素再移除就会抛出异常
//System.out.println(blockingQueue.remove());
}
offer和poll
public static void test2(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));// false 不抛出异常
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null 不抛出异常
}
put和take
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d");//满了会一直阻塞等待
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//System.out.println(blockingQueue.take());//没有元素会一直阻塞等待
}
超时等待
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d",2, TimeUnit.SECONDS));//满了就等待2秒,超时就输出false
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));//没有元素就等待2秒,超时输出null
}
SynchronousQueue 同步队列
没有容量
进去一个元素, 必须等待取出来才能再放进去一个元素!
put take
public class SynchronousQueueTest {
public static void main(String[] args) {
BlockingQueue<String> queue = new SynchronousQueue<>();//同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
queue.put("1");
//要等1取出来才能put2
System.out.println(Thread.currentThread().getName()+"put 2");
queue.put("2");
//要等2取出来才能put3
System.out.println(Thread.currentThread().getName()+"put 3");
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
程序的运行, 每次 : 占用系统的资源 ! 优化资源的使用 !=> 池化技术
线程池, 连接池, 内存池, 对象池… 创建, 销毁, 十分浪费资源
池化技术 : 事先准备好一些资源, 有人要用, 就来我这里拿, 用完之后还给我.
线程池的好处 :
线程复用, 可以控制最大并发数, 管理线程
Executors.newSingleThreadExecutor();//单个线程
Executors.newFixedThreadPool(5);//固定大小的线程池
Executors.newCachedThreadPool();//可缓存的线程池,大小可变
public class Demo1 {
public static void main(String[] args) {
//ExecutorService executor = Executors.newSingleThreadExecutor();//单个线程
//ExecutorService executor = Executors.newFixedThreadPool(5);//固定大小的线程池
ExecutorService executor = Executors.newCachedThreadPool();//可缓存的线程池,大小可变
try {
for (int i = 0; i < 10; i++) {
//使用线程池创建线程
executor.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完要关闭
executor.shutdown();
}
}
}
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
本质都是调用ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大线程池大小
long keepAliveTime, //超时了, 没人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂,创建线程的
RejectedExecutionHandler handler) {//拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
一般只开启核心线程, 超出了就进去阻塞队列, 如果阻塞队列满了就开启剩余的非核心线程. 当非核心线程也满了就进行拒绝策略!
/**
* 拒绝策略
* new ThreadPoolExecutor.AbortPolicy() //阻塞队列满了再要加线程就不处理,直接抛出异常
*new ThreadPoolExecutor.CallerRunsPolicy() //如果阻塞队列满了,并且executor还没关闭,就run()!由哪个线程创建就由哪个线程处理,
* new ThreadPoolExecutor.DiscardPolicy() //阻塞队列满了就尝试抛弃
* new ThreadPoolExecutor.DiscardOldestPolicy(); //阻塞队列满了就会将队列头移除再执行当前任务 (先poll()再execute())
*/
public class Demo2 {
public static void main(String[] args) {
//自定义线程池
//最大线程池大小该如何定义
//1. CPU 密集型 看CPU的核心数,就定义多少,可以保证CPU效率最高,用于计算较多的项目
//Runtime.getRuntime().availableProcessors() //CPU核心数,可用作定义最大线程池大小
//2. IO 密集型 用于IO操作较多的项目
//最大线程池大小>程序中十分耗IO的线程数
System.out.println(Runtime.getRuntime().availableProcessors());//查看CPU核心数
ExecutorService executor = new ThreadPoolExecutor(
2, //核心线程池大小
Runtime.getRuntime().availableProcessors(), //最大线程池大小
3, //超时时间
TimeUnit.SECONDS, //单位
new LinkedBlockingDeque<>(3), //阻塞队列
Executors.defaultThreadFactory(), //线程工程
new ThreadPoolExecutor.AbortPolicy()); //拒绝策略
try {
//最大承载: Deque + max (3+5)
//超出最大承载就会抛出异常 : RejectedExecutionException
for (int i = 1; i <=9; i++) {
//使用线程池创建线程
final int temp=i;
executor.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok"+temp);
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完要关闭
executor.shutdown();
}
}
}
最大线程池大小该如何定义
函数式接口 : 只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 超级多FunctionalInterface
// 简化编程模型, 在新版本的框架底层大量应用
// foreach(消费者的函数式接口)
Function 函数式接口,有一个输入参数,有一个输出
// Function 函数式接口,有一个输入参数,有一个输出
// 只要是函数式接口都可以用Lambda表达式简化
public class Demo1 {
public static void main(String[] args) {
// Function function = new Function() {
// @Override
// public String apply(String str) {
// return str+".com";
// }
// };
Function<String, String> function=(str)->{return str+".com";};//Lambda表达式
//可以更简化Function function=str->str+".com";
System.out.println(function.apply("asd"));
}
}
Predicate断定型接口 : 有一个输入参数,返回值只能是布尔值
//断定型接口 : 有一个输入参数,返回值只能是布尔值
public class Demo2 {
public static void main(String[] args) {
// Predicate predicate = new Predicate() {
// //判断字符串是否空
// @Override
// public boolean test(String str) {
// return str.isEmpty();
// }
// };
Predicate<String> predicate = str->str.isEmpty();//Lambda表达式
System.out.println(predicate.test("asd"));
}
}
Consumer 消费型接口 : 只有输入,没有返回值
//Consumer 消费型接口 : 只有输入,没有返回值
public class Demo3 {
public static void main(String[] args) {
// Consumer consumer = new Consumer() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer<String> consumer=(str)->{System.out.println(str);};//Lambda表达式
consumer.accept("asd");
}
}
Supplier 供给型接口,没有参数,只有返回值
//Supplier 供给型接口,没有参数,只有返回值
public class Demo4 {
public static void main(String[] args) {
// Supplier supplier = new Supplier() {
// @Override
// public Integer get() {
// return 1024;
// }
// };
Supplier<Integer> supplier =()->1024;//Lambda表达式
System.out.println(supplier.get());
}
}
大数据 : 存储 + 计算
集合 MySQL 本质就是存储东西的;
计算都应该交给流来操作!
/**
* 题目要求 : 一分钟内完成此题,只能用一行代码实现
* 现有5个用户 ! 筛选
* 1. ID必须是偶数
* 2. 年龄必须大于等于18
* 3. 用户名转为大写字母
* 4. 用户名字母倒序
* 5. 只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 20);
User u3 = new User(3, "c", 19);
User u4 = new User(4, "d", 18);
User u5 = new User(5, "e", 17);
//集合就是存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//计算交给Stream流
list.stream()//链式编程
.filter(user -> {return user.getId()%2==0;})//只返回Id%2==0的user
.filter(user -> {return user.getAge()>=18;})//只返回Age>=18的user
.map(user -> {return user.getName().toUpperCase();})//返回Name大写
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})//降序
.limit(1)//只有一条数据
.forEach(System.out::println);
}
}
ForkJoin 在 JDK 1.7 , 并行执行任务 ! 提高效率 . 大数据量 !
大数据 : Map Reduce ( 把大任务拆分为小任务 )
ForkJoin 特点 : 工作窃取
这里面维护的都是双端队列
B线程执行完后把A线程的任务也拿过来执行, 提高效率!
ForkJoin
/**
* 使用ForkJoin
* 1. ForkJoinPool 通过它来执行
* 2. 计算任务 ForkJoinPool.execute(ForkJoinTask task)
* 3. 计算类要继承ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private long start,end;
private long temp=10000;//临界值
public ForkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
if(end-start>temp){//大于临界值就使用ForkJoin
// 分支合并计算
long mid=(start+end)/2;//中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, mid);
task1.fork();// 拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(mid+1, end);
task2.fork();// 拆分任务,把任务压入线程队列
return task1.join()+task2.join();//获取结果
}else{
long sum=0;
for (long i = start; i <= end; i++) {
sum+=i;
}
return sum;
}
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();//717
test2();//657
test3();//630
}
public static void test1(){//普通循环
long sum=0;
long start = System.currentTimeMillis();
for (long i = 0; i <=10_0000_0000; i++) {
sum+=i;
}
long end=System.currentTimeMillis();
System.out.println("sum="+sum+" ,时间:"+(end-start));
}
public static void test2() throws ExecutionException, InterruptedException {//ForkJoin
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo task = new ForkJoinDemo(0, 10_0000_0000);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
long sum = submit.get();
long end=System.currentTimeMillis();
System.out.println("sum="+sum+" ,时间:"+(end-start));
}
public static void test3(){//Stream流
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0, 10_0000_0000).parallel().reduce(0, Long::sum);
long end=System.currentTimeMillis();
System.out.println("sum="+sum+" ,时间:"+(end-start));
}
}
同步回调
我们常用的一些请求都是同步回调的,同步回调是阻塞的,单个的线程需要等待结果的返回才能继续执行。
异步回调
有的时候,我们不希望程序在某个执行方法上一直阻塞,需要先执行后续的方法,那就是这里的异步回调。我们在调用一个方法时,如果执行时间比较长,我们可以传入一个回调的方法,当方法执行完时,让被调用者执行给定的回调方法。
Future 设计的初衷 : 对将来的某个事件的结果进行建模
//异步执行
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture =CompletableFuture.runAsync(()->{ //没返回值
System.out.println("异步开始");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
});
TimeUnit.SECONDS.sleep(1);
System.out.println("1111");
completableFuture.get();// 阻塞获取执行结果
//异步开始
//1111 //等待时,先异步执行别的方法,再返回结果
//ForkJoinPool.commonPool-worker-1runAsync=>Void
}
}
/**
*异步调用 : CompletableFuture
* 成功回调
* 失败回调
*/
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { //有返回值
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
int i=10/0; //故意出错
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t); //正常的返回结果 1024 错误时返回 null
System.out.println("u=>" + u); //错误信息 java.lang.ArithmeticException: / by zero
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 404; //错误时可以返回404
}).get());
}
}
内存模型描述了程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节
Java Memory Model(Java内存模型), 围绕着在并发过程中如何处理可见性、原子性、有序性这三个特性而建立的模型。
JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。
关于JMM的一些同步的约定 :
线程 工作内存 主内存
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可再分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题 : 程序或者是线程不知道主内存的值以及被修改过了
Volatile 是Java 虚拟机提供轻量级的同步机制
1. 保证可见性
当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从主内存重新读取最新值。
当我们使用volatile关键字修饰某个变量之后,就相当于告诉CPU:我这个变量需要使用MESI协议( 缓存一致性协议 )和总线嗅探机制处理。从而也就保证了可见性
public class JMMDemo {
//不加volatile程序就会一直等待
//加了volatile可以保证可见性,让线程1知道主内存已经变化了,并且从主存中拿到最新值
private volatile static int num=0;
public static void main(String[] args) {//mian线程
new Thread(()->{//线程1 对主内存的变化是不知道的
while(num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
2. 不保证原子性
原子性 : 即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子性就像数据库里面的事务一样,他们是一个团队,同生共死。
线程A在执行任务的时候, 不能被打扰的, 也不能被分割. 要么同时成功, 要么同时失败.
//不保证原子性
public class VDemo2 {
// volatile 不保证原子性
private volatile static int num=0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上num应该等于20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){//线程>2 main gc
Thread.yield();//使当前线程从运行状态变为就绪状态, 线程礼让,让上面的线程先执行
}
System.out.println(Thread.currentThread().getName()+" "+num);//main 18485
}
}
如果不加lock和synchronized , 怎么样保证原子性
使用原子类, 解决原子性问题
//不保证原子性
public class VDemo2 {
// volatile 不保证原子性
//原子类的 Integer
private volatile static AtomicInteger num=new AtomicInteger();
public static void add(){
//num++;
num.getAndIncrement();//+1 用的是底层的CAS保证原子性
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){//线程>2 main gc
Thread.yield();//使当前线程从运行状态变为就绪状态, 线程礼让,让上面的线程先执行
}
System.out.println(Thread.currentThread().getName()+" "+num);//main 20000
}
}
这些类的底层都直接和操作系统挂钩 ! 在内存中修改值 !
指令重排
什么是 指令重排 : 你写的程序, 计算机并不是按照你写的那样去执行的.
源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–> 执行
指令在进行指令重排的时候, 考虑 : 数据之间的依赖性 !
int x =1; //1
int y =2; //2
x = x+5; //3
y= X*x; //4
我们期望的 : 1234 但是可能执行的时候变成 2134 1324
但不可能是 4123
可能造成影响的结果 : a b x y 这四个值默认都是0;
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常结果 : x=0; y=0; 但是可能由于指令重排
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排的不同结果 : x=2; y=1;
volatile可以避免指令重排 :
内存屏障. CPU指令. 作用:
Volatile 是可以保证可见性, 不能保证原子性, 由于内存屏障, 可以保证避免指令重排的现象产生 !
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
饿汉式
它基于 classloader 机制避免了多线程的同步问题
//饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1=new byte[1024*1024];
private byte[] data2=new byte[1024*1024];
private byte[] data3=new byte[1024*1024];
private byte[] data4=new byte[1024*1024];
private Hungry(){ //让构造函数为 private,这样该类就不会被实例化
}
private final static Hungry HUNGRY=new Hungry();
public static Hungry getInstance(){ //获取唯一可用的对象
return HUNGRY;
}
}
懒汉式,线程不安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式,线程安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
DCL懒汉式 , 线程安全
// 懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static LazyMan lazyMan;//加volatile避免指令重排
//双重检测锁模式 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan(); // 不是一个原子性的操作
/**
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间(对象地址引用)
*
* 本来是123
* 指令重排132 A
* B线程发现它占用空间就不为空直接返回,但是还没有执行构造方法
*/
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
什么是CAS
public class CASDemo {
//CAS compareAndSet : 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(18);
//期望, 更新
//public final boolean compareAndSet(int expect, int update)
// 如果期望的值达到了,就更新,否则就不更新, CAS 是CPU的并发原语
System.out.println(atomicInteger.compareAndSet(18, 21));//true
System.out.println(atomicInteger.get());//21
System.out.println(atomicInteger.compareAndSet(18, 21));//false
System.out.println(atomicInteger.get());//21
}
}
Unsafe类
CAS : 从主存中拿到值, 并和预期值进行比较, 如果相同则执行交换返回true, 否则返回false; 因为使用了volatile保证了可见性, 所以从主存拿到的是最新值, 保证了原子性.
CAS增加 : 比较当前工作内存中的值和主内存中的值 , 如果这个值是期望的, 那么则执行操作 ! 如果不是就重新获取主内存中的值再比较, 一直循环 !
CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。
缺点 :
CAS : ABA问题
A虽然在主存中拿到的是1, 但是不是原来的1,而是改变了的1.
public class CASDemo {
//CAS compareAndSet : 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(18);
//相对于我们平时的写的SQL : 乐观锁 !
//期望, 更新
//public final boolean compareAndSet(int expect, int update)
// 如果期望的值达到了,就更新,否则就不更新, CAS 是CPU的并发原语
//===============捣乱的线程==================
System.out.println(atomicInteger.compareAndSet(18, 21));//true
System.out.println(atomicInteger.get());//21
System.out.println(atomicInteger.compareAndSet(21, 18));//true
System.out.println(atomicInteger.get());//18
//===============期望的线程==================
System.out.println(atomicInteger.compareAndSet(18, 66));//true
System.out.println(atomicInteger.get());//66
}
}
解决ABA问题, 引入原子引用 ! 对应的思想 : 乐观锁
带版本号的原子操作 !
public class CASDemo {
//CAS compareAndSet : 比较并交换
public static void main(String[] args) {
//如果泛型是一个包装类, 注意对象的引用问题. Integer -128 127
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(18, 1); //值18 版本号1
//相对于我们平时的写的SQL : 乐观锁 !
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得目前的版本号 1
System.out.println("A1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(18, 21,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); //true
System.out.println("A2=>"+atomicStampedReference.getStamp()); //A2=>2
System.out.println(atomicStampedReference.compareAndSet(21, 18,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));//true
System.out.println("A3=>"+atomicStampedReference.getStamp());//A3=>3
},"A").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得目前的版本号 1
System.out.println("B1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(18, 66,
stamp, stamp + 1));//false
System.out.println("B1=>"+atomicStampedReference.getStamp());//B1=>3
},"B").start();
}
}
注意 :
Integer 使用了对象缓存机制, 默认范围是 -128 ~ 127 , 推荐使用静态工厂方法 valueOf 获取对象实例, 而不是new, 因为valueOf使用缓存, 而new一定会创建新的对象分配新的内存空间;
公平锁 : 非常公平, 不能够插队, 必须先来后到 !
非公平锁 : 非常不公平, 可以插队 ( 默认都是非公平 )
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
//非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁 ( 递归锁 )
可重入性:同一个线程的外层函数获得锁后,内层函数可以直接获取该锁。
Synchronize
//Synchronize
public class Demo1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
//Asms
//Acall
//Bsms
//Bcall
//A的sms拿到锁的,A的call也拿到了锁,要等A的sms释放,A的call也释放,B才能拿到锁
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call();// 这里也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
Lock
public class Demo2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
//Asms
//Acall
//Bsms
//Bcall
//A的sms拿到锁的,A的call也拿到了锁,要等A的sms释放,A的call也释放,B才能拿到锁
}
}
class Phone2{
Lock lock=new ReentrantLock();
public void sms(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"sms");
call();// 这里也有锁
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
自定义自旋锁测试
//自旋锁
public class SpinLockDemo {
//int 0
//Thread null
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加锁
public void myLock(){
Thread thread=Thread.currentThread();
//自旋锁
while(!atomicReference.compareAndSet(null,thread)){//如果不为空就一直循环等待
}
System.out.println(Thread.currentThread().getName()+"=>myLock");
}
//解锁
public void myUnLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"=>myUnLock");
atomicReference.compareAndSet(thread,null);
}
}
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock=new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用CAS
SpinLockDemo lock = new SpinLockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T2").start();
//T1=>myLock
//T1=>myUnLock
//T2=>myLock
//T2=>myUnLock
}
}
T1拿到锁之后, T2尝试拿锁开始自旋等待, 等T1解锁之后, T2才拿到锁, 然后再T2解锁 !
死锁是什么
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
产生死锁的必要条件:
预防死锁:
避免死锁
系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
解除死锁
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
死锁测试, 怎么排除死锁 :
public class DeadLockDemo {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock: "+lockA+"=>get"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock: "+lockB+"=>get"+lockA);
}
}
}
}
解决办法
死锁检测
1、Jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
2、JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
狂神说JUC : https://www.bilibili.com/video/BV1B7411L7tE?p=1