请戳GitHub原文: github.com/wangzhiwubi…java
请戳GitHub原文: github.com/wangzhiwubi…git
Java高级特性加强-多线程github
全网惟一一个从0开始帮助Java开发者转作大数据领域的公众号~
公众号大数据技术与架构或者搜索import_bigdata关注,大数据学习路线最新更新,已经有不少小伙伴加入了~
本部分网络上有大量的资源能够参考,在这里作了部分整理,感谢前辈的付出,每节文章末尾有引用列表,源码推荐看JDK1.8之后的版本,注意甄别~ ####多线程 ###集合框架 ###NIO ###Java并发容器
参考文章目录: 感谢各位大大的劳动成果~深表敬意~ blog.csdn.net/qq_34337272… blog.csdn.net/qq_34337272… www.jianshu.com/p/d53bf830f… www.jianshu.com/p/c5058b6fe…
Java并发编程这个领域中synchronized关键字一直都是元老级的角色,好久以前不少人都会称它为“重量级锁”。可是,在JavaSE 1.6以后进行了主要包括为了减小得到锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各类优化以后变得在某些状况下并非那么重了。
“非线程安全”问题存在于“实例变量”中,若是是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。
若是两个线程同时操做对象中的实例变量,则会出现“非线程安全”,解决办法就是在方法前加上synchronized关键字便可。
修饰代码块
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
SyncThread的调用:
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
结果以下:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
复制代码
当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程获得执行,另外一个线程受阻塞,必须等待当前线程执行完这个代码块之后才能执行该代码块。Thread1和thread2是互斥的,由于在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。 咱们再把SyncThread的调用稍微改一下:
Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();
复制代码
结果以下:
SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9
复制代码
不是说一个线程执行synchronized代码块时其它的线程受阻塞吗?为何上面的例子中thread1和thread2同时在执行。这是由于synchronized只锁定对象,每一个对象只有一个锁(lock)与之相关联,而上面的代码等同于下面这段代码:
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
复制代码
这时建立了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);咱们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不造成互斥,因此两个线程能够同时执行。
修饰一个方法 Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块相似,只是做用范围不同,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
复制代码
修饰一个静态的方法 Synchronized也可修饰一个静态方法,用法以下:
public synchronized static void method() {
// todo
}
复制代码
咱们知道静态方法是属于类的而不属于对象的。一样的,synchronized修饰的静态方法锁定的是这个类的全部对象.
修饰一个类 Synchronized还可做用于一个类,用法以下:
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
复制代码
总结:
如今咱们来看看synchronized的具体底层实现。先写一个简单的demo:
public class SynchronizedDemo {
public static void main(String[] args) {
synchronized (SynchronizedDemo.class) {
}
method();
}
private static void method() {
}
}
复制代码
上面的代码中有一个同步代码块,锁住的是类对象,而且还有一个同步静态方法,锁住的依然是该类的类对象。编译以后,切换到SynchronizedDemo.class的同级目录以后,而后用javap -v SynchronizedDemo.class查看字节码文件:
概念 happens-before的概念最初由Leslie Lamport在其一篇影响深远的论文(《Time,Clocks and the Ordering of Events in a Distributed System》)中提出,有兴趣的能够google一下。JSR-133使用happens-before的概念来指定两个操做之间的执行顺序。因为这两个操做能够在一个线程以内,也能够是在不一样线程之间。 所以,JMM能够经过happens-before关系向程序员提供跨线程的内存可见性保证(若是A线程的写操做a与B线程的读操做b之间存在happens-before关系,尽管a操做和b操做在不一样的线程中执行,但JMM向程序员保证a操做将对b操做可见)。具体的定义为: 1)若是一个操做happens-before另外一个操做,那么第一个操做的执行结果将对第二个操做可见,并且第一个操做的执行顺序排在第二个操做以前。 2)两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必需要按照happens-before关系指定的顺序来执行。若是重排序以后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM容许这种重排序)。 上面的1)是JMM对程序员的承诺。从程序员的角度来讲,能够这样理解happens-before关系:若是A happens-before B,那么Java内存模型将向程序员保证——A操做的结果将对B可见,且A的执行顺序排在B以前。注意,这只是Java内存模型向程序员作出的保证! 上面的2)是JMM对编译器和处理器重排序的约束原则。正如前面所言,JMM实际上是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。JMM这么作的缘由是:程序员对于这两个操做是否真的被重排序并不关心,程序员关心的是程序执行时的语义不能被改变(即执行结果不能被改变)。所以,happens-before关系本质上和as-if-serial语义是一回事。
具体规则
具体规则以下:
Synchronized的happens-before规则,即监视器锁规则:对同一个监视器的解锁,happens-before于对该监视器的加锁。继续来看代码:
public class MonitorDemo {
private int a = 0;
public synchronized void writer() { // 1
a++; // 2
} // 3
public synchronized void reader() { // 4
int i = a; // 5
} // 6
}
复制代码
该代码的happens-before关系如图所示:
经过上面的讨论如今咱们对Synchronized应该有所印象了,它最大的特征就是在同一时刻只有一个线程可以得到对象的监视器(monitor),从而进入到同步代码块或者同步方法之中,即表现为互斥性(排它性)。这种方式确定效率低下,每次只能经过一个线程,既然每次只能经过一个,这种形式不能改变的话,那么咱们能不能让每次经过的速度变快一点了。打个比方,去收银台付款,以前的方式是,你们都去排队,而后去纸币付款收银员找零,有的时候付款的时候在包里拿出钱包再去拿出钱,这个过程是比较耗时的,而后,支付宝解放了你们去钱包找钱的过程,如今只须要扫描下就能够完成付款了,也省去了收银员跟你找零的时间的了。一样是须要排队,但整个付款的时间大大缩短,是否是总体的效率变高速率变快了?这种优化方式一样能够引伸到锁优化上,缩短获取锁的时间。
这里作一个介绍,CAS为后续锁的章节作一个铺垫O(∩_∩)O~
推荐文章:www.jianshu.com/p/24ffe531e… 什么是CAS? 使用锁时,线程获取锁是一种悲观锁策略,即假设每一次执行临界区代码都会产生冲突,因此当前线程获取到锁的时候同时也会阻塞其余线程获取该锁。而CAS操做(又称为无锁操做)是一种乐观锁策略,它假设全部线程访问共享资源的时候不会出现冲突,既然不会出现冲突天然而然就不会阻塞其余线程的操做。所以,线程就不会出现阻塞停顿的状态。那么,若是出现冲突了怎么办?无锁操做是使用CAS(compare and swap)又叫作比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操做直到没有冲突为止。
CAS的操做过程 CAS比较交换的过程能够通俗的理解为CAS(V,O,N),包含三个值分别为:V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。当V和O相同时,也就是说旧值和内存中实际的值相同代表该值没有被其余线程更改过,即该旧值O就是目前来讲最新的值了,天然而然能够将新值N赋值给V。反之,V和O不相同,代表该值已经被其余线程改过了则该旧值O不是最新版本的值了,因此不能将新值N赋给V,返回V便可。当多个线程使用CAS操做一个变量是,只有一个线程会成功,并成功更新,其他会失败。失败的线程会从新尝试,固然也能够选择挂起线程 CAS的实现须要硬件指令集的支撑,在JDK1.5后虚拟机才可使用处理器提供的CMPXCHG指令实现。 CAS的应用场景 在J.U.C包中利用CAS实现类有不少,能够说是支撑起整个concurrency包的实现,在Lock实现中会有CAS改变state变量,在atomic包中的实现类也几乎都是用CAS实现,关于这些具体的实现场景在以后会详细聊聊,如今有个印象就行了(微笑脸)。 CAS的问题
关注公众号,内推,面试,资源下载,关注更多大数据技术~
预计更新500+篇文章,已经更新50+篇~
复制代码