java多线程入门(二)如何保证数据原子性

简介: java多线程入门(二)如何保证数据原子性

1.多线程有问题的例子



网络异常,图片无法展示
|


2.为了解决上面个的问题我们可以进行那些操作



2.1加锁


2.1.1锁 synchronized


Synchronized的升级顺序是 无锁–>偏向锁–>轻量级锁–>重量级锁,顺内不可逆

使用很简单写在非静态方法上锁的对象为this

写在静态方法中的时候锁的对象为当前的类


  • 同一个对象写在非静态方法上
  • 不同对象写在静态方法上
  • 每次获取到锁的线程执行完,才能让下个获取锁的线程执行,每次执行是同步的


网络异常,图片无法展示
|


写在代码块中注意锁的对象 synchronized(obj){}和方法的静态非静态一样


网络异常,图片无法展示
|


网络异常,图片无法展示
|


2.1.2锁 lock


lock锁获取和释放时手工的,所以用的时候注意在finally中释放锁


private static int money = 0;
  Lock lock=new ReentrantLock();
  @Override
  public void run() {
    for (int i = 0; i < 10000; i++) {
      lock.lock();
      money++;
      lock.unlock();
    }
    System.out.println("处理后金额:" + money);
  }
复制代码


lock锁的一些方法


方法名称 描述
void lock() 获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
void lockInterruptibly() 获取锁,如果可用并立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和lock()方法不同的是在锁的获取中可以中断当前线程(相应中断)。
Condition newCondition() 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
boolean tryLock() 只有在调用时才可以获得锁。如果可用,则获取锁定,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false 。
boolean tryLock(long time, TimeUnit unit) 超时获取锁,当前线程在一下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false.
void unlock() 释放锁。

lockInterruptibly()


通过这个方法获取锁的时候,是可以相应中断的。但是一旦获取了锁之后是不会相应中断的,因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。Synchronized只有获取到锁之后才能中断,等待锁时不可中断


2.1.3锁 ReadWriteLock锁


//返回用于读取操作的锁  
Lock readLock()   
//返回用于写入操作的锁  
Lock writeLock() 
ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
lock.readLock().unlock();
lock.writeLock().lock();
lock.writeLock().unlock();
复制代码


ReentrantLock 是互斥锁。

ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作;多个度之间不是互斥的,但是写和读是互斥的,写合写之间是互斥的;只要没有 writer,读取锁可以由多个 reader 线程同时保持,而写入锁是独占的。


2.2 synchronized和lock的区别


  • synchronized是关键字当有异常抛出时系统会自动释放锁,
    lock是个接口 释放锁需要手动,需要放在finally{}
  • synchronized是非公平的,
    lock可以设置公平和非公平 默认是非公平的
  • synchronized和lock都是可重入锁(就是获取当前的锁,调用别的地方也需要锁这个锁的类型一致可直接获取锁操作)
  • synchronized响应中断的时候需要调方法 Thread.currentThread().isInterrupted()去判断,但是在阻   塞获取锁的时候是无法中断的
    lock的tryLock和lockInterruptibly可以相应中断的
  • synchronized中如果执行比较久的话,其他线程会一直在阻塞等待中;
    lock的话可以使用tryLock和lockInterruptibly 采取中断或者到时间后自动退出;
  • 在读写的时候synchronized是排他锁,每个请求都必须获取到锁;
    ReadWriteLock锁可以把读写分离出来,如果都是读的情况可以同时获取锁提高效率


2.3采用原子性变量


private AtomicBoolean isTrue;
private AtomicInteger num;
private AtomicLong    numLong;
private AtomicReference reference;
复制代码


2.2.1 AtomicReference


public class PeoperEntity {
  private String name;
  private String sex;
  public PeoperEntity(String name,String sex) {
    this.name=name;
    this.sex=sex;
  }
}
public static void main(String[] args) {
  PeoperEntity p1=new PeoperEntity("张三","1");
  PeoperEntity p2=new PeoperEntity("李四","2");
  PeoperEntity p3=new PeoperEntity("王五","3");
  AtomicReference<PeoperEntity> atomicReference = new AtomicReference<>();
  atomicReference.set(p1);
        //如果当前值是p1则修改为p2
  atomicReference.compareAndSet(p1, p2);
  System.out.println(atomicReference.get());
        //如果当前值是p1则修改为p3 因为p1变成了p2所以结果为p2
  atomicReference.compareAndSet(p1, p3);
  System.out.println(atomicReference.get());
  }
复制代码


PeoperEntity [name=李四, sex=2]

PeoperEntity [name=李四, sex=2]

相关文章
|
14天前
|
Java 程序员 容器
Java中的变量和常量:数据的‘小盒子’和‘铁盒子’有啥不一样?
在Java中,变量是一个可以随时改变的数据容器,类似于一个可以反复打开的小盒子。定义变量时需指定数据类型和名称。例如:`int age = 25;` 表示定义一个整数类型的变量 `age`,初始值为25。 常量则是不可改变的数据容器,类似于一个锁死的铁盒子,定义时使用 `final` 关键字。例如:`final int MAX_SPEED = 120;` 表示定义一个名为 `MAX_SPEED` 的常量,值为120,且不能修改。 变量和常量的主要区别在于变量的数据可以随时修改,而常量的数据一旦确定就不能改变。常量主要用于防止意外修改、提高代码可读性和便于维护。
|
6天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
12天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
37 9
|
15天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
12天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
14天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
38 2
|
14天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
25 2
|
14天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
6月前
|
安全 Java
从零开始学习 Java:简单易懂的入门指南之不可变集合、方法引用(二十六)
从零开始学习 Java:简单易懂的入门指南之不可变集合、方法引用(二十六)
下一篇
无影云桌面