JAVA中的synchronized

内置锁

每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A退出同步代码块,线程B才能获取到同一个锁,由于同一时刻只能有一个线程进入同步代码块,故同步代码块中的操作可以保证原子性

对象锁和类锁

对象锁:用于对象实例方法或一个对象实例上
类锁:用于类的静态方法或一个类的class对象上
类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁之间互不干扰,但每个类只有一个类锁
有一点必须注意的是,类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

synchronized

synchronized可以作用于方法或代码块。线程执行synchronized修饰的代码时,流程为:
获取锁->执行synchronized修饰的代码->释放锁

synchronized方法

class Test {
    public synchronized void fun1() {
        System.out.println("fun1 begin");
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("fun1 is working");
        }
        System.out.println("fun1 end");
    }

    public synchronized void fun2() {
        System.out.println("fun2 begin");
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("fun2 is working");
        }
        System.out.println("fun2 end");
    }
}

public class Main {
    public static void main(String[] args) {
        final Test t = new Test();
        new Thread() {
            @Override
            public void run() {
                t.fun1();
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                t.fun2();
            }
        }.start();
    }
}

执行结果为fun1与fun2顺序执行:

fun1 begin
fun1 is working
fun1 is working
fun1 is working
fun1 end
fun2 begin
fun2 is working
fun2 is working
fun2 is working
fun2 end

删除两个方法的synchronized关键字后,执行结果为两个方法交替执行:

fun1 begin
fun2 begin
fun1 is working
fun2 is working
fun1 is working
fun2 is working
fun1 is working
fun1 end
fun2 is working
fun2 end

这是因为在执行synchronized修饰的方法前,线程需要先获取相应的对象锁(这里即为t的对象锁)。当第一个线程在执行t.fun1时会占有对象t的对象锁直到fun1执行结束,在此期间第二个线程由于无法获取t的对象锁,因此在t.fun1()执行完成,释放t的对象锁前无法执行被synchronized修饰的t.fun2()

易知在main函数中建立两个Test对象t1和t2时,在两个线程中分别执行t1.fun1()和t2.fun2()时,由于t1与t2为两个对象,因此两个线程中的分别执行t1.fun1()和t2.fun2()时需要获取的锁不同,因此两个函数会交替执行:

class Test {
    public synchronized void fun1() {
        System.out.println("fun1 begin");
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("fun1 is working");
        }
        System.out.println("fun1 end");
    }

    public synchronized void fun2() {
        System.out.println("fun2 begin");
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("fun2 is working");
        }
        System.out.println("fun2 end");
    }
}

public class Main {
    public static void main(String[] args) {
        final Test t1 = new Test();
        final Test t2 = new Test();
        new Thread() {
            @Override
            public void run() {
                t1.fun1();
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                t2.fun2();
            }
        }.start();
    }
}

执行结果:

fun1 begin
fun2 begin
fun1 is working
fun2 is working
fun2 is working
fun1 is working
fun1 is working
fun2 is working
fun2 end
fun1 end

但是需要注意的是,如果将Test中的fun1与fun2均改为static method,那么,这两个method就变成属于class的method了。此时再执行代码,两个线程需要的为Test.class的锁,因此会顺序执行。执行结果为:

fun1 begin
fun1 is working
fun1 is working
fun1 is working
fun1 end
fun2 begin
fun2 is working
fun2 is working
fun2 is working
fun2 end

synchronized代码块

在类Test中新建两个对象o1和o2,在fun1和fun2中分别使用synchronized(o1)和synchronized(o2)获取obj1和obj2的锁,由于两个对象的锁不同,因此使用两条线程分别调用t.fun1和t.fun2时,两个方法是交互运行的:

class Test {
    Object obj1 = new Object();
    Object obj2 = new Object();

    public void fun1() {
        synchronized (obj1) {
            System.out.println("fun1 begin");
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("fun1 is working");
            }
            System.out.println("fun1 end");
        }
    }

    public void fun2() {
        synchronized (obj2) {
            System.out.println("fun2 begin");
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("fun2 is working");
            }
            System.out.println("fun2 end");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        final Test t = new Test();
        new Thread() {
            @Override
            public void run() {
                t.fun1();
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                t.fun2();
            }
        }.start();
    }
}

运行结果:

fun1 begin
fun2 begin
fun2 is working
fun1 is working
fun2 is working
fun1 is working
fun1 is working
fun2 is working
fun2 end
fun1 end

删除代码中的obj1和obj2,替换为String str1 = “test”,String str2 = “test”,在fun1与fun2的synchronized中将获取锁的对象分别改为str1和str2,会发现两个方法仍然顺序执行:fun1->fun2,这是因为java中的字符串常量存于堆中,看起来str1和str2是两个不同的对象,实际上两者还是指向同一个字符串”test”的地址,因此需等待fun1执行完成后,第二个线程才能获取到字符串”test”的对象锁,进入synchronized代码段