多线程之synchronized关键字详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Trigl/article/details/51164466

synchronized关键字用于多线程访问程序中的共享资源时实现顺序同步访问资源。可以修饰方法或者代码块。而且关键字synchronized取得的锁都是对象锁,什么叫对象锁呢,就是一个对象产生一把锁,如果多个线程调用一个对象的多个方法,这些方法都被synchronized修饰,那么这些线程共同竞争一把锁,最后表现的就是同步顺序执行各个被synchronized修饰的方法。下面深入讲一下synchronized关键字的常见用法。

1 同步方法

使用synchronized修饰的方法是同步方法,多个线程调用同一个对象的同步方法时会顺序同步执行。

1.1 synchronized锁重入

当一个线程执行synchronized关键字修饰的方法的时候,其他线程是不可以访问该对象中synchronized关键字修饰的方法的,因为唯一的一把锁已经被当前线程使用了。但是如果当前线程在synchronized方法/块的内部调用本类或者其父类的其他synchronized方法/块,是永远可以得到锁的,这就叫做锁重入。
如下是一个父子继承关系的锁重入例子:

父类

package com.trigl.concurrent.synchro;

public class Father {
    public int i = 10;
    synchronized public void fatherMethod() {
        i--;
        System.out.println("father i=" + i);
    }
}

子类

package com.trigl.concurrent.synchro;

public class Son extends Father {
    synchronized public void sonMethod() {
        while (i > 0) {
            i--;
            System.out.println("son i=" + i);
            this.fatherMethod();
        }
    }
}

自定义线程

package com.trigl.concurrent.synchro;

public class SynThread extends Thread {
    @Override
    public void run() {
        Son son = new Son();
        son.sonMethod();
    }
}

测试方法

    @Test
    public void testSynThread() {
        SynThread thread = new SynThread();
        thread.start();
    }

输出结果

son i=9
father i=8
son i=7
father i=6
son i=5
father i=4
son i=3
father i=2
son i=1
father i=0

可以看到顺序输出9到0,说明程序是同步进行的。

1.2 出现异常,同步锁自动释放

当线程执行synchronized修饰的代码出现异常时,其所持有的锁会自动释放,从而其他线程可以再次争夺锁的使用权而非一直等待造成死锁。
上栗子:

synchronized修饰的代码类

package com.trigl.concurrent.synchro;

public class Service {
    synchronized public void testMethod() {
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + " run beginTime=" + System.currentTimeMillis());
            while (true) {
                if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
                    System.out.println("ThreadName="
                            + Thread.currentThread().getName()
                            + " run exceptionTime="
                            + System.currentTimeMillis());
                    Integer.parseInt("a"); // 如果参数不是数字格式抛异常
                }
            }
        } else {
            System.out.println("Thread B runTime="
                    + System.currentTimeMillis());
        }
    }
}

线程A

package com.trigl.concurrent.synchro;

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

线程B

package com.trigl.concurrent.synchro;

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

测试方法

    @Test
    public void testService() throws InterruptedException {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        Thread.sleep(1000);
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }

输出结果

ThreadName=a run beginTime=1460337176027
ThreadName=a run exceptionTime=1460337176745
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at com.trigl.concurrent.synchro.Service.testMethod(Service.java:14)
    at com.trigl.concurrent.synchro.ThreadA.run(ThreadA.java:12)
Thread B runTime=1460337177028

1.3 同步不具有继承性

如果父类被synchronized关键字修饰,那么线程执行父类代码会同步,但是同步并不会继承给其子类,调用子类的方法仍然是异步的。栗子如下:

父类

package com.trigl.concurrent.synchro;

public class Main {
    synchronized public void serviceMethod() {}
}

子类

package com.trigl.concurrent.synchro;

public class Sub extends Main {
    @Override
    public void serviceMethod() {
        try {
            System.out.println("threadName=" + Thread.currentThread().getName()
                    + " 即将sleep " + "beginTime=" + System.currentTimeMillis());
            Thread.sleep(500);
            System.out.println("threadName=" + Thread.currentThread().getName()
                    + " sleep结束 " + "endTime=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

测试线程

package com.trigl.concurrent.synchro;

public class TestThread extends Thread {
    private Sub sub;

    public TestThread(Sub sub) {
        this.sub = sub;
    }

    @Override
    public void run() {
        sub.serviceMethod();
    }
}

测试方法

    @Test
    public void testExtends() throws InterruptedException {
        Sub sub = new Sub();
        TestThread a = new TestThread(sub);
        a.setName("A");
        a.start();
        TestThread b = new TestThread(sub);
        b.setName("B");
        b.start();
        Thread.sleep(1000);
    }

输出结果

threadName=A 即将sleep beginTime=1460367274466
threadName=B 即将sleep beginTime=1460367274466
threadName=B sleep结束 endTime=1460367274966
threadName=A sleep结束 endTime=1460367274966

2 同步代码块

一般来说,一个方法处理的内容很多,如果synchronized修饰以后,其他同步方法就必须等待其执行完毕才可以继续执行,如果该方法需要较长时间处理,这就明显会降低效率,失去了多线程的意义,所以我们可以考虑将同步的范围缩小,即从同步一个方法缩小为同步一段代码块,这就是同步代码块产生的原因。同步代码块的语法是:

synchronized (this) {}
synchronized (object) {}

synchronized(this)我称之为this同步代码块,针对的是当前对象;synchronized(object)我称之为非this同步代码块,针对的是object对象。记住一点即可:不论是同步方法还是同步代码块,实质上都是争夺锁的问题,而锁一定是对象级的,即一个对象只会产生一个锁,所以只要有一个线程在执行synchronized修饰的东西(不论是方法还是代码块),那么其他线程都无法访问被synchronized修饰的方法或代码块。
但是注意使用非this同步代码块的时候,里面的object不要用String类型的,因为大家都知道JVM具有String常量池缓存的功能,所以使用String类型可能产生问题。

下面用一个栗子来区分同步方法还有同步代码块的synchronized (this)和synchronized (object)

包含多种synchronized修饰的类

package com.trigl.concurrent.synchro;

/**
 * 多种同步方式
 * @author 白鑫
 * @date 2016年4月15日 上午10:12:48
 */
public class MultiSyn {
    private String anyString;

    public MultiSyn(String anyString) {
        this.anyString = anyString;
    }

    /**
     * 同步方法
     */
    synchronized public void synMethod() {
        System.out.println("IN 同步方法");
        System.out.println("OUT 同步方法");
    } 

    /**
     * this同步代码块
     */
    public void synThis() {
        synchronized (this) {
            System.out.println("IN synchronized (this)同步代码块");
            System.out.println("OUT synchronized (this)同步代码块");
        }
    }

    /**
     * 非this同步代码块
     */
    public void synObject() {
        synchronized (anyString) {
            System.out.println(anyString + " IN synchronized (anyString)同步代码块");
            System.out.println(anyString + " OUT synchronized (anyString)同步代码块");
        }
    }
}

线程1

package com.trigl.concurrent.synchro;

public class SynThread1 extends Thread {
    private MultiSyn ms;

    public SynThread1(MultiSyn ms) {
        super();
        this.ms = ms;
    }

    @Override
    public void run() {
        ms.synMethod();
    }
}

线程2

package com.trigl.concurrent.synchro;

public class SynThread2 extends Thread {
    private MultiSyn ms;

    public SynThread2(MultiSyn ms) {
        super();
        this.ms = ms;
    }

    @Override
    public void run() {
        ms.synThis();
    }
}

线程3

package com.trigl.concurrent.synchro;

public class SynThread3 extends Thread {
    private MultiSyn ms;

    public SynThread3(MultiSyn ms) {
        super();
        this.ms = ms;
    }

    @Override
    public void run() {
        ms.synObject();
    }
}

测试方法

    @Test
    public void testMultiSyn() {
        MultiSyn ms = new MultiSyn("Trigl");
        SynThread1 thread1 = new SynThread1(ms);
        SynThread2 thread2 = new SynThread2(ms);
        SynThread3 thread3 = new SynThread3(ms);
        thread1.start();
        thread2.start();
        thread3.start();
    }

由于多线程有很多不同的输出结果,这里选择两个输出结果来感受一下:

输出结果1

IN 同步方法
OUT 同步方法
IN synchronized (this)同步代码块
OUT synchronized (this)同步代码块
Trigl IN synchronized (anyString)同步代码块
Trigl OUT synchronized (anyString)同步代码块

输出结果2

IN 同步方法
Trigl IN synchronized (anyString)同步代码块
OUT 同步方法
Trigl OUT synchronized (anyString)同步代码块
IN synchronized (this)同步代码块
OUT synchronized (this)同步代码块

分析一下输出结果我们可以知道,同步方法和this同步代码块由于对应的都是同一个MultiSyn对象,因此会顺序同步执行;而非this同步代码块对应的是String字符串对象,因此是异步执行的。

3 静态同步synchronized方法和synchronized(class)代码块

关键字synchronized还可以应用在static静态方法上,或者synchronized(class)代码块。如果这样写,产生的是类级别的锁,也就是给*.java这个类加锁,而非给某个对象加锁。这就意味着线程执行同一个类的不同对象的静态同步synchronized方法和synchronized(class)代码块时,都会同步执行。
上栗子:

静态同步类

package com.trigl.concurrent.synchro;

/**
 * 静态同步
 * @author 白鑫
 * @date 2016年4月15日 上午11:03:41
 */
public class StaticSyn {
    /**
     * 静态同步方法1
     */
    synchronized static public void synMethod1() {
        System.out.println("IN 静态同步方法1");
        System.out.println("OUT 静态同步方法1");
    }

    /**
     * 静态同步方法2
     */
    synchronized static public void synMethod2() {
        System.out.println("IN 静态同步方法2");
        System.out.println("OUT 静态同步方法2");
    }

    /**
     * 静态代码块
     */
    public void synCodeBlock() {
        synchronized (StaticSyn.class) {
            System.out.println("IN synchronized (StaticSyn.class)代码块");
            System.out.println("OUT synchronized (StaticSyn.class)代码块");
        }
    }
}

线程1

package com.trigl.concurrent.synchro;

public class SynThread1 extends Thread {
    private StaticSyn ss;

    public SynThread1(StaticSyn ss) {
        super();
        this.ss = ss;
    }

    @Override
    public void run() {
        ss.synMethod1();
    }
}

线程2

package com.trigl.concurrent.synchro;

public class SynThread2 extends Thread {
    private StaticSyn ss;

    public SynThread2(StaticSyn ss) {
        super();
        this.ss = ss;
    }

    @Override
    public void run() {
        ss.synMethod2();
    }
}

线程3

package com.trigl.concurrent.synchro;

public class SynThread3 extends Thread {
    private StaticSyn ss;

    public SynThread3(StaticSyn ss) {
        super();
        this.ss = ss;
    }

    @Override
    public void run() {
        ss.synCodeBlock();
    }
}

测试方法

    @Test
    public void testStaticSyn() {
        StaticSyn ss1 = new StaticSyn();
        StaticSyn ss2 = new StaticSyn();
        StaticSyn ss3 = new StaticSyn();
        SynThread1 thread1 = new SynThread1(ss1);
        SynThread2 thread2 = new SynThread2(ss2);
        SynThread3 thread3 = new SynThread3(ss3);
        thread1.start();
        thread2.start();
        thread3.start();
    }

输出结果

IN 静态同步方法1
OUT 静态同步方法1
IN synchronized (StaticSyn.class)代码块
OUT synchronized (StaticSyn.class)代码块
IN 静态同步方法2
OUT 静态同步方法2

OVER

展开阅读全文

没有更多推荐了,返回首页