同步一般是指在多线程中,在某一时刻,一个同步块代码只能在一个线程中执行。
Java支持多线程执行,所以可能出现两个或者更多的线程访问同一个字段或者对象。同步是一个过程,保持所有并发线程同步执行。同步避免了因共享内存不一致的问题而引起的内存一致性错误。当一个方法声明为同步,并有一个线程持有这个方法对象的监视器,你的线程就会被阻塞,直到这个线程释放这个监视器。
同步在Java中使用synchronized 这个关键字来实现,你可以使用这个关键字在你定义类的方法或者代码块中,但是这个关键字不能使用在类定义的变量或者属性中。
对象级别锁
对象级别锁是一个机制,当你想同步一个非静态方法或者非静态代码块,让在给定的类实例中只有一个线程来执行这个代码块,这就可以使得实例级别的数据是线程安全的。具体做法如下:
public class DemoClass
{
public synchronized void demoMethod(){}
}
or
public class DemoClass
{
public void demoMethod(){
synchronized (this)
{
//other thread safe code
}
}
}
or
public class DemoClass
{
private final Object lock = new Object();
public void demoMethod(){
synchronized (lock)
{
//other thread safe code
}
}
}
类级别锁
在所有可变的实例或者运行环境中,类级别锁阻止多线程进入同步块,也就是说,如果运行环境中有DemoClass的100个实例,在任何时刻,只能有DemoClass的一个实例来执行它的demoMethod()方法,所有其他的DemoClass实例在其他线程中只能处于阻塞状态,这使得静态数据是线程安全的。
public class DemoClass
{
public synchronized static void demoMethod(){}
}
or
public class DemoClass
{
public void demoMethod(){
synchronized (DemoClass.class)
{
//other thread safe code
}
}
}
or
public class DemoClass
{
private final static Object lock = new Object();
public void demoMethod(){
synchronized (lock)
{
//other thread safe code
}
}
}
一些重要点
1、在Java中,同步保证两个线程不能同时执行同一个同步方法需要有相同的同步或者并发锁。
2、synchronized关键字只能用在方法或者代码块中,这些方法或者代码块可以使静态的也可以使非静态的。
3、当一个线程进入到java的synchronized 方法或者块中的时候,它会获取一个锁,并且无论什么时候它离开这个javasynchronized方法或者块的时候,它会释放这个锁。当执行完成离开synchronized块或者出现任何错误以及异常的时候都会释放锁。
4、java的synchronized 关键字本质上是可重入的,这意味着如果java同步方法调用到了另一个同步方法,并且这个方法需要同样的锁,那么持有锁的这个方法是可以进入另一个需要同一个锁的方法的。
例如:
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
5、如果用在同步块中的对象是空,那么java同步将会抛出NullPointerException。
例如:下面就会抛出空指针异常
public class DemoClass
{
private final static Object lock = null;
public void demoMethod(){
synchronized (lock)
{
}
}
}
6、在Java中,方法的同步会影响应用的性能,所以在完全需要的时候才使用这个同步,另外,可以考虑使用同步代码块来同步你需要同步的关键代码。
7、静态同步方法和非静态同步方法是可能同时运行的,因为它们使用的是不同的对象锁。
8、根据java的语言规范,不能在构造函数中使用java的synchronized关键字,它是非法的并会导致编译错误。
9、在java中,不能在同步块中同步非final字段,因为,非final字段的引用可能在任何时候发生改变,这样如果不同的线程同步不用的对象就相当于没有同步。最好使用String类,它已经声明为终态并且是不可变的。
原文:Thread synchronization, object level locking and class level locking