Java简单的并发和加排他锁

一、 创建线程的方法

  (一)继承线程类java.lang.Thread

   1、继承Thread类,重写run方法

public class Task extends Thread{

 

   public Task(){}

 

   @Override

   public void run(){

       // 主要逻辑

   }

}

  2、创建对象,启动线程

// 创建线程子类对象

Task t = new Task();

 

// 启动线程

t.start();

 

  (二)用Runnable子类对象来创建java.lang.Thread对象

  1、实现接口Runnable的run方法

public class Task implements Runnable{

 

   public Task(){}

 

   @Override

   public void run(){

       // 主要逻辑

   }

}

  2、用子类对象创建Thread对象

// 创建线程对象

Thread t = new Thread(new Task());

 

// 启动线程

t.start();

 

二、Thread类的其他方法

 1、获取线程ID

public int getId()

 2、获取当前运行的线程对象

public static Thread currentThread()

 3、获取名字

public String getName()

 4、设置为守护线程

public void setDaemon(true);

 5、获取线程的工作状态

public int getState()

 6、插队优先执行

public void join()

 7、休眠

public static void sleep(long  毫秒数)

 

三、原子对象修改

 1、有AtomicInteger和AtomicBoolean两种常用的原子类。

 2、原子类的特点:每次修改变量的值是一次完成的,中间不受到其他线程打扰。

 3、AtomicInteger的主要方法

// 获取值

public int get()

 

// 设置值

public void set(int k)

 

// 加一并且返回值

public int incrementAndGet()

 

// 减一并且返回值

public int decrementAndGet()

 

四、让属性值保持最新

  1、用volatire关键字修饰属性

  案例:

public volatie int k;

 2、每次线程获取属性k都是最新的。

        也就是另外一个线程修改属性k后,当前线程获取k的值是最新的。

 

五、加锁

  (一)用synchronized关键字加锁

   有两种方法:

  1、锁住当前对象

       只有获得对象的锁,才能执行被synchronized修饰的方法。

       通俗解释:只要方法加了关键字synchronized,就是线程安全的。

public class F{

 

    // 线程安全的方法

    public synchronized void add(){

        // 代码

    }

}

 

   2、锁住某个对象

        只有拥有指定对象的锁,才能执行下列的代码块:

// 出现在要求安全执行的方法内部

synchronized(锁对象){

     // 线程安全执行的代码块

}

  案例:

     锁对象一般是Object类型或者它的子类。

public class T{

     // 自定义锁对象

     private Object lock = new Object();

 

    public void add(){

        synchronized(lock){

            // 安全执行的代码块

        }

    }

}

 

  (二)用ReentrantLock来手动加锁

  1、主要方法:

// 加锁

public void lock()

 

// 可中断加锁

public void lockInterruptibly() throws InterruptedException

 

// 解锁

public void unlock()

 

// 获得休眠和唤醒的条件对象

public Condition newCondtion()

   注意:lock()方法执行时,如果获取不到锁,会一直等待,直到获取锁。

 

  2、使用案例

public class Test{

     // 创建锁对象

     private ReentrantLock lock = new ReentrantLock();

 

    public Test(){}

 

    public void add(){

        // 加锁

        lock.lock();

 

        // 需要安全执行的代码块

 

        // 解锁

        lock.unlock();

   }

}

 

<think>我们正在讨论Java并发编程中的乐观悲观。根据引用内容,我们可以总结如下: 1. **悲观**:假设最坏情况,每次访问共享资源时都会上,其他线程必须等待释放才能访问。Java中的synchronizedReentrantLock是典型实现。 2. **乐观**:假设冲突很少发生,因此在访问资源时不会上,而是在更新时检查是否有冲突(通常通过版本号或CAS实现)。如果冲突则重试或放弃。 接下来,我们需要详细解释两者的区别、优缺点以及使用场景。同时,根据引用内容,悲观还分为共享(读排他(写)。 我们将按照以下结构组织回答: 1. 悲观与乐观的概念 2. 实现方式(包括Java中的实现) 3. 区别对比(表格形式) 4. 优缺点分析 5. 适用场景 6. 实际代码示例 最后,生成相关问题。</think>### Java并发编程中的乐观与悲观详解 #### 一、核心概念对比 | **特性** | **悲观** | **乐观** | |-------------------|---------------------------------------|---------------------------------------| | **核心思想** | 假定每次访问都会冲突,先再操作[^2] | 假定冲突概率低,先操作再验证[^1] | | **实现方式** | `synchronized`、`ReentrantLock` | `AtomicXXX`类、版本号机制 | | **类型** | 共享(S)或排他(X)[^4] | 无机制 | | **并发性能** | 高竞争场景性能差(线程阻塞) | 低竞争场景性能更优(无阻塞) | | **数据一致性** | 强一致性 | 最终一致性 | | **典型使用场景** | 银行转账、库存扣减 | 点赞计数、商品浏览数统计 | #### 二、实现原理与代码示例 1. **悲观实现** ```java // synchronized实现 public synchronized void transfer(Account from, Account to, int amount) { if (from.getBalance() >= amount) { from.debit(amount); to.credit(amount); } } // ReentrantLock实现 private final Lock lock = new ReentrantLock(); public void updateInventory() { lock.lock(); // 显式 try { if (stock > 0) stock--; } finally { lock.unlock(); // 必须释放 } } ``` 2. **乐观实现** ```java // AtomicInteger实现(基于CAS) private AtomicInteger viewCount = new AtomicInteger(0); public void addView() { viewCount.incrementAndGet(); // 无自增 } // 版本号机制(数据库层面) UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 1001 AND version = 5; // 版本号校验 ``` #### 三、优缺点分析 **悲观优势**: - 强数据一致性保证 - 编程模型简单直观 - 避免重试开销 **悲观劣势**: - 线程阻塞导致吞吐量下降 - 死风险(需破坏死四条件[^3]) - 粒度过大会限制并发性 **乐观优势**: - 高并发场景吞吐量高 - 避免死问题 - 减少上下文切换开销 **乐观劣势**: - 冲突时需重试(自旋消耗CPU) - 实现复杂度较高 - ABA问题(需`AtomicStampedReference`解决) #### 四、适用场景指南 | **场景特征** | **推荐类型** | **原因说明** | |----------------------------|----------------|----------------------------------| | 写操作频率 > 读操作频率 | 悲观 | 减少冲突时的重试开销 | | 数据竞争激烈(高并发写) | 悲观 | 避免大量失败重试导致的资源浪费 | | 读多写少(如计数器) | 乐观 | 无操作提升吞吐量 | | 操作耗时短(< 100ns) | 乐观 | CAS操作成功率更高 | | 跨服务事务(如分布式系统) | 乐观 | 避免全局导致的系统可用性降低 | #### 五、实战选择策略 1. **优先考虑乐观**的场景: - 并发冲突概率 < 20% - 需支持高吞吐量(如 > 10,000 TPS) - 业务能容忍短暂数据不一致 2. **必须使用悲观**的场景: - 资金交易等强一致性要求 - 操作涉及多个关联资源 - 无法承受重试带来的延迟(如实时交易) > **特殊场景处理**:对于读多写少的场景,可用`StampedLock`的乐观读模式: > ```java > StampedLock lock = new StampedLock(); > long stamp = lock.tryOptimisticRead(); // 不阻塞获取版本号 > if (!lock.validate(stamp)) { // 检查版本变化 > stamp = lock.readLock(); // 退化为悲观读 > try { /* 读取数据 */ } > finally { lock.unlockRead(stamp); } > } > ``` #### 六、性能调优建议 1. **减小粒度** ```java // 错误:整个对象 public synchronized void update() {...} // 正确:细化到共享资源 private final Object stockLock = new Object(); public void updateStock() { synchronized(stockLock) {...} // 只定库存变量 } ``` 2. **升级策略** ```java // 先尝试乐观,失败再升级悲观 if (!atomicCompareAndSet()) { synchronized(this) { // 二次检查后更新 } } ``` 3. **避免嵌套** ```java // 危险:可能引发死 synchronized(lockA) { synchronized(lockB) {...} // 不同线程若以相反顺序获取会导致死 } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值