`
qindongliang1922
  • 浏览: 2183623 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
7265517b-f87e-3137-b62c-5c6e30e26109
证道Lucene4
浏览量:117522
097be4a0-491e-39c0-89ff-3456fadf8262
证道Hadoop
浏览量:125920
41c37529-f6d8-32e4-8563-3b42b2712a50
证道shell编程
浏览量:59881
43832365-bc15-3f5d-b3cd-c9161722a70c
ELK修真
浏览量:71297
社区版块
存档分类
最新评论

关于Java里面多线程同步的一些知识

    博客分类:
  • JAVA
阅读更多
# 关于Java里面多线程同步的一些知识

对于任何Java开发者来说多线程和同步是一个非常重要的话题。比较好的掌握同步和线程安全相关的知识将使得我们则更加有优势,同时这些知识并不是非常容易就能熟练掌握的,实际上写出正确的并发代码是一件比较困难的事情。在Java的自带的库里面,已经包含了非常多实用的并发工具类,今天这篇文章,我们主要来学习Java里面synchronized关键字的相关知识。


在这之前,我们应该知道Java里面已经提供了不少的同步工具类,如volatile关键字,atomic变量,
synchronized关键字,Lock接口及其比较常用的实现类ReentrantLock,ReentrantReadWriteLock
因为synchronized出现的较早,所以我们更应该理解其与其他同步工具的区别和联系。在多线程程序里面存在死锁,数据竞争,线程安全等一系列问题,清晰的理解同步概念是我们写出正确程序的重要保障。

### 线程同步是什么

同步是Java多线程编程里面重要的概念,我们知道Java是一门多线程编程语言,可以充分的利用当代cpu多core的优势,当多个线程并发或者并行的修改或者访问共享变量时,可能会出现内存不一致的错误。为了避免这些错误的发生,我们需要让我们的代码合理的同步通过互斥来保证对于临界区资源的访问不能同时存在多个线程访问。
### 为什么需要线程同步

在一个多线程的应用里面,如果你的代码里包含了状态可变的共享变量,那么为了避免共享变量的对象状态出现问题或者发生一些不可预知的行为,你需要通过同步操作来确保程序正确的运行。当然如果你的共享变量的仅仅是只读的或者是不可变的对象,那么你完全不需要同步操作。在java里面同步操作可以保证在任何时候同步的数据块只能有一个线程可以访问。

### 关于synchronized关键字

synchronized关键字是Java里面被大量使用的一个同步工具,它的一些功能如下:

(1)提供了锁操作,可以对于共享资源的访问进行同步从而避免数据竞争

(2)可以避免部分重排序问题,注意是部分不是所有

看下面一段代码:

```
    public void demo1(){

        //1

        int a=1;
        int x=3;

        // 2
        synchronized (this){
            int b=5;
            StringBuffer buffer=new StringBuffer("abc");
            int c=6;
        }

        //3
        int e=4;
        int y=7;

    }
```


上面的代码我分了三部分,其中1,2,3总体执行顺序不会变,因为中间的是同步块,避免部分重排序,但是1,2,3块内部是可以执行重排序的,比如a和x是可有可能重排的,e和y也是有可能重排的,b和c变量是有可能重排的,buffer变量自身都有可能重排,这是因为对象的初始化包括三步:分配内存,初始化构造函数,引用地址,这也是为什么在双检锁里面单例的变量仍然需要volatile关键字来修饰的原因,通过volatile关键字可以保证对象初始化是原子的,内部是设立内存屏障把读操作屏蔽在写操作完成之后。

(3)自动包含加锁和释放锁两个功能。当线程进入一个synchronized修饰的方法或者代码块,它先需要获取锁,获取之后会自动的从主内存获取数据而不是自己的local cache中,当它释放锁的时候,会刷新写操作进入主内存中从而消除内存不一致的问题。


(4)使用方式有同步块和同步方法两种,注意其不能修饰变量,否则会编译错误。 部分场景下如保证可见性,可以使用volatile关键词来完成。除非另有说明大多数情况下应该优先使用同步代码块而非同步方法,仅仅锁住需要加锁的部分代码,而不是为了省事直接锁住整个方法这样会导致更低的效率。

(5)进入临界区需要获取锁,退出临界区会释放锁,这里需要注意的是如果在临界区发生未知异常或者错误,或者执行了break,return,Java仍会保证释放锁。

(6)同步块的条件不能是null,否则会抛出空指针异常

(7)synchronized的一个主要缺点是,不允许并发读,这在一些场景下会降低应用的吞吐量,我们可以通过jdk5之后的读写锁来规避这个缺点。

(8)这里的同步仅仅在一个jvm进程中,如果你需要在多个jvm里面实现同步或者互斥操作,需要考虑使用分布式锁如zookeeper,或者redis等

(9)对于同步的静态方法和非静态方法是可以同时访问的,因为他们加锁的一个是类,一个是实例。

(10)在java5之后,通过volatile修饰的变量,可以保证声明赋值的过程是原子的,尤其在基本类型里面要注意long和double的变量声明赋值,默认不是原子的,如果要在多线程里面使用应该优先考虑使用volatile保证声明的原子性。另外volatile在这种场景性能更优于synchronized关键字。

(11)synchronized使用不当会导致死锁和活锁,这里需要注意。

(12)synchronized不能用于修饰构造方法。这一点看起来比较奇怪,其实思考一下,也有道理。因为即使你对构造方法加锁,它仍然会出现由于重排序导致不
正确的对象的状态被泄露,这一点我在双检锁深入分析时提到过。


(13)synchronized不能修饰变量,volatile关键字不能出现在方法内

(14)java并发包里面提供了更加完善和性能更好的Lock对象。比如被synchronized等待的或者阻塞的线程是没法被打断或者超时的,这个可以在java5之后新的并发包里面使用ReadWriteLock和ReentrantLock来解决,其次在新的并发包里面我们可以对锁的控制粒度更细,比如在一些场景下我可以在一个方法中获取锁,在另外一个方法中释放锁,这是synchronized做不到的。另外,新的并发包我们可以实现非阻塞的操作,通过tryLock方法,如果在synchronized块中,一但有人占用锁,你必须无限的等待,中间什么都不能干,而使用tryLock方法,我们可以知道有人再占用锁,我们先去忙自己的,一会再来看看是否还有人占用,这就是典型的非阻塞。最后新的并发工具底层通过组合使用CAS操作,volatile变量和atomic变量以获得更好的性能。

(15)不推荐使用非final字段作为synchronized block的锁条件,也不推荐使用String类型作为锁条件,因为其引用可变,最佳做法是使用final修饰的Object对象。

### 关于volatile关键字

在这里我们再复习下volatile关键字的功能,这里有一个简单的例子:

```
//x、y为非volatile变量
//flag为volatile变量

x = 2;        //语句1
y = 0;        //语句2
flag = true;  //语句3
x = 4;         //语句4
y = -1;       //语句5
```


volatile可以禁止重排序保证部分的有序性,比如上面的语句,第三个变量是volatile修饰的,这样一来语句3不会被放到语句1和2前面,也不会放到4和5后面,但语句1和2的顺序不保证,同理4和5的顺序不保证。

另外一个例子:

```
//线程1:
context = loadContext();   //语句1
inited = true;             //语句2

//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

```

上面的代码在单线程里面没有问题,在多线程就不一定了,如果语句1和2发生重排序,语句2线执行,同时另外一个线程2刚好访问语句2,这样就会发生重排序导致不一致问题。这个时候我们通过volatile修饰语句2就可以避免重排序,同时由于可见性,线程2能及时感知变化就可以正常访问。

### 对比synchronized和volatile

我们需要从三个方面原子性,可见性,有序性来看他们:

(1)原子性:
synchronize可以保证在本线程内多个步骤操作的原子性,即同一时刻只能有一个线程操作。线程外不保证,参考双检锁的问题案例。

volatile可以在多线程下仅保证单个步骤的原子性,比如变量的赋值。

(2)可见性:

这里我想强调的是volatile是无条件的可见性(jvm保证),不需要额外的条件,其他的线程都能看到,这里有一点需要注意对于引用类型,volatile只保证引用可见,不保证引用内容可见,比如数组或者对象。synchronized关键字是有条件的可见性,其他的线程必须也是通过synchroinized一样的monitor条件才能看到最新的变化,否则是不确定的。

(3)有序性:

都只保证部分有序性



反思:

通过上面的对比想告诉大家不要认为synchronized或者Lock是万能的,他们与volatile不是互斥的关系,其实很多场景下都需要volatile和synchronized的配合才能编写出正确的多线程代码。



### 总结

本篇文章主要介绍了在Java多线程编程里面同步概念的一些相关知识,并重点介绍了synchronized关键字的一些特点以及它的优缺点,在文末还介绍了其与volatile关键字的对比。正确的编写多线程程序不是一件容易的事情,我可以告诉你,你在你的电脑上跑了一千万次都验证结果是正确的多线程程序,真正的结果却不一定是正确的,不要怀疑,因为不同的硬件系统的CPU指令集是不一样的,你仅仅能证明在你的电脑上可能是没问题的。只有正确的理解和掌握JMM内存模型才能使得我们编写多线程程序更加安全和健壮。





有什么问题可以扫码关注微信公众号:我是攻城师(woshigcs)
路漫漫其修远兮,吾将上下而求索
























0
0
分享到:
评论

相关推荐

    Java多线程知识点总结

    了解线程池的概念也是Java多线程编程中的一个重要知识点。线程池是一种多线程处理形式,它将线程和任务的概念分离开来,能够有效管理线程资源,减少线程创建和销毁的开销,提高程序性能。Java中可以通过Executors类...

    Java多线程机制(讲述java里面与多线程有关的函数)

    Java多线程机制是Java编程中至关重要的一部分,它允许程序同时执行多个任务,提升应用程序的效率和响应性。以下是对各个知识点的详细说明: 9.1 Java中的线程: Java程序中的线程是在操作系统级别的线程基础上进行...

    java多线程同步问题

    多线程注意:wait()方法的调用要有判定条件常用 while () obj.wait(timeout, nanos); ... // Perform action appropriate to condition } synchronized会影响共享数据,但对其他语句的执行不会有规律了!

    java 多线程同步

    Java多线程同步是Java编程中关键的并发概念,它涉及到如何在多个线程访问共享资源时保持数据的一致性和完整性。`java.util.concurrent`包是Java提供的一个强大的并发工具库,它为开发者提供了多种线程安全的工具,...

    JAVAJAVA多线程教学演示系统论文

    《JAVA多线程教学演示系统》是一篇深入探讨JAVA多线程编程的论文,它针对教育领域中的教学需求,提供了一种生动、直观的演示方式,帮助学生更好地理解和掌握多线程技术。这篇论文的核心内容可能包括以下几个方面: ...

    基于Java多线程同步的安全性研究.pdf

    "基于Java多线程同步的安全性研究" 本文主要研究了基于Java多线程同步的安全性问题,讨论了Java多线程同步机制的实现方法和安全性问题的解决方法。文章首先介绍了Java多线程同步的必要性和重要性,然后讨论了Java多...

    基于Java多线程同步技术的简易模拟售票系统实现.pdf

    根据给定文件的信息,本篇文档是关于Java多线程同步技术在简易模拟售票系统中的应用研究。文档详细介绍了多线程的概念、如何在Java中创建线程、线程同步技术以及如何利用这些技术来解决共享资源访问时的数据一致性...

    java多线程经典案例

    本案例将深入探讨Java多线程中的关键知识点,包括线程同步、线程通信和线程阻塞。 线程同步是为了防止多个线程同时访问共享资源,导致数据不一致。Java提供了多种同步机制,如synchronized关键字、Lock接口...

    Java多线程同步机制的应用分析.pdf

    "Java多线程同步机制的应用分析" Java多线程同步机制的应用分析是指在Java语言中,如何使用同步机制来保护临界区,以避免多线程之间的冲突和错误。该机制通过管程机制和同步语法来保护临界区,使得多线程可以安全...

    JAVA 线程实现数据库的主从同步更新

    在Java编程环境中,...总之,使用Java线程实现数据库主从同步更新是一种常见且实用的技术手段,它涉及到多线程编程、数据库操作、事务管理等多个方面。理解和掌握这些知识点对于开发高可用性的分布式系统至关重要。

    java多线程代码案例(创建线程,主线程,线程优先级,线程组,线程同步,线程间的通信)

    本文将深入探讨Java多线程中的关键知识点,包括创建线程、主线程、线程优先级、线程组、线程同步以及线程间的通信。 1. **创建线程** 在Java中,可以通过两种方式创建线程:继承`Thread`类或实现`Runnable`接口。...

    java多线程的讲解和实战

    Java多线程是Java编程中的重要概念,尤其在如今的多核处理器环境下,理解并熟练掌握多线程技术对于提高程序性能和响应速度至关重要...通过对这些知识点的学习和实践,读者可以深入理解Java多线程的运用,提升编程技能。

    java多线程查询数据库

    综上所述,"java多线程查询数据库"是一个涉及多线程技术、线程池管理、并发控制、分页查询等多个方面的复杂问题。通过理解和掌握这些知识点,我们可以有效地提高数据库操作的效率和系统的响应速度。

    Java_多线程与并发编程总结.doc

    Java多线程与并发编程是Java开发中至关重要的一部分,它涉及到如何高效地利用CPU资源,以实现程序的并行执行。在操作系统层面,多任务和多进程是通过分配不同的内存空间来实现的,而线程则共享同一进程的内存,这...

    JAVA多线程编程技术PDF

    这份“JAVA多线程编程技术PDF”是学习和掌握这一领域的经典资料,涵盖了多线程的全部知识点。 首先,多线程的核心概念包括线程的创建与启动。在Java中,可以通过实现Runnable接口或继承Thread类来创建线程。创建后...

    java多线程设计模式_java_设计模式_多线程_多线程课题_

    以上就是Java多线程设计模式的一些关键知识点,理解并熟练运用这些模式,将有助于编写出高效、稳定、易于维护的多线程程序。通过阅读《java多线程设计模式》这本书,你可以深入理解这些概念,并学习到更多实际应用的...

    JAVA-多线程 所有文件

    这个“JAVA-多线程 所有文件”压缩包很可能包含了一系列关于Java多线程学习的源代码示例和相关文档。下面我们将深入探讨Java多线程的相关知识点。 1. **线程的概念**:线程是操作系统分配CPU时间的基本单位,一个...

    java+多线程+同步详解源代码学习

    Java多线程与同步是Java编程中的核心概念,它们在构建高效、响应迅速的应用程序时起着至关重要的作用。在大型系统开发中,多线程技术使得程序能够同时执行多个任务,提高系统的并发性,而同步机制则确保了在多线程...

    java+多线程+同步详解源码整理

    Java多线程允许程序同时执行多个独立的线程,从而提高计算机系统的资源利用率和程序的响应速度。Java提供了两种创建线程的方式:通过实现`Runnable`接口或继承`Thread`类。实现`Runnable`接口更为灵活,因为Java不...

    Java 模拟线程并发

    除了基本的线程创建,Java还提供了synchronized关键字用于线程同步,防止多个线程同时访问共享资源导致数据不一致。synchronized可以修饰方法或代码块,当一个线程正在执行synchronized代码时,其他线程必须等待。 ...

Global site tag (gtag.js) - Google Analytics