关于锁的对象

多线程并发下不同同步场景:短信、电话与普通方法的执行顺序

锁的对象共有两种:类对象和Class模板对象

1.标准情况下,两个线程、一个对象,先打印发短信还是打电话?发短信
2.发短信方法设置延迟4秒后,两个线程、一个对象,先打印发短信还是打电话?发短信
Synchronized锁的对象是方法的调用者,故两个方法用的是同一把锁,谁先拿到谁执行

package com.itheima.lock8;

import java.util.concurrent.TimeUnit;

public class Test01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();
    }
}

//资源类
class Phone{
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"发短信");
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"打电话");
    }
}

3.两个线程、一个对象、一个同步方法、一个普通方法,先打印发短信还是hello?hello
因为发短信方法会延迟4秒,hello是普通方法,无锁,不是同步方法,不受锁的影响,不存在抢占资源情况。

package com.itheima.lock8;

import java.util.concurrent.TimeUnit;

public class Test02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sendMsg();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.hello();
        },"B").start();
    }
}

//资源类
class Phone2{
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"发短信");
    }
    public synchronized void call(){
        
        System.out.println(Thread.currentThread().getName()+"打电话");
    }

    //普通方法
    public void hello(){
        System.out.println(Thread.currentThread().getName()+"=>hello");
    }
}

4.两个线程、两个对象、两个同步方法,先打印发短信还是打电话?打电话
两个对象会存在两把锁,锁不一样,故按照时间顺序来执行

package com.itheima.lock8;

import java.util.concurrent.TimeUnit;

public class Test03 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(()->{
            phone1.sendMsg();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}

//资源类
class Phone3{
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"发短信");
    }
    public synchronized void call(){

        System.out.println(Thread.currentThread().getName()+"打电话");
    }

    //普通方法
    public void hello(){
        System.out.println(Thread.currentThread().getName()+"=>hello");
    }
}

5.两个线程、一个对象、两个静态同步方法,先打印发短信还是打电话?发短信
当同步方法为静态时,锁的是其Class模板,一个类只有一个Class对象

package com.itheima.lock8;

import java.util.concurrent.TimeUnit;

public class Test04 {
    public static void main(String[] args) {
        Phone4 phone4 = new Phone4();
        new Thread(()->{
            phone4.msg();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone4.call();
        },"B").start();
    }
}
class Phone4{
    public static synchronized void msg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

6.两个线程、两个对象、两个静态同步方法,先打印发短信还是打电话?发短信
当同步方法为静态时,锁的是其Class模板,一个类只有唯一的一个Class对象

package com.itheima.lock8;

import java.util.concurrent.TimeUnit;

public class Test04 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{
            phone1.msg();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
class Phone4{
    public static synchronized void msg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

7.两个线程、一个对象、一个静态同步方法、一个普通同步方法,先打印发短信还是打电话?打电话
静态同步方法锁的是Class类对象,普通同步方法锁的是类对象,因此拥有两把锁,互不冲突

package com.itheima.lock8;

import java.util.concurrent.TimeUnit;

public class Test05 {
    public static void main(String[] args) {
        Phone5 phone = new Phone5();

        new Thread(()->{
            phone.msg();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone5{
    public static synchronized void msg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

8.两个线程、两个对象、一个静态同步方法、一个普通同步方法,先打印发短信还是打电话?打电话
拥有两个锁,故先输出没有延时的

package com.itheima.lock8;

import java.util.concurrent.TimeUnit;

public class Test05 {
    public static void main(String[] args) {
        Phone5 phone1 = new Phone5();
        Phone5 phone2 = new Phone5();

        new Thread(()->{
            phone1.msg();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
class Phone5{
    public static synchronized void msg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
<think> 我们正在分析关于对象的说法,判断哪个选项是错误的。选项如下: A. Boolean类型不适合作为使用 B. 在异常条件下,保证释放已持有的 C. Integer类型不适合作为使用 D. 使用相同的方式请求和释放,避免死 根据引用内容,我们可以逐一分析: 1. **引用[1]**:讨论了Java的类型和使用注意事项,包括的粒度、死避免、性能等。特别提到要选择合适的类型,并注意避免死。 2. **引用[2]**:关于的自动续期机制,确保不会在持有者未释放时过期。 3. **引用[3]和[4]**:强调了释放时需确保只释放自己的,通过检查value值(或使用Lua脚本保证原子性)来避免误删其他线程的。 4. **引用[5]**:介绍了在Spring Boot中使用Redis分布式的几种方式。 现在分析各选项: - **A. Boolean类型不适合作为使用**:在Java中,对象可以是任意对象,但通常使用`Object`实例。Boolean类型只有两个值(`TRUE`和`FALSE`),如果多个共享同一个Boolean对象(如`Boolean.TRUE`),会导致不同之间互相干扰。因此,Boolean不适合作为对象,因为它的实例有限,容易造成不同之间冲突[^1]。所以这个说法正确。 - **B. 在异常条件下,保证释放已持有的**:这是正确的做法。引用[1]提到避免死的重要性,而引用[3]和[4]强调即使在异常情况下也要释放(例如通过`finally`块),否则可能导致死。此外,引用[2]的自动续期机制也是为了确保不会因未释放而引发问题。所以这个说法正确。 - **C. Integer类型不适合作为使用**:Integer类型和Boolean类似,也存在实例缓存问题(例如`Integer.valueOf(1)`可能返回同一个对象)。如果多个使用相同的Integer值(尤其小整数),会导致不同之间共享同一个对象,从而造成非预期的同步。因此,Integer也不适合作为对象。这个说法正确。 - **D. 使用相同的方式请求和释放,避免死**:这个说法有缺陷。虽然引用[1]提到要避免死,但“使用相同的方式”过于模糊。例如: - 内置(synchronized)和显式(ReentrantLock)的请求和释放方式不同:synchronized自动释放,而ReentrantLock需要手动`unlock()`。 - 即使同一类型,如果请求和释放不对称(如多次lock但只unlock一次),也可能导致死。 - 更重要的是,避免死需要更多策略,如顺序加、超时机制等(引用[1]),而非仅仅“相同方式”。 选项D的错误在于:**“使用相同的方式”并不是避免死的关键,关键在于的请求和释放必须对称(如lock/unlock成对出现),且需避免循环等待**。单纯“相同方式”无法避免死(例如两个线程以相同顺序请求两个不同的,但如果其中一个线程异常未释放,仍可能死)。 因此,**D选项的说法是错误的**,因为它没有准确描述避免死的核心要求。 ### 结论 错误选项是**D**,理由如下: - A、B、C均正确:Boolean/Integer因实例共享问题不适合做;异常时需保证释放。 - D不严谨:避免死需要成对释放和有序请求,而非简单“相同方式”。 §§相关问题§§ 1. 为什么Java中不建议使用基本类型的包装类(如Integer、Boolean)作为对象? 2. 在分布式系统中如何实现的自动续期机制?[^2] 3. 如何通过顺序控制来预防死?[^1] 4. Redis分布式释放时为什么要使用Lua脚本保证原子性?[^4] </think> ### 对象说法错误选项分析 根据提供的引用内容和机制原理,我们逐项分析: 1. **A. Boolean类型不适合作为使用** **正确** - Boolean只有`true/false`两个值,所有`Boolean.TRUE`是同一个对象 - 多场景下不同线程可能共享同一对象,导致非预期同步 ```java // 错误示例:多个共享Boolean.TRUE synchronized(Boolean.TRUE) { ... } // 线程1 synchronized(Boolean.TRUE) { ... } // 线程2 实际共享同一 ``` 2. **B. 在异常条件下,保证释放已持有的** **正确(关键实践)** - 引用[1]强调避免死需确保释放 - 引用[3][4]说明异常时需通过`finally`块或自动续期机制释放[^2] ```java ReentrantLock lock = new ReentrantLock(); try { lock.lock(); // 临界区代码(可能抛出异常) } finally { lock.unlock(); // 确保异常时释放 } ``` 3. **C. Integer类型不适合作为使用** **正确** - Integer存在缓存池(-128~127返回同一对象) - 数值相同时可能意外共享对象 ```java Integer lock1 = 100; // 实际指向缓存同一对象 Integer lock2 = 100; // 非预期共享! ``` 4. **D. 使用相同的方式请求和释放,避免死** **错误(本题答案)** - **核心缺陷**:相同请求/释放方式**不能保证避免死** - 死根本原因(引用[1]): - 循环等待(线程A持1等2,线程B持2等1) - 嵌套未按固定顺序获取 - **正确方式**(引用[1][5]): - 顺序全局一致性(所有线程按固定顺序加) - 超时机制(`tryLock(timeout)`) - 死检测(如Redisson的看门狗机制[^5]) ### 总结 **错误选项:D** 最严重错误是认为"相同请求/释放方式可避免死"。实际需要更复杂的死预防策略: 1. 全局顺序控制(如按对象Hash排序加)[^1] 2. 尝试+超时机制(`lock.tryLock(100ms)`) 3. 使用框架级管理(如Redisson的`RFuture<Boolean> tryLockAsync()`[^5])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值