文章目录
- 线程通信
- 概念
- 使用方式
- 案例
- 单例模式
- 阻塞式队列
- 线程池
 
- 常见的锁策略
- 乐观锁 悲观锁
- CAS
- CAS存在的问题:ABA问题
 
 
- 读写锁
- 自旋锁
- 公平锁 非公平锁
- 非公平锁
- 公平锁
 
- synchronized
- jvm对synchronized的优化:锁升级
- synchronized的其他优化
 
- Lock体系
- synchronized vs lock
 
- 独占锁vs共享锁
- 独占锁
- 共享锁
- Semaphare:信号量
- CountDownLatch
 
 
- 死锁
 
 
- 线程安全的集合
- Map:重点
- Hashtable
- ConcurrentHashMap:并发的hashmap
 
 
线程通信
背景:多线程优势是提高cpu利用率,使用要注意:执行时间比较长的任务,可能存在线程安全问题
 以上前提下,能否满足一种业务需求:需要让线程执行具有一定的顺序性

概念
线程间通信,就是一个线程以通知的方式,唤醒某些等待线程(也可以在某些条件下,让当前线程等待),这样就可以让线程间通过通信的方式满足一定的顺序性
使用方式

 线程间通过通信的方式满足一定的顺序性:
在这里插入代码public class 简单使用 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (lock) {
                        //先做一些事情
                        System.out.println("线程1:步骤1");
                        //在某些条件下,就需要等待
                        lock.wait();
                        //被唤醒,就做另一些事情
                        System.out.println("线程1:步骤2");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        /**
         * 执行顺序:
         * 线程1 synchronized -> wait
         * 线程2 synchronized -> notify
         * 线程1 wait 往下
         */
        Thread.sleep(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    //做一些事情
                    System.out.println("线程2:步骤1");
                    //通知线程1:唤醒执行
                    lock.notify();
                    //做另一些事情
                    System.out.println("线程2:步骤2");
                }
            }
        }).start();
    }
}片

案例
单例模式
更具体参考之前博客:java设计模式之单例模式
阻塞式队列
概念:
满足队列的结构和特性(链式或数组结构,先进先出),也满足线程安全:

 好处:

 生产者消费者模型
import java.util.Random;
public class 阻塞队列 {
    //循环数组:存取元素
    private int[] elements;
    //有效负荷
    private int size;
    //放元素的索引
    private int putIndex;
    //取元素的索引
    private int takeIndex;
    /**
     * @param capacity 容量
     */
    public 阻塞队列(int capacity){
        elements = new int[capacity];
    }
    /**
     * 放元素到阻塞队列:需要保证线程安全,如果队列满了,需要等待
     * @param element
     */
    public synchronized void put(int element) throws InterruptedException {
        //如果满了,就等
        while(elements.length == size){
            wait();
        }
        //如果不满,就放
        elements[putIndex] = element;
        //放的索引往后移动一位(取模是在最后一个位置就往首位移动)
        putIndex = (putIndex+1) % elements.length;
        //有效负荷+1
        size++;
        //通知wait等待的线程
        notifyAll();
    }
    /**
     * 取元素:需要保证线程安全,如果队列是空,需要等待
     * @return
     */
    public synchronized int take() throws InterruptedException {
        //如果是空,就等
        while(size == 0){
            wait();
        }
        //如果不空,就取
        int element = elements[takeIndex];
        //取的索引往后移动一位(取模是在最后一个位置就往首位移动)
        takeIndex = (takeIndex+1) % elements.length;
        //有效负荷-1
        size--;
        //通知wait等待的线程
        notifyAll();
        return element;
    }
    public static void main(String[] args) throws InterruptedException {
        阻塞队列 queue = new 阻塞队列(20);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟消费者
                while (true){
                    try {
                        int e = queue.take();
                        System.out.println("取到的数字:"+e);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        Thread.sleep(100);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模拟生产者
                while (true){
                    try {
                        queue.put(new Random().nextInt());
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}
线程池
线程池:就是初始化(new线程池)的时候,就创建一定数量的线程(不停的从线程池内部的一个阻塞队列)取任务(消费者);我们就可以在其他线程中,提交任务(生产者)到线程池了。
jdk原生的线程池api

常见的锁策略
乐观锁 悲观锁
线程冲突:多个线程并发并行的对同一个共享变量操作(存在线程安全问题)
悲观锁:以悲观的心态看待线程冲突(总有刁民要害朕,总是觉得有其他线程会同时操作共享变量)。所以每次都加锁操作共享变量
乐观锁:以乐观的心态看待线程冲突(总是觉得没有线程会同时操作共享变量)。所以每次都不加锁(程序层面),就直接操作共享变量(依赖操作系统及cpu的一些功能,来操作(里边实际有加锁))

CAS
乐观锁(加锁的一种思想,程序看是无锁操作)的一种实现

CAS存在的问题:ABA问题
ABA问题:读和写操作,比较主存中变量的值时,值没有变,都是A,但实际已经被其他线程修改过了(A=>B=>A)
解决方式:引入一个版本号的字段来解决
读写锁
优酷网站,某个用户上传了一个视频文件,这个用户可以修改(写操作),所有用户都可以播放(读操作)

 满足:读读并发,读写,写写都是互斥
自旋锁
一般会搭配乐观锁一起来实现

 通常结合CAS一起来保证线程安全的修改变量操作
CAS虽然是线程安全的无锁操作,但可能修改失败===>引入自旋的操作,满足不停的尝试修改

CAS+自旋实现线程安全无锁的++ vs synchronized加锁保证线程安全的++
 cas+自旋: 一直处于可运行态(有些地方也直接说运行态)
 所以如果尝试修改操作,能很快得到执行,效率就比较好(比较适用这种场景)。反之,不能很快得到执行,效率就差。
加锁操作: 申请锁失败会阻塞(线程状态会从运行态转变为阻塞态),锁释放以后,又会被唤醒再次竞争锁(阻塞态=>被唤醒)都有性能消耗
公平锁 非公平锁
非公平锁
synchronized申请对象锁,是竞争的方式
优点: 效率更高(不考虑执行顺序)
 缺点: 可能出现线程饥饿(某些线程长期得不到执行)的现象
公平锁
以申请锁的时间先后顺序,来获取到锁——类似排队买票的方式
缺点: 效率稍微差一些
synchronized
以对象头加锁的方式,实现线程安全:申请同一个对象锁时,多个线程间是同步互斥的
对象中有一个对象头,其中就有一个状态的字段:无锁,偏向锁,轻量级锁,重量级锁。
 synchronized申请锁成功,是后三种锁状态之一

jvm对synchronized的优化:锁升级

synchronized的其他优化
锁消除

 锁粗化:共享变量,可能被其他线程持有,但不停的执行加锁释放锁操作,jvm就可以优化为第一次加锁,全部执行完再释放锁

 
Lock体系

 
 lock:悲观锁
synchronized vs lock
1.Lock提供了非公平锁和公平锁,而synchronized只是非公平锁
 2.线程竞争锁激烈的时候,使用Lock效率更高。
 释放锁以后,synchronized是唤醒所有线程来竞争(涉及线程状态转换,就有性能消耗)。lock是唤醒一个(使用aqs来管理线程)
独占锁vs共享锁
独占锁
(1) synchronized加锁(2) Lock加锁 (3)写锁
共享锁
多个线程使用共享的锁,满足一定的条件,就可以并发并行的执行,不满足条件,就需要等待。
读锁属于共享锁
Semaphare:信号量

 使用场景:
 (1)有限资源的并发执行
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
    private static Semaphore S = new Semaphore(5);
    public static void main(String[] args) {
        //模拟停车场同时停车,但不能超出有限的车位数量
        for (int i = 0; i < 20; i++) {
            final int j = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取一个车位
                        S.acquire();
                        //停车以后做其他事情
                        System.out.println(j);
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        //出停车场,归还车位
                        S.release();
                    }
                }
            }).start();
        }
    }
}
(2)多个线程并发执行,需要全部执行完,某个线程再执行后续的代码
import java.util.concurrent.Semaphore;
public class SemaphoreDemo2 {
    private static Semaphore S = new Semaphore(0);
    public static void main(String[] args) throws InterruptedException {
        //模拟停车场同时停车,但不能超出有限的车位数量
        for (int i = 0; i < 5; i++) {
            final int j = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(j);
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        S.release();
                    }
                }
            }).start();
        }
        S.acquire(5);
        System.out.println("main");
    }
}
System.out.println(“main”);会等到5个线程执行完后再执行。
CountDownLatch

 Semaphore vs CountDownLatch
 单纯的从api角度看,CountDownLatch的资源数只能减,semaphore是可以加也可以减。
死锁
双方都持有对方需要的锁,而发生双方无限期的等待锁释放
死锁产生的四个必要条件:

 如何解决死锁问题:
破坏这个循环等待的条件 : 比如大家按照一定的顺序来申请锁
线程安全的集合
Map:重点
Hashtable
底层数据结构:数组+链表 相当于锁整个数组
 特性: 所有方法都是synchronzied加锁保证线程安全
 缺点: 效率不高
ConcurrentHashMap:并发的hashmap
1.8的实现
 底层数据:数组+链表+红黑树

1.7的实现:基于数组+链表
 实现方式:分段锁(Segment)



















