多线程中线程的安全问题

线程的安全问题

错票情况:

·

不是说没加sleep就不会出错,程序本身就有缺陷,只是出错的概率低而已,加了阻塞只是使他的错误概率提高了。

  • 卖票过程中,出现了重票、错票 --------------> 出现了线程安全问题

  • 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。

  • 解决:当一个线程a在操作ticket的时候,其他线程不可以参与进来,直到线程a操作完才可以参与,这种情况即使线程a出现了阻塞也不能被改变(安全)

  • 在Java中,通过同步机制来解决线程的安全问题!



1. 同步代码块处理安全问题

  • synchronized(同步监视器){

    // 需要被同步的代码

    }

    操作共享数据的代码,即为需要被同步的代码(不能包含多了,也不能包含少了!!!!!!!!!!!!)

    共享数据:多个线程共同操作的变量,比如:卖票中的ticket

    同步监视器:俗称:锁​ 🔒 任何一个类的对象,都可以充当锁,可以用this放进去,但要保证this唯一,甚至可以用xiancheng.class也行,因为类也是对象,并且只会加载一次,不管用谁,一定要保证​ 🔒 ​的唯一性!

    要求:多个线程必须共用同一把锁 🔒

同步的方式:

  • 优点:解决了线程的安全问题
  • 缺点:操作同步代码的时候,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

实现runnable接口:可考虑使用this充当监视器

package com.ran;

public class ran {
    public static void main(String[] args) {
        xiancheng t=new xiancheng();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();



    }
}
//创建一个实现了runnable接口的类
class xiancheng implements  Runnable{
    private int ticket=100;
    Object obj=new Object();

    @Override
    public void run() {

        while (true){
            synchronized(this){  //此时的this:唯一的xiancheng的对象  方式二: synchronized(obj)
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+" 卖票,票号为"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }

        }
    }
}

继承Thread类:慎用this,考虑使用当前类充当监视器

package com.ran;

public class ran {
    public static void main(String[] args) throws InterruptedException {
        duoxiancheng t1=new duoxiancheng();
        duoxiancheng t2=new duoxiancheng();
        duoxiancheng t3=new duoxiancheng();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }


}


class duoxiancheng extends Thread{

    private static  int ticket=100;

    @Override
    public void run() {
        while (true){
            // 不可用this,此时的this表示t1,t2,t3,不是同一个锁
            synchronized (duoxiancheng.class){
                if(ticket>0){
                    System.out.println(getName()+" 卖票,票号为" + ticket);
                    ticket--;
                }else {
                    break;
                }
            }

        }
    }
}


2. 同步方法处理安全问题

如果操作共享数据的代码完整地声明在一个方法中,我们不妨将此方法声明为同步的

在需要同步的方法上加一个synchronized即可

实现runnable接口:

package com.ran;

public class ran {
    public static void main(String[] args) {
        xiancheng t=new xiancheng();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
//创建一个实现了runnable接口的类
class xiancheng implements  Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            show();
        }
    }

    private synchronized void show(){ //同步监视器:this
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+" 卖票,票号为"+ticket);
            ticket--;
        }
    }
}

继承Thread类:(切记==+Static==)

package com.ran;

public class ran {
    public static void main(String[] args) throws InterruptedException {
        duoxiancheng t1=new duoxiancheng();
        duoxiancheng t2=new duoxiancheng();
        duoxiancheng t3=new duoxiancheng();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }


}


class duoxiancheng extends Thread{

    private static  int ticket=100;

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

        }
    }

    private static synchronized void show(){ //同步监视器:duoxiangcheng.class
        // private  synchronized void show(){此种方式是错误的,有三个监视器t1,t2,t3
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+" 卖票,票号为" + ticket);
            ticket--;
        }
    }
}


3. 同步方法的总结

  • 同步方法仍然涉及到同步监视器,只是不需要我们显式声明
  • 非静态的同步方法,同步监视器是:this
  • 静态的同步方法,同步监视器是:当前类本身


4. 死锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-evBVSy7c-1609899655423)(E:\学习笔记\图片\image-20200913134252747.png)]

package com.ran;

public class ran {
    public static void main(String[] args) throws InterruptedException {
       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();
    }


}

我们使用同步时,要避免出现死锁!



5. Lock锁处理线程安全问题

在这里插入图片描述

package com.ran;

import java.util.concurrent.locks.ReentrantLock;

public class ran1 {
    public static void main(String[] args) {
        xiancheng t=new xiancheng();
        Thread t1=new Thread(t);
        Thread t2=new Thread(t);
        Thread t3=new Thread(t);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();



    }
}
//创建一个实现了runnable接口的类
class xiancheng implements  Runnable{
    private int ticket=100;
    private ReentrantLock lock=new ReentrantLock();
    Object obj=new Object();

    @Override
    public void run() {

        while (true){
            try {
                lock.lock();
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" 卖票,票号为"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }finally {
                lock.unlock();
            }

        }
    }
}
  • 在继承Thread类创建多线程加Lock锁的时候也切记要加 static,不然就是三把锁了!


6. 安全问题处理方式异同

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NcKAr06z-1609899655428)(E:\学习笔记\图片\image-20200913143441502.png)]

synchronized处理方式和Lock处理方式的异同:

同:

  • 二者都可以解决线程安全问题

异:

  • synchronized机制在执行完相应的同步代码之后,自动释放同步监视器
  • Lock需要手动启动同步( lock() ),结束同步也需要手动实现( unlock() )


7. 同步机制练习

甲乙两个人存钱,每次存1000,存三次,存完要打印一次

package com.ran;

import java.util.concurrent.locks.ReentrantLock;

public class ran {
    public static void main(String[] args)  {
        Account account=new Account(0);
        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);

        c1.setName("甲");
        c2.setName("乙");

        c1.start();
        c2.start();

    }


}
class Customer extends Thread{
    private Account account;
    private static ReentrantLock lock;

    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {

        for (int i = 0; i <30; i++) {
            account.deposit(1000);
        }
    }
}

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

    public Account(double balance) {
        this.balance = balance;
    }

    public  void deposit(double amt){

      try {
          lock.lock();
          if(amt>0){
              balance+=amt;
              try {
                  Thread.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName()+"存钱成功,余额为"+balance);

          }
      }finally {
          lock.unlock();
      }

        }

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值