1.并发编程三要素

  1. 原子性:一个或多个操作要么全部成功,要么全部失败。
  2. 可见性:A线程对主内存的值进行修改并写回主内存后B线程能否立马就知道。
  3. 有序性:是否会禁止指令重排,比如volatile会禁止指令重排,那么volatile就可以保证有序性,即按代码编写的顺序执行。

    2.i++

    1.没有任何附加操作的i++

    ```java public class Test1 { public static void main(String[] args) {
    1. MyAdd myAdd=new MyAdd();
    2. for(int i=0;i<10;i++){
    3. new Thread(()->{
    4. for(int j=0;j<10000;j++){
    5. myAdd.add();
    6. }
    7. }).start();
    8. }
    9. try {
    10. Thread.sleep(1000);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println(myAdd.num);
    } } class MyAdd{ public int num=0; public void add(){
    1. num++;
    } } 1.我们希望输出的num时1010000,但是每次输出的值都不一样,一般都是小于1010000; 2.编写代码的时候注意两点: 1.i和j的上界要设置大一点,否则很难重现i++的问题; 2.主线程需要等待其他线程执行完再打印出num值,不等待的情况下值是没有意义的。
  1. <a name="YXtoH"></a>
  2. ## 2.加了synchronized的i++
  3. ```java
  4. public class Test1 {
  5. public static void main(String[] args) {
  6. MyAdd myAdd=new MyAdd();
  7. for(int i=0;i<10;i++){
  8. new Thread(()->{
  9. for(int j=0;j<10000;j++){
  10. myAdd.add();
  11. }
  12. }).start();
  13. }
  14. try {
  15. Thread.sleep(1000);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println(myAdd.num);
  20. }
  21. }
  22. class MyAdd{
  23. public int num=0;
  24. public synchronized void add(){
  25. num++;
  26. }
  27. }
  28. 1.在add方法上加synchronized就可以解决多线程i++的问题
  29. 2.此时最后num的值都是100,000.

3.使用Reentrantlock的i++

  1. public class Test1 {
  2. public static void main(String[] args) {
  3. MyAdd myAdd=new MyAdd();
  4. for(int i=0;i<10;i++){
  5. new Thread(()->{
  6. for(int j=0;j<10000;j++){
  7. myAdd.add();
  8. }
  9. }).start();
  10. }
  11. try {
  12. Thread.sleep(1000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(myAdd.num);
  17. }
  18. }
  19. class MyAdd{
  20. public int num=0;
  21. Lock lock=new ReentrantLock();
  22. public void add(){
  23. lock.lock();//lock()必须在后面跟try代码块
  24. try {
  25. num++;
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. } finally {
  29. lock.unlock();//unlock()必须在finally的第一行
  30. }
  31. }
  32. }
  33. 1.先创建一个Lock类型的变量;
  34. 2.然后调用该变量的lock()方法,该方法后必须紧跟try-catch-finally方法;
  35. 3.该变量的unlock()方法必须在finally的第一行。
  36. 4.此时最后num的值都是100,000.

4.使用原子类的i++

  1. public class Test1 {
  2. public static void main(String[] args) {
  3. MyAdd myAdd=new MyAdd();
  4. AtomicInteger atomicInteger=new AtomicInteger(0);
  5. for(int i=0;i<10;i++){
  6. new Thread(()->{
  7. for(int j=0;j<10000;j++){
  8. atomicInteger.getAndIncrement();
  9. }
  10. }).start();
  11. }
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println(atomicInteger);
  18. }
  19. }
  20. 1.创建AtomicInteger变量,初始值设置为0
  21. 2.直接在线程执行体中调用该变量的getAndIncrement()方法即可
  22. 3.此时最后atomicInteger的值都是100,000.

3.进程调度和线程调度

进程调度:

image.png

线程调度:

image.png

4.乐/悲观,(非)公平,(可)重入,自旋,共享,互斥锁

image.png
image.png

image.png

  1. 锁的是什么,要么锁一个方法,要么锁一个代码块。
  2. 偏向锁,轻量级锁,重量级锁都是JVM层面对锁的优化,即提高锁的获取和释放效率

5.死锁演示和解决

1.死锁代码演示

  1. public class Test1 {
  2. public static void main(String[] args) {
  3. String s1=new String("1");
  4. String s2=new String("1");
  5. new Thread(()->{
  6. synchronized (s1){
  7. System.out.println(Thread.currentThread().getName()+"已经拿到锁1了");
  8. try {
  9. Thread.sleep(10);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. synchronized (s2){
  14. System.out.println(Thread.currentThread().getName()+"已经拿到锁2了");
  15. }
  16. }
  17. },"A线程").start();
  18. new Thread(()->{
  19. synchronized (s2){
  20. System.out.println(Thread.currentThread().getName()+"已经拿到锁2了");
  21. try {
  22. Thread.sleep(10);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. synchronized (s1){
  27. System.out.println(Thread.currentThread().getName()+"已经拿到锁1了");
  28. }
  29. }
  30. },"B线程").start();
  31. System.out.println("主线程运行结束!");
  32. }
  33. }
  34. A线程已经拿到锁1
  35. 主线程运行结束!
  36. B线程已经拿到锁2
  37. 1.主线程不是守护线程,而是普通线程;
  38. 2.此时A线程和B线程由于死锁所以两个都进入阻塞状态,无法正常结束。

2.死锁四个必要条件

image.png

3.死锁解决方法

  1. public class Test1 {
  2. public static void main(String[] args) {
  3. String s1=new String("1");
  4. String s2=new String("1");
  5. new Thread(()->{
  6. synchronized (s1){
  7. System.out.println(Thread.currentThread().getName()+"已经拿到锁1了");
  8. try {
  9. Thread.sleep(10);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. synchronized (s2){
  15. System.out.println(Thread.currentThread().getName()+"已经拿到锁2了");
  16. }
  17. },"A线程").start();
  18. new Thread(()->{
  19. synchronized (s2){
  20. System.out.println(Thread.currentThread().getName()+"已经拿到锁2了");
  21. try {
  22. Thread.sleep(10);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. synchronized (s1){
  28. System.out.println(Thread.currentThread().getName()+"已经拿到锁1了");
  29. }
  30. },"B线程").start();
  31. System.out.println("主线程运行结束!");
  32. }
  33. }
  34. A线程已经拿到锁1
  35. 主线程运行结束!
  36. B线程已经拿到锁2
  37. B线程已经拿到锁1
  38. A线程已经拿到锁2
  39. 只要破坏四个必要条件中的至少一个,就可以解决死锁
  40. 比如这里就是破坏请求与保持条件。

6.(不)可重入锁

1.不可重入锁(也会导致死锁)

  1. public class Test1 {
  2. public static UnreentrantLock lock = new UnreentrantLock();
  3. public static void main(String[] args) throws InterruptedException {
  4. Test1.methodA();
  5. }
  6. public static void methodA() throws InterruptedException {
  7. lock.lock();
  8. try {
  9. System.out.println("MethodA加锁成功");
  10. methodB();
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. } finally {
  14. lock.unlock();
  15. }
  16. System.out.println("MethodA解锁成功");
  17. }
  18. public static void methodB() throws InterruptedException {
  19. lock.lock();
  20. try {
  21. System.out.println("MethodB加锁成功");
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. } finally {
  25. lock.unlock();
  26. }
  27. System.out.println("MethodB解锁成功");
  28. }
  29. }
  30. class UnreentrantLock{
  31. public boolean isLocked=false;
  32. public synchronized void lock() throws InterruptedException {
  33. while(isLocked){
  34. System.out.println(Thread.currentThread().getName()+"无法获取锁");
  35. wait();
  36. }
  37. isLocked=true;
  38. }
  39. public synchronized void unlock() {
  40. isLocked=false;
  41. notify();
  42. }
  43. }
  44. 运行输出:
  45. MethodA加锁成功
  46. main无法获取锁
  47. 然后程序一直阻塞
  48. 首先可重入锁和不可重入锁是针对同一个线程来说的;
  49. 即对同一线程而言,先成功加了第一把锁,之后又加第二把锁,如果能成功则该锁就是可重入锁;
  50. 当第一次获取Unreentrantlock这把锁之后,就会把isLocked置为true
  51. 之后再想获取这把锁时都会被阻塞在while循环中的wait方法处。
  52. 注意点:
  53. 1.我一开始写lock的时候while内部加了wait(),然后while外面就立马加了notify(),
  54. 这个是没有必要的,因为这不是生成者消费者模式,不需要去争抢锁。
  55. 2.为了跟真实的JUC包中的ReentrantLocklock方法一致,我也把lock.lock()方法放在了try外面,
  56. 此时就需要抛出异常,放在try立马是不需要抛出异常的,该异常是wait()方法触发的。
  57. 3.我们之前在使用waitnotify的时候都是在多线程的生产者消费者中实践的,
  58. 生产方法和消费方法中都会使用waitnotify方法,因为生产方法和消费方法是同级的。
  59. 而这里是单线程。可重入锁和不可重入锁是针对同一个线程来说的。

2.可重入锁

  1. public class Test1 {
  2. public static UnreentrantLock lock = new UnreentrantLock();
  3. public static void main(String[] args) throws InterruptedException {
  4. Test1.methodA();
  5. }
  6. public static void methodA() throws InterruptedException {
  7. lock.lock();
  8. try {
  9. System.out.println("MethodA加锁成功");
  10. methodB();
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. } finally {
  14. lock.unlock();
  15. }
  16. System.out.println("MethodA解锁成功");
  17. }
  18. public static void methodB() throws InterruptedException {
  19. lock.lock();
  20. try {
  21. System.out.println("MethodB加锁成功");
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. } finally {
  25. lock.unlock();
  26. }
  27. System.out.println("MethodB解锁成功");
  28. }
  29. }
  30. class UnreentrantLock{
  31. public boolean isLocked=false;
  32. public Thread lockOwner=null;
  33. public int lockCount=0;
  34. public synchronized void lock() throws InterruptedException {
  35. Thread thread=Thread.currentThread();
  36. while(isLocked && lockOwner!=thread){
  37. System.out.println(Thread.currentThread().getName()+"无法获取锁");
  38. System.out.println("当前锁状态:isLocked:"+isLocked);
  39. System.out.println("当前count数量:lockCount:"+lockCount);
  40. wait();
  41. }
  42. isLocked=true;
  43. lockOwner=thread;
  44. lockCount++;
  45. }
  46. public synchronized void unlock() {
  47. Thread thread=Thread.currentThread();
  48. if(lockOwner==thread){
  49. lockCount--;
  50. if(lockCount==0){
  51. lockOwner=null;
  52. isLocked=false;
  53. notify();
  54. }
  55. }
  56. }
  57. }
  58. 输出结果:
  59. MethodA加锁成功
  60. MethodB加锁成功
  61. MethodB解锁成功
  62. MethodA解锁成功
  63. 程序执行结束
  64. 跟不可重入锁的主要区别就是在lock方法的while判断中多加了一个thread!=lockOwner
  65. 这表明只有当锁被占用,而且当前线程等于占用锁的线程时才会阻塞,
  66. 其言外之意就是如果申请锁的线程就是占用锁的线程,则程序不会阻塞,会照常进行,这就是可重入。
  67. 我感觉对于单线程来说,这里的unlock没有体现出它的作用,即如果我把unlock都注释掉,
  68. 也不影响程序的效果。
  69. 但是实际中肯定是多线程,如果没有unlock那么其他的线程就没法加锁。
  70. 注意阻塞是while中的wait导致的阻塞,而不是while循环。

7.Synchronized底层

1.字节码层面

  1. 我们知道Synchronized可以加在普通方法,静态方法,代码块上
  2. 其实可以分成方法和代码块两个方式
  3. 我们通过字节码文件可以看出两者实现同步的不同之处
  4. image.png
  5. image.png
  6. image.png
  7. image.png

2.对象的三部分组成

image.png
image.png

3.Synchronized优化

为什么要优化?
因为原始的synchronized在线程无法申请到锁的时候就会让该线程进入阻塞状态,
则这涉及到操作系统内核模式和用户模式的切换(线程调度),代价比较高,
所以优化的核心是拒绝走来就阻塞。实在不行才阻塞。
image.png

8.CAS

1.是什么

image.png
image.png

2.cas存在的问题

image.png
image.png

9.可重入读写锁(ReentrantReadWriteLock)

链接1

10.阻塞队列

1.手写阻塞队列

  1. public class test {
  2. public static void main(String[] args) {
  3. BlockingQ blockingQ=new BlockingQ(10);
  4. for(int i=0;i<10;i++){
  5. //1.lambda表达式中不能直接使用for循环中的i
  6. int finalI = i;
  7. new Thread(()->{
  8. //2.生成者消费者最好使用while(true),否则当生产者和消费者的线程的数目不一致时会出错
  9. while(true){
  10. try {
  11. blockingQ.enqueue(String.valueOf(finalI));
  12. System.out.println("队列当前元素个数:"+blockingQ.queque.size());
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }).start();
  18. }
  19. for(int i=0;i<20;i++){
  20. new Thread(()->{
  21. while(true){
  22. try {
  23. blockingQ.dequeue();
  24. System.out.println("队列当前元素个数:"+blockingQ.queque.size());
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }).start();
  30. }
  31. }
  32. }
  33. class BlockingQ{
  34. //3.如果使用的是List引用,则无法使用LinkedList特有的removeLast方法
  35. public LinkedList<Object> queque=new LinkedList<>();
  36. public int limit;
  37. public BlockingQ(int limit){
  38. this.limit=limit;
  39. }
  40. public synchronized void enqueue(Object object ) throws InterruptedException {
  41. while(this.queque.size()==limit){
  42. System.out.println("队列满了,没法继续添加了");
  43. wait();
  44. }
  45. notifyAll();
  46. this.queque.add(object);
  47. }
  48. public synchronized Object dequeue() throws InterruptedException {
  49. while(this.queque.size()==0){
  50. System.out.println("队列空了,没法继续获取了");
  51. wait();
  52. }
  53. notifyAll();
  54. return this.queque.removeLast();
  55. }
  56. }

image.png

讨论情况(在没有while(true)的情况下)

当我们把while(true)去掉,
并且改变生成者线程和消费者线程的相对数目时,
并且在等待一定时间之后在主线程中打印阻塞队列中的元素个数。

1.生成者10,消费者20

  1. public static void main(String[] args) throws InterruptedException {
  2. BlockingQ blockingQ=new BlockingQ(10);
  3. for(int i=0;i<10;i++){
  4. int finalI = i;
  5. new Thread(()->{
  6. try {
  7. blockingQ.enqueue(String.valueOf(finalI));
  8. System.out.println("队列当前元素个数:"+blockingQ.queque.size());
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }).start();
  13. }
  14. for(int i=0;i<20;i++){
  15. new Thread(()->{
  16. try {
  17. blockingQ.dequeue();
  18. System.out.println("队列当前元素个数:"+blockingQ.queque.size());
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }).start();
  23. }
  24. Thread.sleep(2000);
  25. System.out.println(blockingQ.queque.size());
  26. }

分析:
由于消费者数目大于生成者,所以最后元素个数为0,
并且程序会阻塞在消费者线程中

2.生成者20,消费者20

分析:
程序正常结束,最后元素个数为0

3.生成者25,消费者20

分析:
程序正常结束,最后元素个数为5

4.生成者30,消费者20

分析:
程序正常结束,最后元素个数为10

5.生产者31,消费者20

分析:
由于31-20=11>阻塞队列的容量,
所以程序会阻塞在生成者线程中。
最后的元素个数为10。

2.直接用包(把我们自定义的阻塞队列换成java自带的就差不多)

  1. public class BlockingQueueTest {
  2. public static void main(String[] args) {
  3. final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3); //缓冲区允许放3个数据
  4. for(int i = 0; i < 2; i ++) {
  5. new Thread() { //开启两个线程不停的往缓冲区存数据
  6. @Override
  7. public void run() {
  8. while(true) {
  9. try {
  10. Thread.sleep((long) (Math.random()*1000));
  11. System.out.println(Thread.currentThread().getName() + "准备放数据"
  12. + (queue.size() == 3?"..队列已满,正在等待":"..."));
  13. queue.put(1);
  14. System.out.println(Thread.currentThread().getName() + "存入数据,"
  15. + "队列目前有" + queue.size() + "个数据");
  16. } catch (InterruptedException e) {
  17. // TODO Auto-generated catch block
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }.start();
  23. }
  24. new Thread() { //开启一个线程不停的从缓冲区取数据
  25. @Override
  26. public void run() {
  27. while(true) {
  28. try {
  29. Thread.sleep(1000);
  30. System.out.println(Thread.currentThread().getName() + "准备取数据"
  31. + (queue.size() == 0?"..队列已空,正在等待":"..."));
  32. queue.take();
  33. System.out.println(Thread.currentThread().getName() + "取出数据,"
  34. + "队列目前有" + queue.size() + "个数据");
  35. } catch (InterruptedException e) {
  36. // TODO Auto-generated catch block
  37. e.printStackTrace();
  38. }
  39. }
  40. }
  41. }.start();
  42. }
  43. }

3.常见的阻塞队列

image.png

11.多线程最佳实践

image.png

12.Executors线程池

链接1

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }
  7. 由于ThreadPoolExecutor中的阻塞队列是LinkedBlockingQueue,而这个队列的容量是Integer.MAX_VALUE
  8. 所以容易造成OOM
  1. public static ExecutorService newFixedThreadPool(int nThreads) {
  2. return new ThreadPoolExecutor(nThreads, nThreads,
  3. 0L, TimeUnit.MILLISECONDS,
  4. new LinkedBlockingQueue<Runnable>());
  5. }
  6. 由于ThreadPoolExecutor中的阻塞队列是LinkedBlockingQueue,而这个队列的容量是Integer.MAX_VALUE
  7. 所以容易造成OOM
  1. public static ExecutorService newCachedThreadPool() {
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>());
  5. }
  6. 由于ThreadPoolExecutor中的最大线程数是Integer.MAX_VALUE,
  7. 所以容易造成OOM
  1. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
  2. return new ScheduledThreadPoolExecutor(corePoolSize);
  3. }
  4. public ScheduledThreadPoolExecutor(int corePoolSize,
  5. ThreadFactory threadFactory) {
  6. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
  7. new DelayedWorkQueue(), threadFactory);
  8. }
  9. 由于ThreadPoolExecutor中的最大线程数是Integer.MAX_VALUE,
  10. 所以容易造成OOM
  11. 这里的super对应的就是ThreadPoolExecutor
  12. 用法跟其他三个区别