对synchronized的了解
synchronized
关键字解决的是多个线程之间访问资源的同步性,synchronized
关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
在 Java 早期版本中,synchronized
属于 重量级锁,效率低下。
为什么呢?
因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock
来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。
庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对 synchronized
较大优化,所以现在的 synchronized
锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
对synchronized的使用
它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
修饰代码块
class SyncThread implements Runnable{
private static int count;
@Override
public void run() {
synchronized (this){
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName()+"==="+count);
count++;
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
SyncThread syncThread = new SyncThread();
new Thread(syncThread,"Thread-01").start();
new Thread(syncThread,"Thread-02").start();
//结果:
Thread-01===0
Thread-01===1
Thread-01===2
Thread-01===3
Thread-01===4
Thread-02===5
Thread-02===6
Thread-02===7
Thread-02===8
Thread-02===9
当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
new Thread(new SyncThread(),"Thread-01").start();
new Thread(new SyncThread(),"Thread-02").start();
//这样执行的结果
hread-02===0
Thread-01===2
Thread-02===2
Thread-01===4
Thread-02===4
Thread-01===6
Thread-02===7
Thread-01===8
Thread-02===9
这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。
在synchronized(对象){}中也可以直接锁一个对象,不使用this。
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
修饰方法
方式一:
public synchronized void method()
{
// todo
}
方式二:
public void method()
{
synchronized(this) {
// todo
}
}
以上两者等价的,都是锁定了整个方法时的内容。
注意:
-
synchronized关键字不能继承。虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下
class Parent { public synchronized void method() { } } class Child extends Parent { public synchronized void method() { } }
class Parent { public synchronized void method() { } } class Child extends Parent { public void method() { super.method(); } }
-
在定义接口方法时不能使用synchronized关键字。
-
构造方法不能使用synchronized关键字