线程安全与synchronized

线程安全是并行程序的基础,确保执行结果的正确性。一个并发编程的例子展示了多线程环境下计数器可能出现的问题,即线程不安全导致预期结果无法保证。Java的synchronized关键字用于解决这类问题,提供加锁机制,保证多个线程对共享资源的同步访问。它可以用于指定加锁对象、实例方法和静态方法,确保线程间的同步、可见性和有序性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程安全与synchronized

线程安全的概念

并发程序开发的一大关注重点就是线程安全。一般来说,程序并行化是为了获得更高的执行效率,但前提是,高效率不能以牺牲正确性为代价。如果程序并行化后,连基本的执行结果的正确性都无法保证,那么并行程序本身也就没有任何意义了。因此,线程安全就是并行程序的根本和根基。

问题示例

下面的代码演示一个计数器,两个线程同时对i进行累加操作,各执行1000000次。我们希望的执行结果当然是最终的i的值可以达到2000000,但事实并非总是像我们期望的那样。多次执行,大部分i的最终值会小于2000000。因为两个线程同时对i写入是,其中一个线程的结果会覆盖另外一个(虽然i被声明为volatile变量)

public class SynchronizedTest implements Runnable {
    static SynchronizedTest instance = new SynchronizedTest();
    static volatile int count = 0;

    @Override
    public void run() {
        for (int i=0;i<1000000;i++){
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = [" + count + "]");
        //count = [187898]
    }
}

如果在代码中发生了类型的情况,这就是多线程不安全的恶果。线程1和线程2同时读取i为0,并各自计算得到i=1,并先后写入这个结果,因此,虽然i++被执行了两次,但是实际i的值只增加了1。
要从根本上解决这个问题,就必须保证多个线程在对i进行操作时完全同步。当线程1在写入时,线程2不但不能写,同时也不能读。在线程1写完之前,线程2读取的一定是一个过期的数据。Java中提供了重要的关键字synchronized来实现这个功能。
关键字synchronized有多种用法:

  • 指定加锁对象: 对给定对象加锁,进入同步代码前要获得给定对象的锁
  • 直接作用于实例方法: 相当于对当前实例加锁,进入同步代码前要获得当前实例的锁
  • 直接作用于静态方法: 相当于对当前类加锁,进入同步代码前要获得当前类的锁

1.指定加锁对象

public class SynchronizedTest implements Runnable {
   static SynchronizedTest instance = new SynchronizedTest();
   static int count = 0;

   @Override
   public void run() {
       for (int i=0;i<1000000;i++){
           synchronized (instance){
               count++;
           }
       }
   }

   public static void main(String[] args) throws InterruptedException{
       Thread t1 = new Thread(instance);
       Thread t2 = new Thread(instance);
       t1.start();
       t2.start();
       t1.join();
       t2.join();
       System.out.println("count = [" + count + "]");
   }
}

2.直接作用于实例方法

public class SynchronizedTest implements Runnable {
    static SynchronizedTest instance = new SynchronizedTest();
    static int count = 0;

    private synchronized void add(){
        count++;
    }
    @Override
    public void run() {
        for (int i=0;i<1000000;i++){
            add();
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = [" + count + "]");
    }
}

直接作用于实例方法,为了保证线程间的正常同步,传入Thread的Runnable需是同一个实例对象。

3.直接作用于静态方法

public class SynchronizedTest implements Runnable {
    static SynchronizedTest instance = new SynchronizedTest();
    static int count = 0;

    private static synchronized void add(){
        count++;
    }
    @Override
    public void run() {
        for (int i=0;i<1000000;i++){
            add();
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(new SynchronizedTest());
        Thread t2 = new Thread(new SynchronizedTest());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = [" + count + "]");
    }
}

直接作用于静态方法,即使执行这段代码的两个实例指向了不同的Runnable对象,线程间还是可以正常同步。

synchronized除了用于线程安全同步、确保线程安全外,还可以保证线程间的可见性和有序性。从可见性的角度将,synchronized可以完全替代volatile的功能,只是使用上没有那么方便。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值