相信大多数同学在开始接触并发编程的时候,首先了解的就是synchronized关键字的修饰,被synchronized修饰的方法或代码块都可以解决多线程安全问题。在Java SE1.6版本之前,我们称之为重量级锁。因为它在获取共享锁的时候是对CPU的独占锁,以至于在当前线程释放锁之前,其他线程均处于阻塞状态。这样虽然保证了多线程安全的问题,却反而影响了多线程任务的执行效率。在Java SE1.6版本之后,针对Synchronized已经做了很多的优化来减少获得锁和释放锁所带来的性能消耗,其中最重要的就是偏向所和轻量级锁的引入,以及锁的存储结构和升级过程。但我们仍然认为synchronized是一个比较消耗性能的并发操作,对此我们会在后面其他并发锁实现中做比较得出结论。
当然首先我们还是得了解synchronized的应用,然后再理解它的实现原理。
synchronized应用
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现形式有三种:
1、对于普通同步方法,锁是当前实例对象;
2、对于静态同步方法,锁是当前类的Class对象;
3、对于同步方法块,锁是Synchronized括号里配置的对象。
怎么理解这三种形式,让我通过具体代码看下。
.普通同步方法
public class SynchronizedTest {
public synchronized void doSomething(){
try {
System.out.println(Thread.currentThread().getName() + " start");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SynchronizedTest s = new SynchronizedTest();
for(int i = 0; i<5; i++){
Thread t= new MyThread();
t.start();
}
}
}
class MyThread extends Thread{
SynchronizedTest s = new SynchronizedTest();
@Override
public void run(){
s.doSomething();
}
}
看一下运行结果:
Thread-1 start
Thread-0 start
Thread-2 start
Thread-3 start
Thread-4 start
Thread-1 end
Thread-0 end
Thread-2 end
Thread-3 end
Thread-4 end
多线程同步好像没有起作用,各自线程执行各自的,彼此没有影响,为什么?因为普通同步方法锁对象是当前方法的实例对象本身,即实例化对象Synchronized。我们发现每个多线程内部使用的实例化对象Synchronized都是线程自己创建的,并没有锁住同一个对象。
我们尝试修改上面这段代码:
public class SynchronizedTest {
public synchronized void doSomething(){
try {
System.out.println(Thread.currentThread().getName() + " start");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SynchronizedTest s = new SynchronizedTest();
for(int i = 0; i<5; i++){
Thread t= new MyThread(s);
t.start();
}
}
}
class MyThread extends Thread{
private SynchronizedTest s;
public MyThread(SynchronizedTest s){
this.s = s;
}
@Override
public void run(){
s.doSomething();
}
}
执行结果:
Thread-0 start
Thread-0 end
Thread-3 start
Thread-3 end
Thread-4 start
Thread-4 end
Thread-2 start
Thread-2 end
Thread-1 start
Thread-1 end
当我们对调用该同步方法的同一个实例对象传入线程内部的时候,即达到了多线程同步效果,因为此时我们锁住的是同一个实例对象。故普通同步方法有一个明显的缺点就是,锁住的对象为调用该方法的实例对象,太重量级,一般我们只要锁住方法内部需要同步的对象即可,所以我们更建议使用同步方法块。
.同步方法块
public class SynchronizedBlock {
public void doSomething(){
Student student = new Student();
student.setGrade(1);
for (int i = 0; i < 5; i++) {
new MyThread1(student).start();
}
}
public static void main(String[] args) {
SynchronizedBlock s = new SynchronizedBlock();
s.doSomething();
}
}
class MyThread1 extends Thread{
private Student student;
public MyThread1(Student student){
this.student = student;
}
@Override
public void run(){
synchronized (student) {
try {
Thread.sleep(100);
student.setGrade(student.getGrade()+1);
System.out.println(student.getGrade());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Student{
private Integer grade;
public Integer getGrade() {
return grade;
}
public void setGrade(Integer grade) {
this.grade = grade;
}
}
可以尝试去掉run方法内部的同步代码,你会发现得到的是一堆无逻辑的数字。同步方法块,即传入我们需要同步的对象即可,这个对象可以是你需要同步实例下的某个对象(操作变量),也可以是当前的实例对象(即this),也可以是当前类的实例对象(class对象),已符合我们实际应用中大部分需求。
.静态同步方法
Synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。注意这里的类对象不是指当前调用此方法的某个具体类对象,而是指当前的Class类,更深入一点理解是类加载器加载编译后字节码文件所存储的空间对象。
为了验证加锁对象的范围,我们可以在一个类中定义2个不同的静态同步方法。
public class SynchronizedStaticTest {
public synchronized static void test1(){
try {
for( int i = 0; i < 3 ; i++) {
System.out.println("test1 ->"+ i+ " start....");
Thread.sleep(100);
System.out.println("test1 ->"+ i+ " end....");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void test2(){
try {
for( int i = 0; i < 3 ; i++) {
System.out.println("test2 ->"+ i+ " start....");
Thread.sleep(100);
System.out.println("test2 ->"+ i+ " end....");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new MyThreadDemo1().start();
new MyThreadDemo2().start();
}
}
class MyThreadDemo1 extends Thread{
SynchronizedStaticTest t = new SynchronizedStaticTest();
@Override
public void run(){
t.test1();
}
}
class MyThreadDemo2 extends Thread{
SynchronizedStaticTest t = new SynchronizedStaticTest();
@Override
public void run(){
t.test2();
}
}
class StudentDemo{
private Integer grade;
public Integer getGrade() {
return grade;
}
public void setGrade(Integer grade) {
this.grade = grade;
}
}
执行结果:
test1 ->0 start....
test1 ->0 end....
test1 ->1 start....
test1 ->1 end....
test1 ->2 start....
test1 ->2 end....
test2 ->0 start....
test2 ->0 end....
test2 ->1 start....
test2 ->1 end....
test2 ->2 start....
test2 ->2 end....
我们可以看到虽然2个方法内调用的线程都创建了新的实例对象SynchronizedStaticTest,但是2个不同的方法共同结合成一个多线程同步过程。即:多个不同的静态同步方法,均对同一个CLASS类对象加了锁。