java多线程4:synchronized关键字
java多线程4:synchronized关键字
概述
java有各种各样的锁,并且每种锁的特性不同,合理场景下利用锁可以展现出非常高的效率。synchronized内置锁就是Java的一种重量级锁,它能够解决并发编程中出现多个线程同时访问一个共享,可变的临界资源时出现的线程安全问题。让多个线程序列化访问临界资源,同一时刻,只能有一个线程访问临界资源,同步互斥,这样就保证了操作的原子性。
synchronized使用
同步方法块
public class ThreadDemo5 implements Runnable{
private int count = 0; @Override public void run() { synchronized (this){ for (int i = 0; i < 10; ++i){ count++; System.out.println("执行的线程是=>" + Thread.currentThread().getName() + "执行结果为->" + count); } } } public static void main(String[] args) { ThreadDemo5 threadDemo5 = new ThreadDemo5(); Thread thread1 = new Thread(threadDemo5,"thread1"); Thread thread2 = new Thread(threadDemo5,"thread2"); thread1.start(); thread2.start(); }
}
执行结果
执行的线程是=>thread1执行结果为->1
执行的线程是=>thread1执行结果为->2
执行的线程是=>thread1执行结果为->3
执行的线程是=>thread1执行结果为->4
执行的线程是=>thread1执行结果为->5
执行的线程是=>thread1执行结果为->6
执行的线程是=>thread1执行结果为->7
执行的线程是=>thread1执行结果为->8
执行的线程是=>thread1执行结果为->9
执行的线程是=>thread1执行结果为->10
执行的线程是=>thread2执行结果为->11
执行的线程是=>thread2执行结果为->12
执行的线程是=>thread2执行结果为->13
执行的线程是=>thread2执行结果为->14
执行的线程是=>thread2执行结果为->15
执行的线程是=>thread2执行结果为->16
执行的线程是=>thread2执行结果为->17
执行的线程是=>thread2执行结果为->18
执行的线程是=>thread2执行结果为->19
执行的线程是=>thread2执行结果为->20
同步方法块,synchronized锁的是括号里的对象,每个线程要进入代码块前必须先获取对象的的锁,才可执行。synchronized是一个隐式锁,也是jvm内置的锁,它会自动加锁和解锁,同时java的每个对象都可以作为锁。
普通同步方法
public class ThreadDemo6 implements Runnable {
private int count = 0; @Override public void run() { say(); } private synchronized void say(){ for (int i = 0; i < 10; ++i){ count++; System.out.println("现在执行的线程执行=>" + Thread.currentThread().getName() + "结果为->" + count); } try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ThreadDemo6 threadDemo6 = new ThreadDemo6(); Thread thread1 = new Thread(threadDemo6,"Thread-1"); Thread thread2 = new Thread(threadDemo6,"Thread-2"); thread1.start(); thread2.start(); }
}
执行结果
现在执行的线程执行=>Thread-1结果为->1
现在执行的线程执行=>Thread-1结果为->2
现在执行的线程执行=>Thread-1结果为->3
现在执行的线程执行=>Thread-1结果为->4
现在执行的线程执行=>Thread-1结果为->5
现在执行的线程执行=>Thread-1结果为->6
现在执行的线程执行=>Thread-1结果为->7
现在执行的线程执行=>Thread-1结果为->8
现在执行的线程执行=>Thread-1结果为->9
现在执行的线程执行=>Thread-1结果为->10
/停顿5秒/
现在执行的线程执行=>Thread-2结果为->11
现在执行的线程执行=>Thread-2结果为->12
现在执行的线程执行=>Thread-2结果为->13
现在执行的线程执行=>Thread-2结果为->14
现在执行的线程执行=>Thread-2结果为->15
现在执行的线程执行=>Thread-2结果为->16
现在执行的线程执行=>Thread-2结果为->17
现在执行的线程执行=>Thread-2结果为->18
现在执行的线程执行=>Thread-2结果为->19
现在执行的线程执行=>Thread-2结果为->20
普通同步方法,通过例子可以知道他是一个对象锁,线程1未释放锁,线程2只能被动等待,改下代码
public static void main(String[] args) {
Thread thread1 = new Thread(new ThreadDemo6(),"Thread-1"); Thread thread2 = new Thread(new ThreadDemo6(),"Thread-2"); thread1.start(); thread2.start(); }
执行结果
现在执行的线程执行=>Thread-2结果为->1
现在执行的线程执行=>Thread-2结果为->2
现在执行的线程执行=>Thread-2结果为->3
现在执行的线程执行=>Thread-2结果为->4
现在执行的线程执行=>Thread-2结果为->5
现在执行的线程执行=>Thread-2结果为->6
现在执行的线程执行=>Thread-2结果为->7
现在执行的线程执行=>Thread-2结果为->8
现在执行的线程执行=>Thread-2结果为->9
现在执行的线程执行=>Thread-2结果为->10
现在执行的线程执行=>Thread-1结果为->1
现在执行的线程执行=>Thread-1结果为->2
现在执行的线程执行=>Thread-1结果为->3
现在执行的线程执行=>Thread-1结果为->4
现在执行的线程执行=>Thread-1结果为->5
现在执行的线程执行=>Thread-1结果为->6
现在执行的线程执行=>Thread-1结果为->7
现在执行的线程执行=>Thread-1结果为->8
现在执行的线程执行=>Thread-1结果为->9
现在执行的线程执行=>Thread-1结果为->10
停顿。。
不是同一个对象锁,所以线程1和线程2不存在锁的互斥,并且不存在共享资源count变量,所以多个线程访问的必须是同一个对象,锁才会变得有意义。
静态同步方法
public class ThreadDemo6 implements Runnable {
private static int count = 0; @Override public void run() { say(); } private static synchronized void say(){ for (int i = 0; i < 10; ++i){ count++; System.out.println("现在执行的线程执行=>" + Thread.currentThread().getName() + "结果为->" + count); } try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } }
public static void main(String[] args) { Thread thread1 = new Thread(new ThreadDemo6(),"Thread-1"); Thread thread2 = new Thread(new ThreadDemo6(),"Thread-2"); thread1.start(); thread2.start(); }
}
执行结果
现在执行的线程执行=>Thread-1结果为->1
现在执行的线程执行=>Thread-1结果为->2
现在执行的线程执行=>Thread-1结果为->3
现在执行的线程执行=>Thread-1结果为->4
现在执行的线程执行=>Thread-1结果为->5
现在执行的线程执行=>Thread-1结果为->6
现在执行的线程执行=>Thread-1结果为->7
现在执行的线程执行=>Thread-1结果为->8
现在执行的线程执行=>Thread-1结果为->9
现在执行的线程执行=>Thread-1结果为->10
/停顿/
现在执行的线程执行=>Thread-2结果为->11
现在执行的线程执行=>Thread-2结果为->12
现在执行的线程执行=>Thread-2结果为->13
现在执行的线程执行=>Thread-2结果为->14
现在执行的线程执行=>Thread-2结果为->15
现在执行的线程执行=>Thread-2结果为->16
现在执行的线程执行=>Thread-2结果为->17
现在执行的线程执行=>Thread-2结果为->18
现在执行的线程执行=>Thread-2结果为->19
现在执行的线程执行=>Thread-2结果为->20
即使他们是不同的对象,但执行的都是一个类的方法,在执行同步静态方法时,争抢的是类锁,这也是和非静态同步方法所区别开来。因为他们是两个不同的锁,一个是对象锁,一个是类锁。所以,在代码中,一个线程可以同时抢有对象锁,类锁。
monitor和monitorexit
Java的互斥锁是如何的实现的,javap -verbose ThreadDemo3.class 看下字节码子令。
同步代码块
非静态方法同步
静态方法同步
同步块中monitor被占用就处于锁定状态,其他本次抢锁失败的线程将会放入Wait Set等待同步队列中进行等待,占用锁的线程执行完同步块并且释放锁后将会通知放入同步队列的的其他线程,通知他们,我释放锁了赶紧来抢吧!而相对于普通的静态同步方法和非静态同步方法,常量池汇中多了ACC_SYNCHRONIZED标记,方法调用就会去检查是不是有这个标记如果有,jvm就会要求线程在调用前先请求锁,但无论哪种实现,在实质上还是通过对象相关联的的monitor获取的。
而monitor是什么哪?它是每个对象创建之后都会在jvm内部维护一个与之对应Monitor(监视器锁)也有人叫管程反正都是一个东西,可以理解为每个对象天生都有一把看不见的锁我们叫他monitor锁,而每个线程会有一个可用的MR(Monitor Record)列表,还有一个全局可用列表,每一个被锁住的对象都会和一个MR相关联,并且对象monitor中会有一个owner字段存放占用该锁线程唯一标识,表示这个锁已经被哪个线程占用,synchronized就是基于进入与退出Monitor对象实现方法与代码块同步,而监视器锁的实现哪是依赖底层操作系统Mutex lock(互斥锁),它是一个重量级锁,每次从用户态切换到内的态的资源消耗是比较大的,也因此从jdk1.6后,java对synchronized进行了优化,从一开始的无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态,并且这个状态是不可逆的。
jvm加锁过程
对象内存结构
上文说过每个Java对象都是天生的锁,存放在Java的对象头中,对象头包含三个区域,对象头,实例数据,补齐填充
第一部分是存储对象自身运行时的数据,哈希码,GC,偏向时间戳,保存对象的分代年龄,锁状态标志,偏向锁线程id,线程持有的锁,如果是数组还需要一块区域存放数组大小,class的对象指针是虚拟机通过他确定这个对象是哪个类的实例,我们平时getClass直接获取类就跟这个有关,官方称这部分为Mark Word,第二部分略过,第三部分规定对象的大小必须是8字节的整数倍,至于为什么,lz没去深究暂时不知道。我们重点关注是Mark Word的锁标志位,所以锁的状态是保存在对象头中的,至于偏向状态,篇幅有限,下节在谈。
锁的粗化和消除
锁的粗化
锁带来性能开销是很大的,为了保证多线程的并发操作,通常会要求每个线程持有锁的时间越短越好,但如果遇到一连串对同一把锁进行请求和释放的操作,jvm会进行优化智能的把锁操作的整合成一个较大同步块,从而减少了对锁的频繁申请和释放提高性能。
public class ThreadDemo7 implements Runnable {
public void test(){ synchronized (this){ System.out.println(1111); } synchronized (this){ System.out.println(222); } synchronized (this){ System.out.println(333); } } public static void main(String[] args) { ThreadDemo7 threadDemo7 = new ThreadDemo7(); Thread thread = new Thread(threadDemo7); thread.start(); } @Override public void run() { test(); }
}
锁的消除
我们设置了同步块,在字节码中也发现了monitorenter和monitorexit,至少看上去有锁的获取和释放过程,但执行的结果与我们预测的风马牛不相及。
public class ThreadDemo8 implements Runnable {
private static int count = 0; @Override public void run() { synchronized (new Object()){ count++; System.out.println("锁的消除...=>" + Thread.currentThread().getName() + "值=>" + count); } } public static void main(String[] args) { ThreadDemo8 threadDemo8 = new ThreadDemo8(); for (int i = 0; i < 10; ++i){ Thread thread = new Thread(threadDemo8); thread.start(); } }
}
执行结果
锁的消除...=>Thread-6值=>4
锁的消除...=>Thread-4值=>2
锁的消除...=>Thread-5值=>4
锁的消除...=>Thread-0值=>4
锁的消除...=>Thread-1值=>6
锁的消除...=>Thread-2值=>6
锁的消除...=>Thread-3值=>7
锁的消除...=>Thread-9值=>8
锁的消除...=>Thread-7值=>9
锁的消除...=>Thread-8值=>10
这是因为jit在编译代码时,使用了逃逸分析的技术,判断程序中的使用锁的对象是否被其他线程使用,如果只被一个线程使用,这个同步代码就不会生成synchronized锁标识的锁申请和释放的机器码,消除了锁的使用流程。所以,并不是所有的实例对象都存放在堆区,如果发生线程逃逸行为,将会存储在线程栈上。
总结
锁的重入和锁膨胀升级,在后期在慢慢整理。
参考
https://blog.csdn.net/axiaoboge/article/details/84335452
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
2 个案例带你迅速入门 Python Flask 框架
2 个案例带你迅速入门 Python Flask 框架 Flask 是 python 中非常流行的一个 web 框架,容易学习。这篇文章主要通过 2 个实际案例讲解 Flask 如何使用。第一个例子是实现一个调用公交车到站信息的接口服务;第二个例子是通过接口展示所有的测试报告。 Flask 安装安装 pythonpip install flaskflask 安装 Flask 启动服务fromflaskimportFlask app=Flask(__name__) app.run()通过 3 行代码,可以启动一个 flask 的服务。 第一行,导入第二行,创建 Flask 程序实例第三行,通过实例启动服务运行这个代码,可以在本地的 5000 端口访问, 得到结果是 404 页面无法找到: flask running.gif 为 Flask 程序添加接口此时,访问 http://localhost:5000 看到 404 错误,是因为没有定义接口。 一个 url 地址和一个处理函数对应,就可以形成一个接口。 通过下面的代码,可以在之前的代码基础上添加一个接口: @app.route('/'...
- 下一篇
透彻理解C++11新特性:右值引用、std::move、std::forward
透彻理解C++11新特性:右值引用、std::move、std::forward 目录浅拷贝、深拷贝左值、右值右值引用类型强转右值 std::move重新审视右值引用右值引用类型和右值的关系函数参数传递函数返还值传递万能引用引用折叠完美转发 std::forwardC++11出现的右值相关语法可谓是很多C++程序员难以理解的新特性,不少人知其然而不知其所以然,面试被问到时大概就只知道可以减少开销,但是为什么减少开销、减少了多少开销、什么时候用...这些问题也不一定知道,于是我写下了这篇夹带自己理解的博文,希望它对你有所帮助。 浅拷贝、深拷贝在介绍右值引用等概念之前,可以先来认识下浅拷贝(shallow copy)和深拷贝(deep copy)。 这里举个例子: class Vector{ int num; int* a; public: void ShallowCopy(Vector& v); void DeepCopy(Vector& v); };浅拷贝:按位拷贝对象,创建的新对象有着原始对象属性值的一份精确拷贝(但不包括指针指向的内存)。//浅拷贝void Vect...
相关文章
文章评论
共有0条评论来说两句吧...