Synchronized的使用

锁的类型
  • 类锁:只有synchronized修饰静态方法或者修饰一个类的class对象时,才是类锁。
  • 对象锁:除了类锁,所有其他的上锁方式都认为是对象锁。比如synchronized修饰普通方法或者synchronized(this)给代码块上锁等

我们先来看下面这段代码:

private static void count(String name, int i) {
        x = i;
        y = i;
        if (x != y) {
            System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " x=" + x + ",y="+ y);
        }
    }

阅读上面的代码,我们可能会觉得不会有什么输出值。是的如果上面的代码是在一个线程中运行,那么确实不会有输出值;那么我们在两个线程,或者更多线程中运行,会是什么结果呢?我们来验证一下:

//生成一个Runnable对象
private static Runnable getRunnable1(final String name, final int i) {
        return new Runnable() {
            @Override
            public void run() {
                count("Count", i);
            }
        };
    }

//建立线程池,并生成两个线程
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < Integer.MAX_VALUE/10; i++) {
       executorService.execute(getRunnable1(name, i));
       executorService.execute(getRunnable1(name, i));
   }
 executorService.shutdown();

运行程序,并查看输出结果:

Count-->CurrThread:pool-4-thread-69 x=1350
Count-->CurrThread:pool-4-thread-69 y=1322
Count-->CurrThread:pool-4-thread-5 x=1427
Count-->CurrThread:pool-4-thread-5 y=1432

为什么会有结果输出呢?两个线程中都调用的是count方法,里面就是实现了简单的赋值操作;一个线程正在进行赋值操作,这时另一个线程也调用方法进行赋值操作,这样就会造成这个问题,因为多线程中,共同调用一个方法进行赋值,并不是一个线程赋值完成后,另一个线程在进行调用。那么怎么解决这个问题呢?这就是我们这篇要讲的内容。
互斥锁,也就是:当多个线程共同访问共享数据时,需要保证同一时刻有且只有一个线程进行数据操作,其他线程等待该线程执行完后在执行。关键字Synchronized就可满足。下面来看它的具体使用。

  • 修饰非静态实例方法
    建立一个线程实现类,里面调用Count类中的方法
public class TempRunnable implements Runnable {
    private Count count;

    public TempRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
              count.count("TempRunnable_Count");
    }
}

Count类中的方法

public void count(String name) {
        x++;
        System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " x=" + x);
    }

建立两个线程

private static void outPutString() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count = new Count();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new TempRunnable(count));
            executorService.execute(new TempRunnable(count));
        }
        executorService.shutdown();
    }

我们期望的结果是2000,来看下代码输出结果:

TempRunnable_Count-->CurrThread:pool-4-thread-276 x=1366
TempRunnable_Count-->CurrThread:pool-4-thread-59 x=1364

这个结果明显不是我们想要的,我们在来看synchronized修饰的方法:

public synchronized void improve(String name) {
        x++;
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " x=" + x);
    }

运行程序,结果为:

TempRunnable_improve(Improve)-->CurrThread:pool-4-thread-4 x=1999
TempRunnable_improve(Improve)-->CurrThread:pool-4-thread-60 x=2000

这个明显是我们需要的结果。在看下面例子:
首先,一个类中建立三个方法,两个synchronized方法和一个普通方法,如下:

public class Count {
    public synchronized void countImprove3(String name) {
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
    }

    public synchronized void countImprove4(String name) {
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
    }

    public void countImprove(String name) {
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " end...");
    }
}

其次:建立一个Runnable实现类,如下:

public class MyRunnable implements Runnable {
    private Count count;

    public MyRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
        count.countImprove3("countImprove3:synchronized");
        count.countImprove("normal");
        count.countImprove4("countImprove4:synchronized");
    }
}

最后,在代码中的使用,如下:

        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count = new Count();
        executorService.execute(new MyRunnable(count));
        executorService.execute(new MyRunnable(count));
        executorService.shutdown();

看输出结果:

countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
normal(Improve)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
normal(Improve)-->CurrThread:pool-4-thread-1 end...
normal(Improve)-->CurrThread:pool-4-thread-2 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
normal(Improve)-->CurrThread:pool-4-thread-2 end...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...

从输出结果我们可以看到:

  • 在同一个实例对象中,调用synchronized方法的同时,不影响非synchronized方法的调用;
  • 在同一个实例对象中,一个线程调用了其中的synchronized方法后,其他线程就不能再继续调用其中的其他synchronized方法;只有当先调用的线程释放锁后其他线程才能调用。
  • 给方法添加synchronized修饰,本质上就是给这个类的对象加锁,也就是对象锁。
    那我们传入不同的实例对象,有是什么情况呢,看代码:
        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count = new Count();
        executorService.execute(new MyRunnable(new Count(new Count("Count Instance 1:"))));
        executorService.execute(new MyRunnable(new Count(new Count("Count Instance 2:"))));
        executorService.shutdown();

其输出结果为:

Count Instance 1:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove(Improve)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove(Improve)-->CurrThread:pool-4-thread-2 start...
Count Instance 2:countImprove(Improve)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove(Improve)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...

从上面的输出结果可以看出,他们之间的调用互不影响。因为传入了各自的Count对象,运行后他们进入各自的对象锁。这样就会造成最开始例子中值不一样的问题了。
如果我就想传入不同的对象,但是要实现的效果和传入同一对象一样,这该怎么实现呢?这就需要将synchronized作用于静态方法中,看例子:
新建Count类中有一个static修饰的synchronized方法,如下:

public class Count {
    public static synchronized void add(String name) {
        m++;
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " m=" + m);
    }

    public synchronized void count(String name) {
        y++;
        System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " y=" + y);
    }
}

先调用count方法,运行,结果为:

Count Instance 1:-->CurrThread:pool-4-thread-53 y=98
Count Instance 1:-->CurrThread:pool-4-thread-46 y=97

调用add方法,运行,结果为:

Count Instance 2:(Improve)-->CurrThread:pool-4-thread-35 m=199
Count Instance 1:(Improve)-->CurrThread:pool-4-thread-7 m=200

通过上面两个例子可以看出,传入不同的对象,调用synchronized方法,并没有起到互斥锁的作用,因为他们进入了各自的对象锁,因此输出的值并不是依次叠加后的值;调用静态synchronized方法,结果输出正确,所以静态的synchronized方法相当于给整个类加锁,也就是类锁。线程调用其中的静态的synchronized方法,只有一个线程执行完释放锁后其余线程才能进行调用。但是不影响调用其他非静态的方法。

新建类Count中有下面几个方法:

public class Count {
    public void countImprove2(String name) {
        synchronized (this) {
            for (int i = 0; i < 3; i++) {
                System.out.println(name + "(countImprove2)-->CurrThread:" + Thread.currentThread().getName() + " start...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + "(countImprove2)-->CurrThread:" + Thread.currentThread().getName() + " end...");
            }
        }
    }

    public synchronized void countImprove4(String name) {
        for (int i = 0; i < 3; i++) {
            System.out.println(name + "(countImprove4)-->CurrThread:" + Thread.currentThread().getName() + " start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "(countImprove4)-->CurrThread:" + Thread.currentThread().getName() + " end...");
        }
    }

    public void countImprove(String name) {
        for (int i = 0; i < 3; i++) {
            System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " end...");
        }
    }
}

该类中提供了一个synchronized方法,一个方法中含synchronized代码块和一个普通方法。接下来在建立两个线程类ThreadAThreadB,代码如下:

public class ThreadA implements Runnable {
    private Count count;

    public TempRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
        count.countImprove2(count.getName());
    }
}

public class ThreadB implements Runnable {
    private Count count;

    public TempRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
        count.countImprove4(count.getName());
    }
}

在代码中使用:

        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count1 = new Count("Count Instance 1:");
        executorService.execute(new TempRunnable(count1));
        executorService.execute(new MyRunnable(count1));
        executorService.shutdown();

传入了相同的Count实例对象,运行结果为:

Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...

从结果看它是一个执行完成后,另一个才开始执行的。在代码中我们的countImprove2方法是上的对象锁,而countImprove4synchronized代码段传入的是this,因此也就相当于上的对象锁;而调用时传入的也是同一对象,所以:当一个线程调用countImprove2方法,在没有结束前其他的线程并不能调用countImprove4方法,但是可以调用非synchronized修饰过的方法。
更改上面的使用代码,如下:

        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count1 = new Count("Count Instance 1:");
        Count count2 = new Count("Count Instance 2:");
        executorService.execute(new TempRunnable(count1));
        executorService.execute(new MyRunnable(count2));
        executorService.shutdown();

传入了两个不同的实例对象,运行结果为:

Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...

从结果看,他们是互不影响的。因为传入的是两个不同的,所以他们加的也是不同的对象锁,因此互不影响。
我们来在Count类中加入下面两个方法:

......
    public void countImprove5(String name) {
        synchronized (Count.class) {
            for (int i = 0; i < 2; i++) {
                System.out.println(name + "(countImprove5)-->CurrThread:" + Thread.currentThread().getName() + " start...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + "(countImprove5)-->CurrThread:" + Thread.currentThread().getName() + " end...");
            }
        }
    }

    public static synchronized void countImprove3(String name) {
        for (int i = 0; i < 2; i++) {
            System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
        }
    }
......

ThreadAThreadB中分别调用countImprove5countImprove3方法,在传入相同的对象,运行结果为:

Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...

从结果看它是一个执行完成后,另一个才开始执行的。在代码中我们的countImprove5方法中是synchronized代码段,传入的是Count.class因此上的是类锁;而countImprove3为静态synchronized方法,也就相当于上的类锁;而调用时传入的也是同一对象,所以:当一个线程调用countImprove5方法,在没有结束前其他的线程并不能调用countImprove3方法,但是可以调用非synchronized修饰过的方法。

更改代码在ThreadB中调用countImprove4方法,传入相同的对象,运行结果为:

Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...

从结果看互不干扰,因为countImprove4方法加的是对象锁,而countImprove5加的是类锁,他们不是同一类锁,因此互不干扰。

更改代码,在ThreadAThreadB中分别调用countImprove2countImprove3方法,在传入相同的对象,运行结果为:

Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...

从结果看也是互不干扰,因为countImprove2方法加的是对象锁,而countImprove3加的是类锁,他们不是同一类型的锁,因此互不干扰。

总的来说:
  • 加了相同的锁,不管是对象锁(同一个类的实例对象)还是类锁(同一个类),只有先获取到锁的线程执行完成后其它线程才能继续执行。也就是说:为对象锁时,获取到锁的线程执行完方法后,下次执行的加锁的方法可能是另一个线程了;为类锁时,先获取到锁的线程将改线程内的加了同步锁的方法执行完成后,其它线程才能继续执行。
  • 加了不同的锁,不同的线程之间调用是互不影响的。
  • 不管加了什么锁,对于普通方法的调用不影响。

下一篇:Lock的使用

参考:
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容