JUC学习笔记——01. 线程, 进程和Lock锁

1. 什么是JUC

JUC就是java.util.concurrent下面的类包,专门用于多线程的开发。

2. 线程和进程

1. 概念
  1. 进程: 运行的程序, 一个线程往往可以包含多个线程

    java默认有两个线程: main线程和GC线程

  2. 线程

2. 并发和并行
  1. 并发: 多个线程操作同一资源

    CPU一核, 快速交替, 模拟出来多条线程,

  2. 并行: 多个线程同时执行

    CPU多核, 多个线程同时执行

3. 线程的多个状态
  1. 源码

    public enum State {
        // 新生   
        NEW,
    	// 运行
        RUNNABLE,
    	// 阻塞
        BLOCKED,
    	// 等待, 死死等待
        WAITING,
    	// 等待, 超时放弃
        TIMED_WAITING,
    	// 终止
        TERMINATED;
    }
    
4. wait和sleep的区别
  1. 来自不同的类:

    wait --> Object

    sleep --> Thread

  2. 关于锁的释放

    wait会释放锁, sleep不会释放锁

  3. 使用的范围不同

    wait必须在同步代码块红使用

    sleep可以在任何地方使用

  4. 是否需要捕获异常

    wait不需要捕获异常

    sleep需要捕获异常

3. Lock锁(重点)

1. synchronized 关键字
  1. 代码

    public class SaleTicketDemo01 {
        public static void main(String[] args) {
            Ticket ticket = new Ticket();
            new Thread(() -> {
                for (int i = 0; i < 60; i++) {
                    ticket.sale();
                }
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 60; i++) {
                    ticket.sale();
                }
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 60; i++) {
                    ticket.sale();
                }
            }, "C").start();
        }
    }
    
    // 资源类: OOP
    class Ticket {
        private int num = 50;
        public synchronized void sale(){
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第 " + (num --) + "张票, 剩余: " + num);
            }
        }
    }
    
2. lock锁
  1. 代码

    public class SaleTicketDemo02 {
        public static void main(String[] args) {
            Ticket2 ticket = new Ticket2();
            new Thread(() -> {
                for (int i = 0; i < 60; i++) {
                    ticket.sale();
                }
            }, "A").start();
            new Thread(() -> {
                for (int i = 0; i < 60; i++) {
                    ticket.sale();
                }
            }, "B").start();
            new Thread(() -> {
                for (int i = 0; i < 60; i++) {
                    ticket.sale();
                }
            }, "C").start();
        }
    }
    
    class Ticket2 {
        private int num = 50;
    	// 步骤1: new ReentrantLock();
        Lock lock = new ReentrantLock();
    
        public void sale(){
            // 步骤2: 加锁, lock()
            lock.lock(); // 加锁
            try {
                // 业务代码
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出了第 " + (num --) + "张票, 剩余: " + num);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 步骤3: 解锁, unlock()
                lock.unlock();  // 解锁
            }
        }
    }
    
3. synchronized 和 Lock的区别
  1. synchronized是内置的java关键字; lock是一个java类

  2. synchronized无法判断获取锁的状态; lock可以判断是否获取到了锁

  3. synchronized会自动释放锁; lock必须手动释放锁, 如果不释放 会出现死锁

  4. synchronized如果线程1阻塞, 线程2会一直等待; lock锁不一定会等待下去.

  5. synchronized可重入锁, 不可以中断, 非公平; Lock可重入锁, 可以判断锁, 非公平(可以自己设置)

  6. synchronized适合少量的代码同步问题, lock适合锁大量的代码

4. 生产者和消费者
1. 传统方式
  1. 普通情况下可以用if判断

    public class Provider {
        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 num = 5;
        // +1
        public synchronized void increment() throws InterruptedException {
            if (num != 0) {
                // 等待
                this.wait();
            }
            // 业务
            num++;
            System.out.println(Thread.currentThread().getName() + " => " + num);
    
            // 通知其他线程
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            if (num == 0) {
                // 等待
                this.wait();
            }
    
            // 业务
            num--;
            System.out.println(Thread.currentThread().getName() + " => " + num);
            // 通知其他线程
            this.notifyAll();
        }
    }
    
  2. 如果是多个生产者和消费者, 则需要用while

    if可能会导致虚假唤醒

    public class Provider {
        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 num = 5;
        // +1
        public synchronized void increment() throws InterruptedException {
            while (num != 0) {
                // 等待
                this.wait();
            }
            // 业务
            num++;
            System.out.println(Thread.currentThread().getName() + " => " + num);
    
            // 通知其他线程
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            // 这里不能用if
            while (num == 0) {
                // 等待
                this.wait();
            }
    
            // 业务
            num--;
            System.out.println(Thread.currentThread().getName() + " => " + num);
            // 通知其他线程
            this.notifyAll();
        }
    }
    
2. JUC方式
  1. 代码1

    这个代码和上一个代码执行的结果一样, 但是不能精准的通知和唤醒线程;

    public class Provider {
        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 num = 5;
    
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        // condition.await();       // 等待
        // condition.signalAll();   // 唤醒
    
        // +1
        public void increment() throws InterruptedException {
            // 加锁
            lock.lock();
            try {
                while (num != 0) {
                    // 等待
                    condition.await();
                }
                // 业务
                num++;
                System.out.println(Thread.currentThread().getName() + " => " + num);
    
                // 通知其他线程
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void decrement() throws InterruptedException {
            // 加锁
            lock.lock();
            try {
                // 这里不能用if
                while (num == 0) {
                    // 等待
                    condition.await();
                }
    
                // 业务
                num--;
                System.out.println(Thread.currentThread().getName() + " => " + num);
    
                // 通知其他线程
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    
  2. 区别

    image-20201212162718119

  3. 精准通知和唤醒线程

    这才是使用新技术的原因

    // A执行完调用B; B执行完调用C; C执行完调用D; D执行完再掉A
    public class Provider {
        public static void main(String[] args) {
            Data data = new Data();
            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 Data {
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        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() + "=> AAA");
                // 唤醒指定的线程B
                number = 2;
                condition2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        public void printB(){
            lock.lock();
            try {
                while (number != 2) {
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName() + "=> BBB");
                // 唤醒指定的线程C
                number = 3;
                condition3.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        public void printC(){
            lock.lock();
            try {
                while (number != 3) {
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName() + "=> CCC");
                // 唤醒指定的线程A
                number = 1;
                condition1.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    

    image-20201212162843444

5. 锁的相关问题

锁是什么, 如何判断锁的是谁?

1. 问题1: 加锁方法谁先执行
  1. 代码

    public class Test01 {
        // 一个线程打电话, 一个线程发消息
        // 先执行sendMsm还是先执行call
        public static void main(String[] args) {
            Phone phone = new Phone();
            new Thread(() -> {
                phone.sendMsm();
            }, "A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                phone.call();
            }, "B").start();
        }
    }
    
    class Phone{
        // synchronized: 锁的对象是方法的调用者
        // 两个方法用的是同一个锁, 谁先拿到谁执行
        public synchronized void sendMsm(){
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sendMsm");
        }
        public synchronized void call(){
            System.out.println("call");
        }
    }
    
  2. 说明

    synchronized: 锁的对象是方法的调用者
    两个方法用的是同一个锁, 谁先拿到谁执行

2. 问题2: 加锁方法和普通方法谁先执行
  1. 代码

    public class Test02 {
        // 一个线程打电话, 一个线程发消息
        // 先执行sendMsm还是先执行call
        public static void main(String[] args) {
            Phone2 phone = new Phone2();
            new Thread(() -> {
                phone.sendMsm();
            }, "A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                phone.hello();
            }, "C").start();
        }
    }
    
    class Phone2{
        public synchronized void sendMsm(){
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sendMsm");
        }
    
        public void hello(){
            System.out.println("hello");
        }
    }
    
  2. 说明

    hello是普通方法, 不受进程的影响.

    所以hello先执行

3. 问题3: 不同对象调用, 谁先执行
  1. 代码

    public class Test02 {
        // 一个线程打电话, 一个线程发消息
        // 先执行sendMsm还是先执行call
        public static void main(String[] args) {
            Phone2 phone1 = new Phone2();
            Phone2 phone2 = new Phone2();
            new Thread(() -> {
                phone1.sendMsm();
            }, "A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                phone2.call();
            }, "B").start();
        }
    }
    
    class Phone2{
        public synchronized void sendMsm(){
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sendMsm");
        }
        public synchronized void call(){
            System.out.println("call");
        }
    }
    
  2. 说明

    先执行call, 再执行sendMsm

    因为是不同的对象, 所以不是同一个锁

4. 问题4: 静态同步方法
  1. 代码

    public class Test03 {
        // 一个线程打电话, 一个线程发消息
        // 先执行sendMsm还是先执行call
        public static void main(String[] args) {
            Phone3 phone1 = new Phone3();
            Phone3 phone2 = new Phone3();
            new Thread(() -> {
                phone1.sendMsm();
            }, "A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                phone2.call();
            }, "B").start();
        }
    }
    
    class Phone3{
        public static synchronized void sendMsm(){
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sendMsm");
        }
        public static synchronized void call(){
            System.out.println("call");
        }
    }
    
  2. 说明

    调用同一个对象或者不同对象都是先执行sendMsm

    因为静态方法再类一加载就有了, 锁的是Class

5. 问题5: 静态同步方法和普通同步方法
  1. 代码

    public class Test03 {
        public static void main(String[] args) {
            Phone3 phone1 = new Phone3();
            Phone3 phone2 = new Phone3();
            new Thread(() -> {
                phone1.sendMsm();
            }, "A").start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            new Thread(() -> {
                phone2.call();
            }, "B").start();
        }
    }
    
    class Phone3{
        public static synchronized void sendMsm(){
            
            System.out.println("sendMsm");
        }
        public synchronized void call(){
            System.out.println("call");
        }
    }
    
  2. 说明

    先执行静态同步方法, 再执行普通同步方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值