线程同步和线程通信

  • 问题的提出

在这里插入图片描述
例 题
模拟火车站售票程序,开启三个窗口售票。

public class Rwindow implements  Runnable {
    private int ticket =3;
    @Override
    public void run() {
        while(ticket>0){
            System.out.println("买票号"+ ticket);
            ticket--;
        }
    }
}
class test1{
    public static void main(String[] args) {
        Rwindow t = new Rwindow();
        Thread thread = new Thread(t);
        Thread thread1 = new Thread(t);
        Thread thread2= new Thread(t);

        thread.setName("窗口1");
        thread.setName("窗2");
        thread.setName("窗3");
        thread.start();
        thread1.start();
        thread2.start();

    }
}

理想状态
在这里插入图片描述
极端状态 1 2 3 都sleep了进入阻塞状态
在这里插入图片描述
在这里插入图片描述

安全问题

由此就导致多线程出现了安全问题
.问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。

1.synchronized同步代码块:

synchronized(对象){
            //需要被同步的代码块
        }`

说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量。比如: ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。这一点非常重要

Runnable()

在这里插入图片描述
在这里插入图片描述
Runnable方式可以把synchronized里的关键字换成this
因为只有一个对象,
但是继承不可以,因为有三个对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
synchronized里的关键字也可以换成 当前类.class
反射,类只会被加载一次,所有产生的Class对象唯一
在这里插入图片描述

继承

一定要加static 哦
在这里插入图片描述
在这里插入图片描述

2.synchronized同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步(synchronized)方法
注意:
1.同步方法仍然涉及到同步监视器,只是不需要显示的声明
2.非静态的同步方法,同步监视器 this
静态同步监视器,当前类本身

public synchronized void show(String name){
}

Runnable()

在这里插入图片描述

继承

在这里插入图片描述

懒汉单例模式变成线程安全的

懒汉单例模式:

class Bank{
    private Bank(){}
    private static Bank instance =null;
    public static Bank getInstance() {
        //方式一:效率较差
       
            if (instance == null) {
                instance = new Bank();
            }
            return instance;
            }
   }

方式一:

class Bank{
    private Bank(){}
    private static Bank instance =null;
    public static Bank getInstance() {
        //方式一:效率较差
  synchronized (Bank.class) {
            if (instance == null) {
                instance = new Bank();
            }
            return instance;
        }

因为后面等待的数据就不用再判断等不等于null
直接返回instance 就好
方式二:

    if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;

在这里插入图片描述

死锁

来看个例子:
在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

  • 死锁
  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1= new StringBuffer();
        StringBuffer s2= new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized(s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);

                    }
                }
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

在这里插入图片描述

在这里插入图片描述

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
    内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。

构造函数:

public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }


注意:如果同步代码有异常,要将unlock()写入finally语句块
在这里插入图片描述

synchronized 与 Lock 的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是
    隐式锁,出了作用域自动释放
    Lock手动挡,快 , synchronized 自动挡
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
    更好的扩展性(提供更多的子类)
    优先使用顺序:
    Lock -> 同步代码块(已经进入了方法体,分配了相应资源) ->同步方法
    (在方法体之外)

实战

在这里插入图片描述

class Account{
    private double balance;
    private ReentrantLock lock = new ReentrantLock();

    public  double depisit(double money){
            if (money > 0) {
                balance += money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "余额" + balance);
            return balance;
        }

}
class Customer extends  Thread{
    private Account acct;
    public Customer(Account acct){
        this.acct=acct;
    }

    @Override
    public void run() {
        for(int i=0;i<3;i++){
            acct.depisit(1000);
        }
    }
}
class Test{
    public static void main(String[] args) {
        Account acct = new Account();
        Customer t1= new Customer(acct);
        Customer t2= new Customer(acct);
        t1.setName("甲");
        t2.setName("乙");
        t1.start();
        t2.start();
    }
}

同步代码块一睡准出错:线程不安全
在这里插入图片描述
三种方式都可以,博主这里用了最简单的

在这里插入图片描述
在这里插入图片描述
但有没有发现这样只能实现甲用户自己取,乙用户自己取,能不能实现甲乙用户交替取钱呢?
当然可以啦,这就是线程通信了

线程通信

在这里插入图片描述
甲乙用户就可以交替执行了
在这里插入图片描述

生产者消费者问题

经典例题:生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处
取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图
生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通
知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如
果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
生产者比消费者快时,消费者会漏掉一些数据没有取到。
消费者比生产者快时,消费者会取相同的数据。

class Clerk{
    private int productCount=0;
    public  synchronized void producerTask(){
        if(productCount<20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+"生产第"+productCount+"件");
            //生产者只要生产了一个产品,就可以把对方唤醒
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public   synchronized  void consumerTask(){
        if(productCount>0){
            System.out.println(Thread.currentThread().getName()+"消费第"+productCount+"件");
            productCount--;
            //消费者只要消费了一个产品就可以把对方唤醒
            notifyAll();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
class Producer extends  Thread{//生产者
    private Clerk clerk;
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }
    @Override
    public void run() {
        System.out.println(getName()+"开始生产产品");
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.producerTask();
        }

    }
}
class Consumer extends Thread{//消费者
    private Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk=clerk;
    }
    @Override
    public void run() {
        System.out.println(getName()+"开始消费产品");
       while(true){
           try {
               Thread.sleep(100);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           clerk.consumerTask();
       }
    }
}
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p = new Producer(clerk);
        Consumer c = new Consumer(clerk);
        p.setName("生产者1");
        c.setName("消费者1");
        p.start();
        c.start();
    }
}

注意一点:博主烦的错误,一定要让两个线程阻塞起来,线程安全问题才更容易表示出来
在这里插入图片描述
不加sleep的话程序执行很快,就不会交替执行了,加了之后,看结果,交替执行在这里插入图片描述

在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值