Java线程安全问题

Java 线程安全问题

当我们使用多个线程访问同一资源 (可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

实际举例

开启三个窗口售票,总票数为100张。(相当于多线程)

java 复制代码
//实现方式一: 实现Runnable接口
public class WindowTest {
public static void main(String[] args) {
  SaleTicket s = new SaleTicket();
  Thread t1 = new Thread(s);
  Thread t2 = new Thread(s);
  Thread t3 = new Thread(s);
  t1.setName("窗口1");
  t2.setName("窗口2");
  t3.setName("窗口3");
  t1.start();
  t2.start();
  t3.start();
}
}
class SaleTicket implements Runnable{
int ticket = 100;
@Override
public void run(){
  while (true){
      if(ticket > 0){
          System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);
          ticket --;
      }else{
          break;
      }
  }
}
}
//输出结果
//存在不同窗口卖出相同票号的问题
//出现了线程的安全问题
java 复制代码
//实现方式二: 继承Thread类
public class WindowTest1 {
//使用继承Thread类实现
public static void main(String[] args) {
  Window w1 = new Window();
  Window w2 = new Window();
  Window w3 = new Window();
  w1.setName("窗口1");
  w2.setName("窗口2");
  w3.setName("窗口3");
  w1.start();
  w2.start();
  w3.start();
}

}
class Window extends Thread{
static int ticket = 100;
@Override
public void run() {
  while(true){
      if(ticket > 0){
          System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);
          ticket--;
      }else{
          break;
      }
  }
}
}
//即使使用static 修饰ticket 仍然存在同票现象

归纳问题

  • 多线程卖票出现的问题

    出现了同票和错票现象

  • 什么原因导致?

    线程1操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作

  • 如何解决?

    必须保证一个线程A在操作ticket的过程中,其他线程必须等待,直到线程A操作ticket结束以后,其他线程才能进来继续操作ticket。

  • Java是如何解决线程安全问题的?

    使用线程的同步机制。同步代码块同步方法

同步代码块

同步代码块概述

synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。

java 复制代码
synchronized(同步监视器){	//多个线程必须共用同一个同步监视器,当线程类只有一个实例化时候,可以使用this
    //需要被同步的代码
}

说明

  • 需要被同步的代码,即为操作共享数据的代码共享数据,即多个线程都需要操作的数据
  • 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他线程必须等待。
  • 同步监视器,俗称锁。哪个线程获得了锁,哪个线程就能执行需要被同步的代码。
  • 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。

使用同步代码块修改程序

使用同步代码块修改上述实现Runnalbe接口的线程问题

java 复制代码
//使用同步代码块解决上述线程问题
class SaleTicket implements Runnable{
    int ticket = 100;

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){	//SaleTicket只有唯一类,可以使用this
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);
                    ticket--;
                }
                else {
                    break;
                }
            }
        }
    }
}
public class Test{
    public static void main(String []args){
        SaleTicket p = new SaleTicket();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(p);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

使用同步代码块解决继承Thread类的线程安全问题

java 复制代码
//使用继承Thread类的方式,实现卖票
//使用同步代码块解决卖票的线程安全问题
public class Test2 {
    public static void main(String[] args) {
        SaleTicket2 t1 = new SaleTicket2();
        SaleTicket2 t2 = new SaleTicket2();
        SaleTicket2 t3 = new SaleTicket2();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class SaleTicket2 extends Thread{
    static int ticket = 100;    //ticket必须为静态变量
    public void run() {
        while(true){
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (SaleTicket2.class){
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);
                    ticket--;
                }
                else {
                    break;
                }
            }
        }
    }

}

同步方法

同步方法概述

同步方法说明

如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。

**同步方法:**synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

java 复制代码
public synchronized void method(){
    //可能会产生线程安全问题的代码
}
//声明了方法后,要在run()方法中调用method()
使用同步方法修改程序

实现Runnable接口

java 复制代码
//使用实现Runnable接口的方式 实现多窗口卖票问题
//使用同步方法解决多窗口中的线程安全问题
public class WindowTest1 {
    public static void main(String[] args) {
        Window p = new Window();
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(p);
        Thread t3 = new Thread(p);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t2.start();
        t1.start();
        t3.start();
    }
}
class Window implements Runnable{
    int ticket = 100;
    boolean isFlag = true;
    public synchronized void show(){
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);
            ticket--;
        }
        else {
            isFlag = false;
        }
    }

    @Override
    public void run() {
        while(isFlag){
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            show();
        }
    }
}

继承Thread类方式

java 复制代码
//使用继承Thread类的方式,实现多窗口售票的模式
//使用同步方法,解决线程安全问题
public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window2 extends Thread{
    static boolean isFlag = true;
    static int ticket = 100;
    //使用继承Thread类的方式,则多线程会声明多个实例化对象,ticket必须使用static修饰

    @Override
    public void run() {
        while(isFlag){
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                show();
        }
    }
    public static synchronized void show(){     //必须使用static的同步监视器
        //此时的同步监视器是 当前类本身(加了static)
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName()+"售票,票号为"+ticket);
            ticket--;
        }else{
            isFlag = false;
        }
    }
}
说明
  • 如果操作共享数据的代码完整的声明在了一个方法中,那么就可以将此方法声明为同步方法即可
  • 非静态的同步方法,默认同步监视器是this ; 静态的同步方法,默认同步监视器是当前类本身

汇总说明

线程创建的方式有两种,分别为实现Runnalbe接口继承Thread类,对于不同的线程创建类,使用的同步方法和同步代码块也有不同

  • 当线程创建为继承Thread类时,需要注意:

  • 使用实现Runnalbe接口和同步代码块时:

    synchronized (this) 同步代码块中同步监视器可以使用this替代

  • 使用实现Runnable接口和同步方法时:

    将公共代码部分,封装到一个方法中,方法格式为(可修改)public synchronized void method() ,并在pubilc void run()中调用即可

  • 使用继承Thread类时:

    公共的属性需要用static修饰 例如 static int ticket = 100

    synchronized ()同步监视器中不能使用this, 可以使用synchronized(类名.class)替代,例如synchronized (SaleTicket2.class)

    • 若继续使用同步方法时,同步方法也需要使用static进行修饰

synchronized利弊分析

  • 好处,解决了线程安全问题
  • 弊端,在操作共享数据时,多线程其实时串行执行的,意味着性能的降低。
相关推荐
2202_7544215412 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
蓝染-惣右介14 分钟前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
小林想被监督学习15 分钟前
idea怎么打开两个窗口,运行两个项目
java·ide·intellij-idea
HoneyMoose17 分钟前
IDEA 2024.3 版本更新主要功能介绍
java·ide·intellij-idea
我只会发热19 分钟前
Java SE 与 Java EE:基础与进阶的探索之旅
java·开发语言·java-ee
LZXCyrus20 分钟前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
是老余20 分钟前
本地可运行,jar包运行错误【解决实例】:通过IDEA的maven package打包多模块项目
java·maven·intellij-idea·jar
crazy_wsp20 分钟前
IDEA怎么定位java类所用maven依赖版本及引用位置
java·maven·intellij-idea
.Ayang23 分钟前
tomcat 后台部署 war 包 getshell
java·计算机网络·安全·web安全·网络安全·tomcat·网络攻击模型
Enougme23 分钟前
Appium常用的使用方法(一)
python·appium