多线程环境中,对于共享变量的访问一定需要进行正确的管理才能保证代码的正确执行,也就是保证线程安全。而加synchronized关键关键字无疑是个简单的办法,synchronized是java提供的一个关键字,给代码块或者方法加上这个关键字就可以保证一个线程开始执行被synchronized修饰的方法或代码块时,首先需要获得这个对象的内置锁,执行完被保护的代码之后再释放这个锁,获取锁和释放所都是Jvm来完成的,我们需要做的只是给需要保证线程安全的方法加一个关键词。但不同的情况,“这个对象”又有不同的指向。 为了说明每种情况,这里给出一个示例:
public class LockTest {
static int count;
public synchronized void InstanceAdd(){
count++;
}
public static synchronized void staticAdd(){
count++;}
public void blockAdd(){
synchronized (this){
count++;
}
}
public static void main(String[] args) {
LockTest lockTest1 = new LockTest();
LockTest lockTest2 = new LockTest();
Thread thread1= new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<1000;i++){
lockTest1.staticAdd();
// lockTest1.InstanceAdd();
// lockTest1.blockAdd();
}
}
});
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<1000;i++){
lockTest2.staticAdd();
// lockTest2.blockAdd();
// lockTest2.InstanceAdd();
}
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count="+count);
}
}
这个示例,有一个类变量count,然后三个方法实现同一个逻辑,就是count++,这三个方法都实现了同步,只不过有些不同,分别是类方法的同步,实例方法的同步,代码块的同步。main函数实例化了两个LockTest对象,启动了两个线程,每个线程通过调用这三种方法的其中之一进行1000次count++,如果线程安全的话,最终打印出的count应该是2000。
这是因为此时thread1获得的是lockTest1的内置锁,thread2获得了lockTest2的内置锁,这两个线程对count进行的是肆无忌惮的操作,没有达到同步的效果,所以当synchronized作用于代码块时,他锁住的就是synchronized()括号中的对象,执行这段代码需要获得的就是这个对象的内置锁。
除了这三种情况还有很重要的一点,就是我们题目所说,synchronized到底锁住了什么?是受保护代码还是对象?是对象。java中每个对象都有一个内置锁,而“锁住”是什么意思?就是获得了这个对象的内置锁。但一个线程获得了某个对象的内置锁之后,其他线程还能访问这个对象吗?是可以的。 我们给这个类新加一个方法
public void add(){
count++;
}
其中一个线程调用同步方法,另一个线程调用这个方法。并且我们两个线程使用同一个对象,lockTest1或lockTest2。我们会发现,程序正常执行,只是count没有加到2000。对与调用同步方法的线程来说,他获取了相应对象的内置锁,对与调用add这个新方法的线程来说,他并没有去获得任何锁,但前一个线程对对象锁的获取并不能影响后一个线程对对象的访问,于是后一个线程还是可以直接调用这个add方法。所以说,锁机制更像是一种约定,约定我们进行某个操作之前必须去获取某个锁,从而来完成多线程的安全操作。通过合理运用synchronized我们可以做到线程安全,但无疑的是,其中的代码对与多个线程来说只能是排队执行,大大降低了应用程序的性能,锁的获得和释放本身也是要消耗资源的,于是就有了对锁的优化,当然这就是后话了。