synchronized关键字是利用锁的机制来实现同步的。锁机制具有互斥性(操作的原子性)和可见性。
synchronized可以修饰静态/非静态方法、代码块,可以对类或者对象加锁。

先来做个最简单的,用没用synchronized的对比:

import java.util.concurrent.TimeUnit;
public class SynchroDemo {
    private static int i = 0;
    private static int j = 0;

    public synchronized void accessResources1() {
        i++;
        System.out.println("accessResources1,i=" + i + "," + Thread.currentThread().getName() + " is runing");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void accessResources2() {
        j++;
        System.out.println("accessResources2,j=" + j + "," + Thread.currentThread().getName() + " is runing");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final SynchroDemo deno = new SynchroDemo();
        for (int i = 0; i < 5; i++) {
            Runnable runnable = new Runnable() {
                public void run() {
                    deno.accessResources1();
                    deno.accessResources2();
                }
            };
            Thread thread = new Thread(runnable);
            thread.start();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

上面代码用JDK1.8运行的结果:

Android synchronized 修饰类 synchronized可修饰_多线程


可以看出,用JDK1.8运行,进程拿到锁并非先到先得。(如果你电脑运行出来是五个线程特别乖巧逐个调用accessResources1的,那应该是你的电脑比我的更棒棒啰~可以把延时缩短一点试试)换个jdk1.5试试:

Android synchronized 修饰类 synchronized可修饰_System_02


在JDK1.6前,Synchronized属于重量级锁,在JDK1.6,Synchronized得到了很多性能上的改进,引入了自旋锁、偏向锁、轻量级锁等技术来减少锁操作的开销,Synchronized锁也被优化成一个升级的过程。关于各种锁的特性,这里挖个坑,往后再整理记录。

与accessResources2相比,accessResources1方法有Synchronized的修饰,各个线程对变量i的修改有序递增,整个accessResources1方法满足了原子性、可见性和顺序性。注意,是整个accessResources1方法,这也是Synchronized的一个缺点:粒度太大,而且在压力太大的时候,若synchronized升级到了重量级锁,相当于是从并发退化到串行,会引起阻塞。

synchronized具有重入性,同一线程可以对同一对象进行多次加锁,不过要注意一个对象加锁后再对另一个对象加锁,不能交叉加锁,否则会造成死锁。

  • 对象锁:
public synchronized void accessResources1() {
        //锁定代码块的时候,所修饰的、要锁定的方法不能是静态方法
        synchronized (this) {//this指的是当前对象
            i++;
            System.out.println("accessResources1,i=" + i + "," + Thread.currentThread().getName() + " is runing");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
  • 类锁:
    当类加载器把class文件加载到方法区的时候,向堆区生产了一个Class对象。由它产生的所有对象都对应了唯一的Class对象
public synchronized void accessResources1() {
        //锁定代码块的时候,所修饰的、要锁定的方法不能是静态方法
        synchronized (SynchroDemo.class) {
            i++;
            System.out.println("accessResources1,i=" + i + "," + Thread.currentThread().getName() + " is runing");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

想验证一下synchronized的互斥性,可以把延时时间拉长到一分钟,启用 Jconsole,通过线程堆栈分析:

Android synchronized 修饰类 synchronized可修饰_多线程_03