Java笔记033-多线程基础/Synchronized、分析同步原理、互斥锁、线程的死锁、释放锁、章节作业

目录

多线程基础

Synchronized

使用多线程模拟售票系统

线程同步机制

具体同步方法-Synchronized

分析同步原理

互斥锁

基本介绍

使用互斥锁来解决售票问题

 注意事项和细节

线程的死锁

基本介绍

应用案例

 释放锁

下面操作会释放锁

释放锁的分析 

下面操作不会释放锁

章节作业 

1、编程题

2、编程题


多线程基础

Synchronized

使用多线程模拟售票系统

[售票系统],编程模拟三个售票窗口售票,分别使用继承Thread和实现Runnable方式,并分析有什么问题

package com17.ticket;
 
/**
 * @author 甲柒
 * @version 1.0
 * @title SellTicket
 * @package com17.ticket
 * @time 2023/3/27 11:41
 * 使用多线程,模拟三个窗口同时售票 100张
 */
public class SellTicket {
    public static void main(String[] args) {
//        //测试
//        SellTicket01 sellTicket01 = new SellTicket01();
//        SellTicket01 sellTicket02 = new SellTicket01();
//        SellTicket01 sellTicket03 = new SellTicket01();
//
//        //这里会出现票数超卖现象
//        sellTicket01.start();//启动售票线程
//        sellTicket02.start();//启动售票线程
//        sellTicket03.start();//启动售票线程
 
        System.out.println("=====使用Runnable接口方式实现=====");
        SellTicket02 sellTicket02 = new SellTicket02();
 
        //这里也会出现票数超卖现象
        new Thread(sellTicket02).start();//第1个线程-窗口
        new Thread(sellTicket02).start();//第2个线程-窗口
        new Thread(sellTicket02).start();//第3个线程-窗口
    }
}
 
//使用Thread方式
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享ticketNum
 
    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束~~~");
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
        }
    }
}
 
//实现接口方式
class SellTicket02 implements Runnable {
    private static int ticketNum = 100;//让多个线程共享ticketNum
 
    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束~~~");
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
        }
    }
}

票数会超卖

线程同步机制

  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
  2. 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。

具体同步方法-Synchronized

1、同步代码块

synchronized(对象){//得到对象的锁,才能操作同步代码
    //需要被同步代码
}

2、synchronized还可以放在方法声明中,表示整个方法-为同步方法

public synchronized void m(String name){
    //需要被同步的代码
}

3、如何理解:
就好像某人去厕所前先把门关上(上锁),完事后再出来(解锁),那么其他人就可以在使用厕所了

4、使用synchronized解决售票问题

package com17.syn;

/**
 * @author 甲柒
 * @version 1.0
 * @title SellTicket
 * @package com17.syn
 * @time 2023/3/29 16:41
 * 使用多线程,模拟三个窗口同时售票 100张
 */
public class SellTicket {
    public static void main(String[] args) {
//        //测试
//        SellTicket01 sellTicket01 = new SellTicket01();
//        SellTicket01 sellTicket02 = new SellTicket01();
//        SellTicket01 sellTicket03 = new SellTicket01();
//
//        //这里会出现票数超卖现象
//        sellTicket01.start();//启动售票线程
//        sellTicket02.start();//启动售票线程
//        sellTicket03.start();//启动售票线程

//        System.out.println("=====使用Runnable接口方式实现=====");
//        SellTicket02 sellTicket02 = new SellTicket02();
//
//        //这里也会出现票数超卖现象
//        new Thread(sellTicket02).start();//第1个线程-窗口
//        new Thread(sellTicket02).start();//第2个线程-窗口
//        new Thread(sellTicket02).start();//第3个线程-窗口

        //测试
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口
    }
}

//使用Thread方式
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享ticketNum


    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束~~~");
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
        }
    }
}

//实现接口方式
class SellTicket02 implements Runnable {
    private static int ticketNum = 100;//让多个线程共享ticketNum

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束~~~");
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
        }
    }
}

//实现接口方式,使用synchronized实现线程同步
class SellTicket03 implements Runnable {
    private static int ticketNum = 100;//让多个线程共享ticketNum
    private boolean loop = true;//控制run方法变量

    public synchronized void sell() {//同步方法,在同一时刻,只能有一个线程来执行run方法
        if (ticketNum <= 0) {
            System.out.println("售票结束~~~");
            loop = false;
            return;
        }
        //休眠50毫秒
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
    }

    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一个同步方法
        }
    }
}

分析同步原理

互斥锁

基本介绍

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
  3. 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问
  4. 同步的局限性:导致程序的执行效率要降低
  5. 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
  6. 同步方法(静态的)的锁为当前类本身

使用互斥锁来解决售票问题

package com17.syn;

/**
 * @author 甲柒
 * @version 1.0
 * @title SellTicket
 * @package com17.syn
 * @time 2023/3/29 16:41
 * 使用多线程,模拟三个窗口同时售票 100张
 */
public class SellTicket {
    public static void main(String[] args) {
//        //测试
//        SellTicket01 sellTicket01 = new SellTicket01();
//        SellTicket01 sellTicket02 = new SellTicket01();
//        SellTicket01 sellTicket03 = new SellTicket01();
//
//        //这里会出现票数超卖现象
//        sellTicket01.start();//启动售票线程
//        sellTicket02.start();//启动售票线程
//        sellTicket03.start();//启动售票线程

//        System.out.println("=====使用Runnable接口方式实现=====");
//        SellTicket02 sellTicket02 = new SellTicket02();
//
//        //这里也会出现票数超卖现象
//        new Thread(sellTicket02).start();//第1个线程-窗口
//        new Thread(sellTicket02).start();//第2个线程-窗口
//        new Thread(sellTicket02).start();//第3个线程-窗口

        //测试
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口
    }
}

//使用Thread方式
//new SellTicket01.start();
//new SellTicket01.start();
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享ticketNum

//    public void m1(){
//        synchronized (this){
//            System.out.println("hello");
//        }
//    }
    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束~~~");
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
        }
    }
}

//实现接口方式
class SellTicket02 implements Runnable {
    private static int ticketNum = 100;//让多个线程共享ticketNum

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束~~~");
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
        }
    }
}

//实现接口方式,使用synchronized实现线程同步
class SellTicket03 implements Runnable {
    private static int ticketNum = 100;//让多个线程共享ticketNum
    Object object = new Object();
    private boolean loop = true;//控制run方法变量

    //同步方法(静态的)的锁为当前类本身
    //解读
    //1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
    //2. 如果在静态方法中,实现一个同步代码块
    /*
    synchronized (SellTicket03.class){
        System.out.println("m2");
    }
    */
    public synchronized static void m1() {

    }

    public static void m2() {
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
    }

    //说明
    //1. public synchronized void sell() {} 就是一种同步方法
    //2. 这时锁在 this对象
    //3. 也可以在代码块上写 synchronized ,同步代码块,互斥锁还是在this对象
    public /*synchronized*/ void sell() {//同步方法,在同一时刻,只能有一个线程来执行run方法

        synchronized (/*this*/ object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束~~~");
                loop = false;
                return;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数 " + (--ticketNum));
        }
    }

    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一个同步方法
        }
    }
}

 注意事项和细节

  1. 同步方法如果没有使用static修饰:默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class
  3. 实现的落地步骤:
  • 需要先分析上锁的代码
  • 选择同步代码块或同步方法
  • 要求多个线程的锁对象为同一个

线程的死锁

基本介绍

多个线程都占用了对方的锁资源,但是不肯相让,导致了死锁,在编程时一定要避免死锁的发生

应用案例

package com17.syn;

/**
 * @author 甲柒
 * @version 1.0
 * @title DeadLock_
 * @package com17.syn
 * @time 2023/3/29 17:49
 * 模拟线程死锁
 */
public class DeadLock_ {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();
    }
}

//线程
class DeadLockDemo extends Thread {
    static Object o1 = new Object();//保证多线程,共享一个对象,这里使用static
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }

    @Override
    public void run() {

        //下面业务逻辑的分析
        //1. 如果flag 为 True,线程就会先得到 持有 o1 对象锁,然后尝试去获取 o2 对象锁
        //2. 如果线程A 得不到 o2 对象锁,就会Blocked
        //3. 如果flag 为 False,线程就会先得到 持有 o2 对象锁,然后尝试去获取 o1 对象锁
        //4. 如果线程B 得不到 o1 对象锁,就会Blocked

        if (flag) {
            synchronized (o1) {//对象互斥锁,下面就是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入1");
                synchronized (o2) {//这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入3");
                synchronized (o1) {//这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + " 进入4");
                }
            }
        }
    }
}

 释放锁

下面操作会释放锁

  1. 当前线程的同步方法、同步代码块执行结束
    案例:上厕所,完事出来
  2. 当前线程在同步代码块、同步方法中遇到break、return
    案例:没有正常的完事,经理叫他修改bug,不得已出来
  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
    案例:没有正常的完事,发现忘带纸,不得已出来
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
    案例:没有正常完事,觉得需要酝酿一下,所以出来等会再进去 

释放锁的分析 

下面操作不会释放锁

  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
    案例:上厕所,太困了,在坑位上眯了一会
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
    提示:尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

章节作业 

1、编程题

  1. 在main方法中启动两个线程
  2. 第1个线程循环随机打印100以内的整数
  3. 直到带2个线程从键盘读取了"Q"命令

 

代码如下

package com17.exercise;

import java.util.Scanner;

/**
 * @author 甲柒
 * @version 1.0
 * @title Exercise01
 * @package com17.exercise
 * @time 2023/3/29 18:50
 */
public class Exercise01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);
        a.start();
        b.start();
    }
}

//创建A线程类
class A extends Thread {
    private boolean loop = true;

    @Override
    public void run() {
        //输出1-100 数字
        while (loop) {
            System.out.println((int) (Math.random() * 100 + 1));
            //休眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("A线程退出...");
    }

    public void setLoop(boolean loop) {//可以修改loop变量
        this.loop = loop;
    }
}

//直到带2个线程从键盘读取了"Q"命令
class B extends Thread {
    private A a;
    private Scanner scanner = new Scanner(System.in);

    public B(A a) {//构造器中,直接传入A类对象
        this.a = a;
    }

    @Override
    public void run() {
        while (true) {
            //接收到用户的输入
            System.out.println("请输入你的指令(Q)表示退出:");
            char key = scanner.next().toUpperCase().charAt(0);
            if (key == 'Q') {
                //以通知的方式结束A线程
                a.setLoop(false);
                System.out.println("b线程退出~~~");
                break;
            }
        }
    }
}

2、编程题

  1. 有2个用户分别从同一个卡上取钱(总额:10000)
  2. 每次都取1000,当余额不足时,就不能取款了
  3. 不能出现超取现象=》线程同步问题

代码如下

package com17.exercise;

/**
 * @author 甲柒
 * @version 1.0
 * @title Exercise02
 * @package com17.exercise
 * @time 2023/3/29 19:14
 */
public class Exercise02 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        thread1.setName("t1");
        Thread thread2 = new Thread(t);
        thread2.setName("t2");
        thread1.start();
        thread2.start();
    }
}

//编程收款的线程
//1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
//2.每次取出1000
class T implements Runnable {
    private int money = 10000;

    @Override
    public void run() {
        while (true) {

            //解读
            //1. 这里使用synchronized实现了线程同步
            //2. 当多个线程执行到这里时,就会去争夺this对象锁
            //3. 哪个线程争夺到(获取)this对象锁,就执行synchronized代码块,执行完后,会释放this对象锁
            //4. 争夺不到this对象锁,就blocked,准备继续争夺
            //5.this对象锁是非公平锁
            synchronized (this) {//
                //判断余额是否够
                if (money < 1000) {
                    System.out.println("余额不足~~~~");
                    break;
                }
                money -= 1000;
                System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
                try {
                    //休眠1s
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_59621600/article/details/129838471