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

目录

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实现精准等待和通知:


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();

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;

    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、传统的并发卖票问题

代码:

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 + "张票!");
        }
    }
}

运行结果:

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

代码:

//卖票的方式
    public synchronized void sale(){
        if(num>0){
            System.out.println(Thread.currentThread().getName() + "卖出了第" + num-- + "张票,剩余" + num + "张票!");
        }
    }

运行结果:

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();

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

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

 

代码:

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();
        }
    }
}

 

运行结果:

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版本):

代码演示:

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();
    }
}

运行结果:

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

 

存在问题(虚假唤醒):

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

运行结果:

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:

代码演示:

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();
        }
    }
}

运行结果:

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实现精准等待和通知:

代码演示:

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();
        }
    }
}

运行结果:

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

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值