JUC并发编程学习笔记(简单易懂)1

简介: JUC并发编程学习笔记(简单易懂)

1:回顾多线程

进程和线程是什么

进程是操作系统分配资源的最小单元,而线程是cpu调度的最小单元。

  • java默认有几个线程

2个,main线程和GC线程(GC垃圾回收机制)

  • java可以开启线程么

不能

  • 并发和并行

并发,多线程操作同一个资源,cpu单核,模拟多条线程,快速交替

并行,多人一起走,cpu多核,多个线程可以同时执行,线程池

package main;
public class Demo1 {
    public static void main(String[] args) {
        //获取cpu的核数
        //cpu密集型,io密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

示例:

6a296ccaed8e4a92a0b6f1171c2434f0.png

线程有几个状态:

Thread.State

public enum State {
        /**
         * 新建状态
         */
        NEW,
        /**
         * 运行状态
         */
        RUNNABLE,
        /**
         * 堵塞状态
         */
        BLOCKED,
        /**
         * 等待状态
         */
        WAITING,
        /**
         * 超时等待
         */
        TIMED_WAITING,
        /**
         * 终止状态
         */
        TERMINATED;
    }

1.1 wait/sleep 的区别

1.来自不同类,wait->Object,sleep->Thread

2.锁的释放,wait->释放锁,sleep->不释放锁

3.使用范围,wait->同步代码块,sleep->任何地方

1.2 synchronized锁

package main;
/*
* 真正的多线程开发,公司中的开发,降低耦合型
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1. 属性  方法
* */
public class TicketSale {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源丢入线程
        Ticket ticket = new Ticket();
        //@FunctionalInterface 函数式接口,jkd1.8 lambda 表达式(参数)->{代码}
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}
//资源类OOP
class  Ticket{
    //属性  方法
    private int number = 30;
    //卖票的方式
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
        }
    }
}

1.3 Lock 锁

Class ReentrantLock 构造方法
public ReentrantLock() 创建一个ReentrantLock的实例。 这相当于使用ReentrantLock(false)
public ReentrantLock(boolean fair) 根据给定的公平政策创建一个 ReentrantLock的实例. fair - true如果此锁应使用合理的订购策略

09a585be58444ab3b65e2715d506616f.png

1.3.1 什么是公平锁,非公平锁?

  • 非公平锁:可以插队(无参构造方法默认为非公平锁)
  • 公平锁:先来后到(有参构造方法传值true时为公平锁)
package main;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
 * 真正的多线程开发,公司中的开发,降低耦合型
 * 线程就是一个单独的资源类,没有任何附属的操作!
 * 1. 属性  方法
 * */
public class TicketSale2 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源丢入线程
        Ticket2 ticket = new Ticket2();
        //@FunctionalInterface 函数式接口,jkd1.8 lambda 表达式(参数)->{代码}
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}
/*
* lock三部曲
* 1.new ReentrantLock()
* 2.lock.lock();//加锁
* 3.finally-> lock.unlock() //解锁
* */
class  Ticket2{
    //属性  方法
    private int number = 30;
    //卖票的方式
    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();//解锁
        }
    }
}
/*
公平锁
线程1购买了第10张票
线程2购买了第9张票
线程3购买了第8张票
线程1购买了第7张票
线程2购买了第6张票
线程3购买了第5张票
线程1购买了第4张票
线程2购买了第3张票
线程3购买了第2张票
线程1购买了第1张票
非公平锁
线程1购买了第10张票
线程1购买了第9张票
线程1购买了第8张票
线程1购买了第7张票
线程1购买了第6张票
线程1购买了第5张票
线程1购买了第4张票
线程1购买了第3张票
线程1购买了第2张票
线程1购买了第1张票
*/

1.4  synchronized 和 Lock 区别

  1. lock是一个接口,而synchronized是java的一个关键字。
  2. synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。
  3. Synchronized 可重入锁,不可以中断的,非公平; Lock ,可重入锁,可以判断锁,非公平(可以自己设置);
  4. Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
  5. Synchronized 线程1(获得锁,阻塞)、绩程2(等待,傻傻的等) ; Lock锁就不一定会等待下去;
  6. Synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁

synchronized

8893784728e24951b23c387d8207e2c8.png

Lock

efa1be47130446ec84ffd6bdcb1b8ac1.png

1.5 synchronized 版的生产者和消费者

package pc;
/*
* 线程之间的通信问题:生产者和消费者问题 等待唤醒 ,通知唤醒
* 线程交替执行  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 (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
//等待 业务 通知
class Data{//数字  资源类
    private  int number = 0;
    public  synchronized void increment() throws InterruptedException {
        if (number != 0) {
            //等待
            this.wait();
        }
        number++;
        //通知其他线程,我+1完毕了
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
}

1.6 防止虚假唤醒问题

四个线程时(两个生产者两个消费者)出现虚假唤醒问题,需要使用while来替换if来判断唤醒后是否可以继续执行

理解 : 拿两个加法线程A、B来说,比如A先执行,执行时调用了wait方法,那它会等待,此时会释放锁,那么线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行。如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行

package pc;
/*
* 线程之间的通信问题:生产者和消费者问题 等待唤醒 ,通知唤醒
* 线程交替执行  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 (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data{//数字  资源类
    private  int number = 0;
    public  synchronized void increment() throws InterruptedException {
        while (number != 0) {
            //等待
            this.wait();
        }
        number++;
        //通知其他线程,我+1完毕了
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
}

1.7 JUC 版的生产者消费者问题 Lock Condition

await:

33413370398d47e28ce44619273cdc69.png

signalAll:

5ba50c0396c84f01bf8c7f44f8680fc3.png

package pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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 (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data2 {//数字  资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
//    condition.await();//等待
//    condition.signalAll();//唤醒全部
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number != 0) {
                //等待
                condition.await();
            }
            number++;
            //通知其他线程,我+1完毕了
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

1.8 Condition 实现精准通知唤醒

await:

33413370398d47e28ce44619273cdc69.png

signal:

df26dc7b967b4506972092cb3c3fb609.png

117ee27010b747058ab83815369a4d55.png

package pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        }, "C").start();
    }
}
class Data3 {// 资源类 Lock
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1;
    public void printA() {
        lock.lock();
        try {
            //业务,判断->执行->通知
            while (number != 1) {
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "--->A");
            //唤醒指定的人:B
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB() {
        lock.lock();
        try {
            //业务,判断->执行->通知
            while (number != 2) {
                //等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "--->B");
            //唤醒指定的人:C
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC() {
        lock.lock();
        try {
            //业务,判断->执行->通知
            while (number!=3){
                //等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"--->C");
            number=1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

1.9 八锁现象问题理解

同一把锁:synchronized 锁的对象是方法的调用者(phone)

package lock;
import java.util.concurrent.TimeUnit;
/*
 * 1.标准情况下,两个线程先打印发短信还是打电话?1.发短信﹑2.打电话
 * 2.sendSms延迟4秒,两个线程先打印 发短信还是 打电话?1.发短信 2.打电话
 * */
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        //锁的存在
        new Thread(()->{
            phone.sendSms();
        },"A").start();
        TimeUnit.SECONDS.sleep(1);
        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("打电话");
    }
}

结果d445635a325845869d25b9ccad425995.png

两个对象不是用一把锁:phone,phone2

package com.example.juc.test;
import java.util.concurrent.TimeUnit;
/*
 * 3.增加了一个普通方法,先发短信还是Hello
 * 4.两个对象,两个同步方法,先发短信还是先打电话
 * */
public class Demo1 {
   public static void main(String[] args) throws InterruptedException {
      //两个对象,两个调用者,两把锁!
      Phone phone = new Phone();
      Phone phone2 = new Phone();
      //锁的存在
      new Thread(()->{
         System.out.println(Thread.currentThread().getName());
         phone.hello();
         phone.sendSms();
      },"A").start();
      TimeUnit.SECONDS.sleep(1);
      new Thread(()->{
         System.out.println(Thread.currentThread().getName());
         phone2.hello();
         phone2.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("打电话");
   }
   //这里没有锁,不是同步方法,不受锁的影响
   public void hello(){
      System.out.println("hello");
   }
}

static静态方法两把锁锁的都是class模板:class phone

Phone唯一的一个Class对象

package com.example.juc.test;
import java.util.concurrent.TimeUnit;
/*
 * 5.增加两个静态的同步方法,只有一个对象,先打印 发短信还是打电话
 * 6.两个对象!增加两个静态的同步方法, 先打印 发短信还是打电话
 * */
public class Demo1 {
   public static void main(String[] args) throws InterruptedException {
      //两个对象的Class类模板只有一个,static,锁的是Class
      Phone phone = new Phone();
      Phone phone2 = new Phone();
      //锁的存在
      new Thread(()->{
         phone.sendSms();
      },"A").start();
      TimeUnit.SECONDS.sleep(1);
      new Thread(()->{
         phone2.call();
      },"B").start();
   }
}
//Phone唯一的一个Class对象
class Phone{
   //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("打电话");
   }
}

结果:

8a12d43db55948c388d2b850b1049b99.png

不是同一把锁,A线程的锁是phone类,B线程的锁是phone2

package com.example.juc.test;
import java.util.concurrent.TimeUnit;
/*
 * 7.1个静态的同步方法,1个普通的同步方法,1个对象,先打印谁
 * 8.1个静态的同步方法,1个普通的同步方法,2个对象,先打印谁
 * */
public class Demo1 {
   public static void main(String[] args) throws InterruptedException {
      //两个对象的Class类模板只有一个,static,锁的是Class
      Phone phone = new Phone();
      Phone phone2 = new Phone();
      //锁的存在
      new Thread(() -> {
         phone.sendSms();
      }, "A").start();
      TimeUnit.SECONDS.sleep(1);
      new Thread(() -> {
         phone2.call();
      }, "B").start();
   }
}
//Phone唯一的一个Class对象
class Phone {
   //静态的同步方法  锁的是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("打电话");
   }
}

结果:

453355277fb0421ab713ea7f4a41b6c8.png

总结:

  • new this      具体的一个手机
  • static Class 唯一的一个类模板

目录
相关文章
|
安全 Java
并发编程系列教程(02) - 多线程安全
并发编程系列教程(02) - 多线程安全
30 0
|
存储 缓存 安全
【并发编程的艺术】JAVA并发机制的底层原理
Java代码的执行过程:代码编译->Java字节码->类加载器加载到JVM->JVM执行字节码,最终转化为汇编指令在CPU中执行。所以,Java中使用的并发机制,也依赖于JVM的实现和CPU指令。本章将重点描述这两个关键字的实现,并由此深入探索操作系统底层原理。
75 0
|
3月前
|
安全 数据库连接 API
C#一分钟浅谈:多线程编程入门
在现代软件开发中,多线程编程对于提升程序响应性和执行效率至关重要。本文从基础概念入手,详细探讨了C#中的多线程技术,包括线程创建、管理及常见问题的解决策略,如线程安全、死锁和资源泄露等,并通过具体示例帮助读者理解和应用这些技巧,适合初学者快速掌握C#多线程编程。
83 0
|
Java API 调度
并发编程系列教程(01) - 多线程基础
并发编程系列教程(01) - 多线程基础
73 0
|
7月前
|
安全 Java
多线程(进阶三:JUC)
多线程(进阶三:JUC)
72 0
|
安全
JUC并发编程学习笔记(简单易懂)2
JUC并发编程学习笔记(简单易懂)
65 0
JUC并发编程学习笔记(简单易懂)2
|
Web App开发 安全 Java
JUC高并发编程(一)——JUC基础知识
JUC高并发编程(一)——JUC基础知识
142 0
|
存储 SQL 缓存
JUC 并发编程学习笔记(总)
JUC 并发编程学习笔记(总)
102 0
JUC 并发编程学习笔记(总)
|
缓存 安全
并发编程学习一
并发编程学习一
80 0
|
缓存 Java API
JUC 并发编程学习笔记(中)
JUC 并发编程学习笔记(中)
77 0
JUC 并发编程学习笔记(中)