synchronized关键字简介
JAVA中synchronized关键字是用来增加和释放排他锁的,这把锁可以是Java中的一些对象,比如this,变量以及Class对象等等。只有获取到了这把锁才可以去修改多线程/进程中的共享变量。举例来说,
1 | synchronized(lock) { |
其中lock是锁对象,n是多任务的共享变量。锁和共享对象也可以是同一个东东,应该是没问题的。
1 | synchronized(lock) { |
需要注意的,synchronized关键字不仅仅是修饰代码块,最主要有以下3种应用方式:
(1)修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁,相当于synchronized(this),锁是实例本身。
(2)修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁,相当于synchronized(XXX.class),锁是该类的类对象。
(3)修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。synchronized(param)
中param会指定加锁的对象,可以是this,也可以是一个ArrayList对象等,锁可以是自定义的任何obj。
我们来概括一下如何使用synchronized:
(1)找出修改共享变量的线程代码块
(2)选择一个实例作为锁
(3)使用`synchronized(lockObject) { … }`
synchronized和wait/notify/notifyAll配合使用
在多任务/线程操作共享对象时,有两个问题需要处理。一个是共享对象的操作安全问题,即共享对象加锁的机制的;另一个是多任务/线程之间地协调问题,即线程/进程间通信问题(比如生产者生产了内容,需要通知消费者获取内容消费)。synchronized关键字是解决第一个问题的,第二个问题需要wait/notify/notifyAll来解决。
(1)wait/notify/notifyAll是基类Object类中的方法,也就是Java中所有对象都有这几个方法
(2)wait/notify/notifyAll必须用在synchronized修饰的代码块或方法中,否则JVM会被java.lang.IllegalMonitorStateException:current thread not owner错误。且需要调用synchronized的锁对象的wait/notify/notifyAll方法。比如synchronized(this){},那就是this.wait/notify/notifyAll;比如synchronized(obj){},那就是obj.wait/notify/notifyAll方法
(3)调用obj.wait首先会把当前线程挂起,且释放synchronized的锁对象,其他线程可以抢占锁对象了,然后等待obj.notify/notifyAll来唤醒,唤醒之后重新抢占锁对象,获取到锁对象后,接着之前的挂起的语句继续执行
(4)obj.notify唤醒一个被obj.wait挂起的线程,并释放锁对象,当前线程不会挂起
(5)obj.notifyAll唤醒所有被obj.wait挂起的线程,并释放锁对象,当前线程不会挂起
代码举例如下:
1 | public class Runoob { |
JAVA中的线程安全
如果一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的(thread-safe)。
1 | public class Counter { |
上面的Counter类就是线程安全的。Java标准库的java.lang.StringBuffer
也是线程安全的。还有一些不变类,例如String,Integer,LocalDate,它们的所有成员变量都是final,多线程同时访问时只能读不能写,这些不变类也是线程安全的。最后,类似Math这些只提供静态方法,没有成员变量的类,也是线程安全的。除了上述几种少数情况,大部分类,例如ArrayList,都是非线程安全的类,我们不能在多线程中修改它们。但是,如果所有线程都只读取,不写入,那么ArrayList是可以安全地在线程间共享的。
一般来说,没有特殊说明时,一个类默认是非线程安全的。