首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【JUC】001-多线程回顾、synchronized与Lock锁

【JUC】001-多线程回顾、synchronized与Lock锁

作者头像
訾博ZiBo
发布2025-01-06 16:40:32
发布2025-01-06 16:40:32
2110
举报

0、警醒自己

1、学习不用心,骗人又骗己;

2、学习不刻苦,纸上画老虎;

3、学习不惜时,终得人耻笑;

4、学习不复习,不如不学习;

5、学习不休息,毁眼伤身体;

7、狗才等着别人喂,狼都是自己寻找食物;

一、什么是JUC

二、进程与线程

1、概述

见之前的博客:

https://blog.csdn.net/qq_29689343/article/details/95861050

Java默认有两个线程,一个是Main线程,另一个是GC(垃圾回收)线程;

Java开启线程的三种方式:Thread、Runnable、Callable;

Java真的能开启线程吗?不能!

new Thread().start();

代码语言:javascript
复制
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();

2、并发编程的目的

充分利用CPU资源;

3、线程有几个状态

6个;

Thread.State;

代码语言:javascript
复制
    public enum State {
        //新生
        NEW,

        //运行
        RUNNABLE,

        //阻塞
        BLOCKED,

        //等待,死死的等
        WAITING,

        //超时等待,限时等待
        TIMED_WAITING,

        //终止
        TERMINATED;
    }

4、wait与sleep的区别

来自不同的类:

wait来自Object;

sleep来自Thread;

关于锁的释放:

wait会释放锁,sleep睡眠,抱着锁睡觉了,不会释放锁;

释放的范围不同:

wait必须用在同步代码块;

sleep可以在任何地方使用;

是否需要捕获异常:

wait不需要,sleep需要;

三、传统加锁synchronized

1、传统的并发卖票问题

代码:
代码语言:javascript
复制
package com.zibo;

//买票
//企业开发中,线程是一个单独的资源类,没有任何附属操作
//属性、方法
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        // 并发:多线程同时操作一个资源类,把资源类丢入线程
        Ticket ticket = new Ticket();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        },"C").start();

    }
}
//资源类OOP
class Ticket{
    //属性、方法
    private int num = 50;

    //卖票的方式
    public void sale(){
        if(num>0){
            System.out.println(Thread.currentThread().getName() + "卖出了第" + num-- + "张票,剩余" + num + "张票!");
        }
    }
}
运行结果:
代码语言:javascript
复制
A卖出了第50张票,剩余49张票!
A卖出了第49张票,剩余48张票!
A卖出了第48张票,剩余47张票!
A卖出了第47张票,剩余46张票!
B卖出了第46张票,剩余44张票!
A卖出了第45张票,剩余44张票!
B卖出了第44张票,剩余43张票!
A卖出了第43张票,剩余42张票!
B卖出了第42张票,剩余41张票!
A卖出了第41张票,剩余40张票!
B卖出了第40张票,剩余39张票!
A卖出了第39张票,剩余38张票!
B卖出了第38张票,剩余37张票!
A卖出了第37张票,剩余36张票!
B卖出了第36张票,剩余35张票!
C卖出了第34张票,剩余33张票!
A卖出了第35张票,剩余34张票!
C卖出了第32张票,剩余31张票!
B卖出了第33张票,剩余32张票!
C卖出了第30张票,剩余29张票!
A卖出了第31张票,剩余30张票!
C卖出了第28张票,剩余27张票!
B卖出了第29张票,剩余28张票!
C卖出了第26张票,剩余25张票!
A卖出了第27张票,剩余26张票!
C卖出了第24张票,剩余23张票!
B卖出了第25张票,剩余24张票!
C卖出了第22张票,剩余21张票!
A卖出了第23张票,剩余22张票!
C卖出了第20张票,剩余19张票!
B卖出了第21张票,剩余20张票!
C卖出了第18张票,剩余17张票!
A卖出了第19张票,剩余18张票!
C卖出了第16张票,剩余15张票!
B卖出了第17张票,剩余16张票!
C卖出了第14张票,剩余13张票!
A卖出了第15张票,剩余14张票!
C卖出了第12张票,剩余11张票!
B卖出了第13张票,剩余12张票!
C卖出了第10张票,剩余9张票!
A卖出了第11张票,剩余10张票!
C卖出了第8张票,剩余7张票!
B卖出了第9张票,剩余8张票!
C卖出了第6张票,剩余5张票!
A卖出了第7张票,剩余6张票!
C卖出了第4张票,剩余3张票!
B卖出了第5张票,剩余4张票!
C卖出了第2张票,剩余1张票!
A卖出了第3张票,剩余2张票!
B卖出了第1张票,剩余0张票!

2、传统的解决方式在方法上加synchronized

代码:
代码语言:javascript
复制
//卖票的方式
    public synchronized void sale(){
        if(num>0){
            System.out.println(Thread.currentThread().getName() + "卖出了第" + num-- + "张票,剩余" + num + "张票!");
        }
    }
运行结果:
代码语言:javascript
复制
A卖出了第50张票,剩余49张票!
A卖出了第49张票,剩余48张票!
A卖出了第48张票,剩余47张票!
A卖出了第47张票,剩余46张票!
A卖出了第46张票,剩余45张票!
A卖出了第45张票,剩余44张票!
B卖出了第44张票,剩余43张票!
B卖出了第43张票,剩余42张票!
B卖出了第42张票,剩余41张票!
B卖出了第41张票,剩余40张票!
B卖出了第40张票,剩余39张票!
B卖出了第39张票,剩余38张票!
B卖出了第38张票,剩余37张票!
B卖出了第37张票,剩余36张票!
B卖出了第36张票,剩余35张票!
B卖出了第35张票,剩余34张票!
B卖出了第34张票,剩余33张票!
B卖出了第33张票,剩余32张票!
B卖出了第32张票,剩余31张票!
B卖出了第31张票,剩余30张票!
B卖出了第30张票,剩余29张票!
B卖出了第29张票,剩余28张票!
B卖出了第28张票,剩余27张票!
B卖出了第27张票,剩余26张票!
B卖出了第26张票,剩余25张票!
B卖出了第25张票,剩余24张票!
A卖出了第24张票,剩余23张票!
A卖出了第23张票,剩余22张票!
A卖出了第22张票,剩余21张票!
A卖出了第21张票,剩余20张票!
A卖出了第20张票,剩余19张票!
A卖出了第19张票,剩余18张票!
A卖出了第18张票,剩余17张票!
A卖出了第17张票,剩余16张票!
A卖出了第16张票,剩余15张票!
A卖出了第15张票,剩余14张票!
A卖出了第14张票,剩余13张票!
A卖出了第13张票,剩余12张票!
A卖出了第12张票,剩余11张票!
A卖出了第11张票,剩余10张票!
C卖出了第10张票,剩余9张票!
C卖出了第9张票,剩余8张票!
C卖出了第8张票,剩余7张票!
C卖出了第7张票,剩余6张票!
C卖出了第6张票,剩余5张票!
C卖出了第5张票,剩余4张票!
C卖出了第4张票,剩余3张票!
C卖出了第3张票,剩余2张票!
C卖出了第2张票,剩余1张票!
C卖出了第1张票,剩余0张票!

四、Lock锁

1、包路径

2、接口的方法

3、实现类

4、使用Lock代替synchronized

Lock lock = new ReentrantLock();

公平锁:十分公平,可以先来后到,按顺序;

非公平锁:可以插队(默认);

代码:
代码语言:javascript
复制
package com.zibo;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//买票
//企业开发中,线程是一个单独的资源类,没有任何附属操作
//属性、方法
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        // 并发:多线程同时操作一个资源类,把资源类丢入线程
        Ticket2 ticket = new Ticket2();
        //我们现在不想用synchronized了
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}

//资源类OOP
class Ticket2{
    //属性、方法
    private int num = 50;

    Lock lock = new ReentrantLock();

    //卖票的方式
    //使用Lock锁
    public void sale(){
        //加锁
        lock.lock();
        try {
            if(num>0){
                System.out.println(Thread.currentThread().getName() + "卖出了第" + num-- + "张票,剩余" + num + "张票!");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
运行结果:
代码语言:javascript
复制
A卖出了第50张票,剩余49张票!
A卖出了第49张票,剩余48张票!
A卖出了第48张票,剩余47张票!
A卖出了第47张票,剩余46张票!
A卖出了第46张票,剩余45张票!
A卖出了第45张票,剩余44张票!
A卖出了第44张票,剩余43张票!
C卖出了第43张票,剩余42张票!
C卖出了第42张票,剩余41张票!
C卖出了第41张票,剩余40张票!
C卖出了第40张票,剩余39张票!
C卖出了第39张票,剩余38张票!
C卖出了第38张票,剩余37张票!
C卖出了第37张票,剩余36张票!
C卖出了第36张票,剩余35张票!
C卖出了第35张票,剩余34张票!
C卖出了第34张票,剩余33张票!
C卖出了第33张票,剩余32张票!
C卖出了第32张票,剩余31张票!
C卖出了第31张票,剩余30张票!
C卖出了第30张票,剩余29张票!
C卖出了第29张票,剩余28张票!
C卖出了第28张票,剩余27张票!
C卖出了第27张票,剩余26张票!
C卖出了第26张票,剩余25张票!
C卖出了第25张票,剩余24张票!
C卖出了第24张票,剩余23张票!
B卖出了第23张票,剩余22张票!
B卖出了第22张票,剩余21张票!
B卖出了第21张票,剩余20张票!
B卖出了第20张票,剩余19张票!
B卖出了第19张票,剩余18张票!
B卖出了第18张票,剩余17张票!
B卖出了第17张票,剩余16张票!
B卖出了第16张票,剩余15张票!
B卖出了第15张票,剩余14张票!
B卖出了第14张票,剩余13张票!
B卖出了第13张票,剩余12张票!
B卖出了第12张票,剩余11张票!
B卖出了第11张票,剩余10张票!
B卖出了第10张票,剩余9张票!
B卖出了第9张票,剩余8张票!
B卖出了第8张票,剩余7张票!
B卖出了第7张票,剩余6张票!
B卖出了第6张票,剩余5张票!
B卖出了第5张票,剩余4张票!
B卖出了第4张票,剩余3张票!
A卖出了第3张票,剩余2张票!
A卖出了第2张票,剩余1张票!
A卖出了第1张票,剩余0张票!

五、synchronized与Lock锁区别

1、区别

1、synchronized是内置关键字,Lock是一个接口;

2、synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁;

3、synchronized是全自动的,自动释放锁,lock锁必须要手动释放锁,否则死锁(自动档、手动档);

4、synchronized:线程1(获得锁,阻塞)、线程2(傻傻等待);lock锁:不一定会等下去(尝试获取锁);

lock锁:lock.tryLock();尝试获取锁;

5、synchronized是可重入锁、不可中断、非公平;lock锁是可重入锁、可以判断、是否公平(可设置);

6、synchronized适合锁少量代码,Lock适合锁大量的代码;

2、锁是什么,如何判断锁的是谁(见【JUC】002)

(面试重点:单例模式、排序算法、生产者和消费者、死锁)

生产者、消费者问题(synchronized版本):

代码演示:

代码语言:javascript
复制
package com.zibo;

/**
 * 线程之间的通信问题:生产者和消费者问题,等待唤醒,通知唤醒
 * 线程交替执行,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 num = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        if(num!=0){
            //等待操作
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "==>" + num);
        //通知其他线程我加完了
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if(num==0){
            //等待操作
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "==>" + num);
        //通知其他线程我减完了
        this.notifyAll();
    }
}

运行结果:

代码语言:javascript
复制
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
存在问题(虚假唤醒):

上面是两个线程,四个线程还安全吗?

运行结果:

代码语言:javascript
复制
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
C==>1
B==>0
A==>1
B==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
B==>0
A==>1
D==>0
C==>1
D==>0
A==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
生产者、消费者问题(Lock版本):

关于等待和通知的分析:

关于Condition:

代码演示:

代码语言:javascript
复制
package com.zibo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程之间的通信问题:生产者和消费者问题,等待唤醒,通知唤醒
 * 线程交替执行,A和B操作同一个变量 num=0
 * A num+1
 * B num-1
 */
public class B {
    public static void main(String[] args) {
        DataB data = new DataB();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.increment();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.decrement();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.increment();
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.decrement();
            }
        },"D").start();
    }
}

//等待、业务、通知
class DataB{
    private int num = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //condition.await();//等待
    //condition.signalAll();//通知(唤醒)

    //+1
    public void increment(){
        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();
        }
    }

    //-1
    public void decrement() {
        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();
        }
    }
}

运行结果:

代码语言:javascript
复制
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
A==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0
C==>1
B==>0

结果分析:

同样能够实现等待和通知,但目前看不出这种方式有什么优势,如果没有优势,为什么要用这种技术呢?(任何一种新的技术一定不仅仅是替代了旧的技术,一定有优势或补充)

假如我们想让A、B、C、D顺序执行呢?

Condition实现精准等待和通知:

代码演示:

代码语言:javascript
复制
package com.zibo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程之间的通信问题:生产者和消费者问题,等待唤醒,通知唤醒
 * 线程交替执行,A和B操作同一个变量 num=0
 * A num+1
 * B num-1
 */
public class C {
    public static void main(String[] args) {
        DataC data = new DataC();
        /*
         * A B C三个线程:A执行完,通知B执行,B执行完通知C执行,C执行完通知A执行,以此类推
         */
        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 DataC{
    private int num = 1;//1A 2B 3C
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    //A
    public void printA(){
        //加锁
        lock.lock();
        try {
            //业务——判断——执行——通知
            while(num!=1){
                //等待操作
                condition1.await();//等待
            }
            System.out.println(Thread.currentThread().getName() + "==>" + "AAA");
            //通知B执行
            num = 2;
            condition2.signal();//通知(唤醒)
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //解锁
            lock.unlock();
        }
    }

    //B
    public void printB(){
        lock.lock();
        try {
            //业务——判断——执行——通知
            while(num!=2){
                //等待操作
                condition2.await();//等待
            }
            System.out.println(Thread.currentThread().getName() + "==>" + "BBB");
            //通知C执行
            num = 3;
            condition3.signal();//通知(唤醒)
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //C
    public void printC(){
        lock.lock();
        try {
            //业务——判断——执行——通知
            while(num!=3){
                //等待操作
                condition3.await();//等待
            }
            System.out.println(Thread.currentThread().getName() + "==>" + "CCC");
            //通知C执行
            num = 1;
            condition1.signal();//通知(唤醒)
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行结果:

代码语言:javascript
复制
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
A==>AAA
B==>BBB
C==>CCC
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-01-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0、警醒自己
  • 一、什么是JUC
  • 二、进程与线程
    • 1、概述
    • 2、并发编程的目的
    • 3、线程有几个状态
    • 4、wait与sleep的区别
      • 来自不同的类:
      • 关于锁的释放:
      • 释放的范围不同:
      • 是否需要捕获异常:
  • 三、传统加锁synchronized
    • 1、传统的并发卖票问题
      • 代码:
      • 运行结果:
    • 2、传统的解决方式在方法上加synchronized
      • 代码:
      • 运行结果:
  • 四、Lock锁
    • 1、包路径
    • 2、接口的方法
    • 3、实现类
    • 4、使用Lock代替synchronized
      • 代码:
      • 运行结果:
  • 五、synchronized与Lock锁区别
    • 1、区别
    • 2、锁是什么,如何判断锁的是谁(见【JUC】002)
      • 生产者、消费者问题(synchronized版本):
      • 存在问题(虚假唤醒):
      • 生产者、消费者问题(Lock版本):
      • Condition实现精准等待和通知:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档