百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术流 > 正文

【干货】探秘如何通过synchronized关键字实现线程同步

citgpt 2024-08-01 13:31 8 浏览 0 评论

#来点儿干货#


1. 引言

在Java的多线程编程领域,掌握线程安全性是你代码成功运行的关键。当多线程同时涉及共享数据的读写时,隐患就像一把悬在头顶的利刃,可能导致程序行为的不可预测性。本文将为你揭示线程安全性的奥秘,并深入研究如何通过Java中强大的synchronized关键字实现线程的安全同步。让我们一同踏入这个精彩而关键的多线程世界。

【干货】探秘如何通过synchronized关键字实现线程同步

2. 线程安全性

2.1. 什么是线程安全性

线程安全性是指在多线程环境中,对共享数据的访问不会引起不一致或异常的状态。在多线程中,多个线程可能同时访问和修改相同的数据,因此确保线程安全性是至关重要的。

2.2. 为什么需要线程安全性

  • 共享数据: 多个线程共享同一份数据。
  • 竞态条件: 多个线程同时修改数据可能导致竞态条件,即结果依赖于执行的顺序。
  • 数据一致性: 确保多线程环境下数据的一致性,防止脏读、幻读等问题。

3. synchronized关键字

3.1 概述

synchronized关键字是Java中用于实现线程同步的主要机制。它可以用于方法或代码块,确保在同一时刻只有一个线程执行被 synchronized修饰的部分。我们通常将其应用于三种不同类型的对象锁,以确保线程同步和避免并发问题。3.2节是这三种对象锁介绍以及适用场景说明。

3.2 三种对象锁

3.2.1 实例方法锁

当synchronized关键字用于实例方法时,它锁定的是当前实例对象。只有一个线程能够在同一时刻访问该实例对象的同步方法。

public class Example {
    public synchronized void instanceMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(100); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Example example = new Example();

        // 创建两个线程,分别调用同一个实例的 instanceMethod 方法
        Thread thread1 = new Thread(() -> {
            example.instanceMethod();
        }, "Thread-1");

        Thread thread2 = new Thread(() -> {
            example.instanceMethod();
        }, "Thread-2");

        // 启动线程
        thread1.start();
        thread2.start();
    }
}

3.2.2 静态方法锁

当synchronized关键字用于静态方法时,它锁定的是整个类的Class对象。这意味着只有一个线程能够在同一时刻访问该类的所有静态同步方法。

public class Example {
    public static synchronized void staticMethod() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(100); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // 创建两个线程,分别调用同一个类的 staticMethod 方法
        Thread thread1 = new Thread(Example::staticMethod, "Thread-1");

        Thread thread2 = new Thread(Example::staticMethod, "Thread-2");

        // 启动线程
        thread1.start();
        thread2.start();
    }
}

3.2.3 同步代码块锁

当synchronized关键字用于代码块时,需要指定一个对象作为锁。只有获取了该对象的锁的线程能够执行该代码块。这样提供了更细粒度的控制。

public class Example {
    private final Object lock = new Object();

    public void someMethod() {
        // 非线程安全的操作
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(100); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        synchronized (lock) {
            // 同步代码块线程安全的操作
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    Thread.sleep(100); // 模拟耗时操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Example example = new Example();

        // 创建两个线程,分别调用同一个实例的 someMethod 方法
        Thread thread1 = new Thread(() -> {
            example.someMethod();
        }, "Thread-1");

        Thread thread2 = new Thread(() -> {
            example.someMethod();
        }, "Thread-2");

        // 启动线程
        thread1.start();
        thread2.start();
    }
}

这三种对象锁分别应用于实例方法、静态方法和代码块,提供了不同层次的锁定粒度。选择合适的对象锁类型取决于具体的应用场景和需求。

  • 实例方法锁锁定的是当前实例对象
  • 静态方法锁锁定的是整个类
  • 同步代码块锁允许更灵活地选择锁定的对象

通过合理应用这些对象锁,可以实现对共享资源的有效保护,确保程序在多线程环境中的稳定性和正确性。

4. synchronized的优缺点

4.1. 优点

  • 简单易用: 使用简便,无需手动管理锁。
  • 内置支持: Java内置的关键字,方便使用。

4.2. 缺点

  • 细粒度不足: 可能导致过度同步,影响性能。
  • 效率相对较低: 某些情况下,采用更精细的锁可能提高效率。


5. 替代方案与并发包

5.1. Lock接口

使用Lock接口及其实现类提供更灵活的锁定机制。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private final Lock lock = new ReentrantLock();

    public void someMethod() {
        lock.lock();
        try {
            // 线程安全的操作
        } finally {
            lock.unlock();
        }
    }
}

5.2 ConcurrentHashMap

多线程环境下的高效Map实现。

import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

public class ConcurrentHashMapExample {
    private final Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();

    public void addToMap(String key, int value) {
        concurrentMap.put(key, value);
    }
}

5.3 AtomicInteger等原子类

提供原子操作,避免了使用synchronized的需要。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    private final AtomicInteger counter = new AtomicInteger(0);

    public void increment() {
        counter.incrementAndGet();
    }
}

5.4 java.util.concurrent包提供的工具类

包含一系列用于并发编程的工具和数据结构。

import java.util.concurrent.CountDownLatch;

public class ConcurrentExample {
    private final CountDownLatch latch = new CountDownLatch(1);

    public void someMethod() throws InterruptedException {
        // 非线程安全的操作

        latch.await(); // 等待其他线程完成某个操作
    }
}

6. 最佳实践

6.1 同步的合理使用

  • 避免过度同步: 细粒度控制同步块,避免锁的争用。
  • 同步块粒度控制: 尽量减小同步块的范围,提高并发度。

6.2 使用并发工具

  • 并发集合: ConcurrentHashMap等。
  • 同步队列: BlockingQueue等。

7. 总结

在多线程编程中,确保线程安全性是至关重要的。synchronized关键字作为Java中的主要同步机制,通过简单易用的语法提供了基本的线程同步功能。

然而,在实际应用中,需要根据具体场景选择适当的锁机制或并发工具,以提高性能和避免潜在的问题。通过合理的同步使用和选择合适的工具,我们能够更好地编写稳定、高效的多线程程序。

相关推荐

js中arguments详解

一、简介了解arguments这个对象之前先来认识一下javascript的一些功能:其实Javascript并没有重载函数的功能,但是Arguments对象能够模拟重载。Javascrip中每个函数...

firewall-cmd 常用命令

目录firewalldzone说明firewallzone内容说明firewall-cmd常用参数firewall-cmd常用命令常用命令 回到顶部firewalldzone...

epel-release 是什么

EPEL-release(ExtraPackagesforEnterpriseLinux)是一个软件仓库,它为企业级Linux发行版(如CentOS、RHEL等)提供额外的软件包。以下是关于E...

FullGC详解  什么是 JVM 的 GC
FullGC详解 什么是 JVM 的 GC

前言:背景:一、什么是JVM的GC?JVM(JavaVirtualMachine)。JVM是Java程序的虚拟机,是一种实现Java语言的解...

2024-10-26 08:50 citgpt

使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
  • 使用Spire.Doc组件利用模板导出Word文档
跨域(CrossOrigin)

1.介绍  1)跨域问题:跨域问题是在网络中,当一个网络的运行脚本(通常时JavaScript)试图访问另一个网络的资源时,如果这两个网络的端口、协议和域名不一致时就会出现跨域问题。    通俗讲...

微服务架构和分布式架构的区别

1、含义不同微服务架构:微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并...

深入理解与应用CSS clip-path 属性
深入理解与应用CSS clip-path 属性

clip-pathclip-path是什么clip-path 是一个CSS属性,允许开发者创建一个剪切区域,从而决定元素的哪些部分可见,哪些部分会被隐...

2024-10-25 11:51 citgpt

HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
  • HCNP Routing&Switching之OSPF LSA类型(二)
Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
  • Redis和Memcached的区别详解
Request.ServerVariables 大全

Request.ServerVariables("Url")返回服务器地址Request.ServerVariables("Path_Info")客户端提供的路...

python操作Kafka

目录一、python操作kafka1.python使用kafka生产者2.python使用kafka消费者3.使用docker中的kafka二、python操作kafka细...

Runtime.getRuntime().exec详解

Runtime.getRuntime().exec详解概述Runtime.getRuntime().exec用于调用外部可执行程序或系统命令,并重定向外部程序的标准输入、标准输出和标准错误到缓冲池。...

promise.all详解 promise.all是干什么的
promise.all详解 promise.all是干什么的

promise.all详解promise.all中所有的请求成功了,走.then(),在.then()中能得到一个数组,数组中是每个请求resolve抛出的结果...

2024-10-24 16:21 citgpt

Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解
  • Content-Length和Transfer-Encoding详解

取消回复欢迎 发表评论: