前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 多线程系列Ⅱ

Java 多线程系列Ⅱ

作者头像
终有救赎
发布2024-01-30 08:55:10
1160
发布2024-01-30 08:55:10
举报
文章被收录于专栏:多线程多线程

一、引言

在多线程环境中,线程安全是确保程序正确性的关键因素。Java作为一种广泛使用的编程语言,其线程安全的概念、策略和案例分析对于开发人员来说至关重要。

二、Java线程安全概念

  1. 线程安全定义

线程安全是多线程编程中的重要概念,它指的是在并发环境中,共享数据的一致性和完整性得到保证。换句话说,在多线程环境中,线程安全能够防止数据竞争和不可预测的行为。

  1. Java中的线程安全

在Java中,线程安全性主要通过synchronized关键字volatile关键字原子类以及来实现。这些机制可以确保在多线程环境下,对共享资源的访问是互斥的,从而避免数据竞争和不一致性问题。

  1. Java中线程不安全的情况
  • 共享变量:在多线程环境中,如果多个线程同时访问和修改同一个共享变量,就可能导致数据不一致的问题。例如,两个线程同时对一个计数器进行加1操作,由于操作顺序不确定,最后得到的结果可能不是期望的结果。
  • 非同步方法:如果一个方法没有进行同步处理,那么当多个线程同时调用该方法时,就可能出现数据竞争的问题。例如,一个线程正在执行一个方法,另一个线程突然插入了该方法的中间代码,就可能导致第一个线程得到错误的结果。
  • 死锁:死锁是指两个或多个线程互相等待对方释放资源,导致程序无法继续执行的情况。例如,线程A拥有资源1,线程B拥有资源2,两个线程都想要对方手里的资源,但是谁都不愿意先释放自己的资源,结果就形成了死锁。
  • 线程间通信问题:线程间通信也可能导致线程不安全的问题。例如,一个线程正在等待某个条件成立(如另一个线程已经完成了某个任务),而另一个线程迟迟不满足该条件,就会导致第一个线程一直等待下去,浪费CPU资源。
  • 资源竞争:资源竞争是指多个线程同时争夺同一资源,导致某些线程无法获得足够的资源而出现异常的情况。例如,多个线程同时访问同一个文件或数据库连接,就有可能导致某些线程无法获取到所需的资源而抛出异常。

三、Java线程安全策略

  1. 使用synchronized关键字

synchronized关键字是Java提供的一种内置的线程同步机制。它可以应用于方法或代码块,确保同一时刻只有一个线程可以执行该代码块。例如:

代码语言:javascript
复制
public synchronized void add(int value) { 
    this.count += value; 
}
  1. 使用volatile关键字

volatile关键字用于确保多线程对共享变量的访问是原子的。当一个变量被声明为volatile时,它会保证修改的值会立即被更新到主内存中,从而避免线程之间的数据不一致。例如:

代码语言:javascript
复制
public class Counter { 
    private volatile int count; 
    //... 
}
  1. 使用原子类

Java提供了原子类(如AtomicInteger、AtomicLong等),这些类提供了更精确的线程安全操作。它们使用内部锁或CAS(Compare-and-Swap)操作来确保对共享资源的访问是原子的。例如:

代码语言:javascript
复制
public class Counter { 
    private AtomicInteger count = new AtomicInteger(0); 
    // ... 
}
  1. 使用显式锁

Java提供了显式锁(如ReentrantLock、ReadWriteLock等),允许开发人员更灵活地控制线程同步。这些锁可以确保对共享资源的访问是互斥的,从而避免数据竞争。例如:

代码语言:javascript
复制
public class Counter {  
    private int count = 0;  
  
    public synchronized void increment() {  
        count++;  
    }  
  
    public int getCount() {  
        return count;  
    }  
}

四、具体例子

案例1:我们有一个简单的程序,其中有一个计数器变量count,两个线程分别对其进行加1操作。由于没有进行同步处理,结果可能会出现数据不一致的情况。

代码语言:javascript
复制
public class Counter {  
    private int count = 0;  
  
    public void increment() {  
        count++;  
    }  
  
    public int getCount() {  
        return count;  
    }  
}  
  
public class ThreadA extends Thread {  
    private Counter counter;  
  
    public ThreadA(Counter counter) {  
        this.counter = counter;  
    }  
  
    @Override  
    public void run() {  
        for (int i = 0; i < 10000; i++) {  
            counter.increment();  
        }  
    }  
}  
  
public class ThreadB extends Thread {  
    private Counter counter;  
  
    public ThreadB(Counter counter) {  
        this.counter = counter;  
    }  
  
    @Override  
    public void run() {  
        for (int i = 0; i < 10000; i++) {  
            counter.increment();  
        }  
    }  
}

案例分析:为什么会出现数据不一致的情况?

e960ca1a66b94ac0b181c0ee8ae73f21~tplv-k3u1fbpfcp-jj-mark 3024 0 0 0 q75.webp
e960ca1a66b94ac0b181c0ee8ae73f21~tplv-k3u1fbpfcp-jj-mark 3024 0 0 0 q75.webp

上图存在一种情况就是,线程A线程B如果几乎同时读取 i = 0 到自己的工作内存中。

线程A执行 i++  结果后将 i = 1 赋值给工作内存;但是这个时候还没来的将最新的结果刷新回主内存的时候线程B读取主内存的旧值 i = 0 ,然后执行use指令将 i = 0的值传递给线程B去进行操作了。

即使这个时候线程A立即将 i = 1刷入主内存那也晚了线程B已经使用旧值 i = 0进行操作了,像这种情况计算结果就不对了。

解决方案: 可以使用synchronized关键字对increment()方法进行同步处理,以确保同一时刻只有一个线程可以访问该方法。这样就可以避免数据竞争和数据不一致的问题。

代码语言:javascript
复制
public class Counter {  
    private int count = 0;  
  
    public synchronized void increment() {  
        count++;  
    }  
  
    public int getCount() {  
        return count;  
    }  
}

案例2:我们有一个程序,其中有两个线程A和B,它们需要共享一个布尔型变量flag。线程A负责将flag设置为true,线程B负责在flag为true时执行一些操作。

代码语言:javascript
复制
public class FlagExample {  
    private volatile boolean flag = false;  
  
    public class ThreadA extends Thread {  
        @Override  
        public void run() {  
            flag = true;  
        }  
    }  
  
    public class ThreadB extends Thread {  
        @Override  
        public void run() {  
            if (flag) {  
                // 执行一些操作  
            }  
        }  
    }  
}

案例分析:由于没有进行同步处理,可能会出现线程B已经读取了flag的旧值(false),而在线程A还没有更新flag之前,线程B就执行了操作的情况。这样就会导致线程B执行了不必要的操作。

解决方案:可以使用synchronized关键字对setFlag()方法和flag变量进行同步处理,以确保同一时刻只有一个线程可以访问该方法和变量。这样就可以避免线程B执行了不必要的操作的问题。

代码语言:javascript
复制
public class FlagExample {  
    private volatile boolean flag = false;  
    private final Object lock = new Object();  
  
    public class ThreadA extends Thread {  
        @Override  
        public void run() {  
            synchronized (lock) {  
                flag = true;  
            }  
        }  
    }  
  
    public class ThreadB extends Thread {  
        @Override  
        public void run() {  
            synchronized (lock) {  
                if (flag) {  
                    // 执行一些操作  
                }  
            }  
        }  
    }  
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、引言
  • 二、Java线程安全概念
  • 三、Java线程安全策略
  • 四、具体例子
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档