多线程访问同一个资源进行读写操作,就很容易出一些问题(比如我们常见的读者写者,生产者消费者模型)所以我们会选择对他们设置信号量或者加锁,来限制同一个时刻只有一个线程对某个对象进行操作。
多线程是一个蛮复杂的工作,锁加多了就算是看伪代码有的时候脑子都转不过来,所以不要随便加锁(如果对自己的脑子没太多自信的话
Synchronized
Synchronized关键字的作用是实现线程间的同步,它就像我们用PV原语来解决进程互斥问题,然后用对象的wait(),notify()提供线程的同步功能。
它的使用场景如下:
分类 | 具体方法 | 被锁的对象 | 伪代码 |
---|---|---|---|
方法 | 实例方法 | 实例对象 | public synchronized void method(){}; //实例方法,锁住的是实例对象 |
方法 | 静态方法 | 类对象 | public static synchronized void method(){}; //静态方法,锁住的是类对象 |
代码块 | 实例对象 | 类的实例对象 | synchronized(this){...};//同步代码块,锁住的是该类实例对象 |
代码块 | class对象 | 类的实例对象 | synchronized(SynchronizedDemo.class){...}; //同步代码块,锁住的是该类的类对象 |
代码块 | 任意实例对象Object | 类的实例对象Object | String lock=" "; synchronized(lock){...}; //同步代码块,锁住的是配置的实例对象 |
Attention:如果锁住的是类对象的话,不论new多少个实例出来,因为他们是属于一个类的,所以都会被锁住,保证进程间同步。
从OS的原理上理解,很明显的通过这种方式来依次排队操作共享资源的方式是比较效率低下的。这并不是一个一个非常好的同步机制,但是它是其他并发容器的基础。
Synchronized是怎么实现同步的呢
在学OS的时候,有一章一直在讲PV原语,Proberen申请资源信号量-1,Verhogen释放资源信号量+1(Dijkstra是荷兰人,所以就这么任性的用荷兰语),PV的题目都快做吐了,但是其实在真正的现代OS环境和Java当中,并不是这样简单的来解决问题的。
在Synchronized关键字的机制里面,每个对象拥有一个monitor(计数器),当线程获取该对象锁后,计数器就会加一,释放锁后就会将monitor减一。(为什么你和PV操作刚好相反啊!)
Disadvantage:
由于我们没办法设置synchronized关键字在获取锁的时候等待时间,所以synchronized可能会导致线程为了加锁而无限期地处于阻塞状态。
使用synchronized关键字等同于使用了互斥锁,即其他线程都无法获得锁对象的访问权。这种策略对于读多写少的应用而言是很不利的,因为即使多个读者看似可以并发运行,但他们实际上还是串行的,并将最终导致并发性能的下降。
因为时间片的轮转,导致我们在使用上觉得连贯,在一个时间周期里面看起来像是并发进行(针对单核process而言),但实际上,效率并不高。
Q: Synchronized关键字和lock是一样的吗?它们的区别是什么?
我觉得这两个是不同的概念,但是我的一些同学在开发过程中把Synchronize也叫做锁,一般说加一个对象锁。(虽然觉得不对但是也找不到什么反驳的理由.jpg)
看了一些博客和书之后发现,对象锁知识synchronized在实现锁机制中的一类,它其实有偏向锁,轻量锁,自旋锁等等,所以其实synchronized是一个锁的封装。
关于进程的五状态图这里就不画了,几个状态的切换。值得注意的是,线程在wait()的时候是会释放对象的锁的,而sleep()或者yield()是不会的。
练手的Demo(使用semaphore信号量解决读者写者问题)
https://github.com/JhinQaQ/ReadAndWrite
参考
《Operating Systems : Design and Implementation》
《Java并发编程的艺术》
《实战Java高并发程序设计》
让你彻底理解Synchronized https://www.jianshu.com/p/d53bf830fa09
synchronized的两大不足 http://swiftlet.net/archives/3010
synchronized、锁、多线程同步的原理是咋样的 https://www.jianshu.com/p/5dbb07c8d5d5