2018/7/13 21:10:42当前位置媒体热门新闻热点浏览文章

# 关于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关键字的功可以,这里有一个简单的例子: 

Java代码 

```  

//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的顺序不保证。 

另外一个例子: 

Java代码 

```  

//线程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内存模型才可以使得我们编写多线程程序更加安全和健壮。 

附:分享一个进阶java高级架构群,群内提供免费架构视频资料,还会有大牛解答疑问,欢迎进群学习交流;群号:779792048

网友评论