java线程共享模型之管程(synchronized原理、wait-notify、park方法)

前言


本章主要整理的synchronized的原理,其中设计对象头中monitor的知识,其中,waitSet涉及wait - notify方法,然后,重点刨析了synchronized中的好几种锁对应的流程,在最后,顺便整理了一下park&unpark方法。
下面是一下该文章的前置内容。


前文导航


1.线程与进程等基础概念一次理清

2. java中操作线程打断、创建、查看等基础方法的操作

3. 锁及其操作(synchronized)


一、 变量的线程安全分析

1.1 成员变量与静态变量是否线程安全?

  • 如果它们没有被共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够被改变,又分两种情况
    • 如果只有读操作,则线程安全
    • 如果有读写操作,则这段代码是临界区,需要考虑线程安全。

1.2 局部变量是否线程安全?

  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的
    • 如果逃离了方法的作用访问,需要考虑线程安全。

1.3 局部变量线程安全分析

  • 局部变量

在这里插入图片描述

在这里插入图片描述

  • 局部变量引用的对象

局部变量引用的对象是否线程安全
如果一个局部变量引用的对象没有逃离方法作用域,即这个对象只在当前方法内使用,且不会被其他线程访问或持有,那么它是线程安全的。
如果该对象逃离了方法作用域(例如被返回,或者作为共享数据传递给了外部),那么它可能会被多个线程访问和修改,从而导致线程安全问题。

具体举例:

1. 局部变量引用的对象没有逃离方法作用域 :

在这种情况下,对象在方法内部使用完后就消失了,因此不涉及线程安全问题。

class ThreadSafeLocal {
   
   
    public void process() {
   
   
        String str = "Hello";  // 局部变量,线程安全
        str = str + " World";  // 字符串是不可变的,操作是线程安全的
        System.out.println(str);  // 每个线程有自己的局部副本
    }
}

在这个例子中,str 是局部变量,每个线程调用 process() 时,都会有自己的 str 变量副本。并且 str 引用的 String 是不可变的,内部操作不会影响其他线程。因此,线程是安全的。

2. 局部变量引用的对象逃离了方法作用域 :

如果局部变量引用的对象被传递到方法外部,或者被多个线程共享访问,那么这个对象可能会出现线程安全问题。

class SharedObject {
   
   
    private StringBuilder sb = new StringBuilder();

    public StringBuilder getSb() {
   
   
        return sb;  // sb 被返回到方法外部,可能被多个线程访问
    }
}

class ThreadUnsafeLocal {
   
   
    public void process() {
   
   
        SharedObject sharedObj = new SharedObject();
        StringBuilder sb = sharedObj.getSb();  // sb 被传递到外部
        sb.append(" World");  // 多线程环境下会发生竞争条件
        System.out.println(sb.toString());
    }
}

在这个例子中,sb 是局部变量,但它引用的 StringBuilder 对象是从 SharedObject 返回的,并且可能会被多个线程共享访问。StringBuilder 是可变的,因此多个线程同时对它进行操作时,会发生竞态条件,导致数据错误。

1.4 常见线程安全类


  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

这里说的线程安全是指,多个线程调用它们同一个示例的某个方法时,是线程安全的。也可以理解为 :

  • 它们每个方法是原子的
  • 但注意它们多个方法的组合不是原子的。
多个方法组合调用 :

假设我们有一个 Counter 类,它包含两个方法:increment()getCount()increment() 会增加计数器的值,而 getCount() 会返回当前的计数值。现在我们想通过 increment()getCount() 的组合来增加计数器的值并获取最新的计数。

如果没有同步机制,多个线程同时调用 increment()getCount() 方法时,可能会导致结果不一致,因为这些方法的组合操作(即获取计数值并更新)并不是原子的。

class Counter {
   
   
    private int count = 0;

    public void increment() {
   
   
        count++;  // 不是原子的
    }

    public int getCount() {
   
   
        return count;  // 也是线程安全的,但它只读取,不会修改
    }

    public void incrementAndGet() {
   
   
        increment();
        System.out.println(getCount());  // 方法组合不是原子的
    }
}

public class ThreadUnsafeExample {
   
   
    public static void main(String[] args) throws InterruptedException {
   
   
        Counter counter = new Counter();

        // 创建两个线程,它们同时调用 incrementAndGet
        Thread t1 = new Thread(() -> {
   
   
            for (int i = 0; i < 1000; i++) {
   
   
                counter.incrementAndGet();  // 增加计数并打印
            }
        });

        Thread t2 = new Thread(() -> {
   
   
            for (int i = 0; i < 1000; i++) {
   
   
                counter.incrementAndGet();  // 增加计数并打印
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final count: " + counter.getCount());  // 可能不会是 2000
    }
}

incrementAndGet():该方法组合了两个操作:首先调用 increment(),然后调用 getCount()。即使每个方法内部是线程安全的(getCount() 只是读取数据,没有修改),方法的组合操作仍然不是线程安全的。因为在 increment() 执行时,如果有多个线程同时调用这个组合方法,它们会竞争修改 count<

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值