1.为什么要使用synchronized?

在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性)

2.synchronized锁什么,加锁的目的是什么?

synchronized锁的是对象,锁的的对象可能是this、临界资源对象、class类对象
加锁的目的是保证操作的原子性

3.代码示例

3.1锁this和临界资源对象

本例中:

  • testSync1是synchronized代码块,锁的是object,临界资源对象
  • testSync2和testSync3都是锁的是当前对象this
/**
 * synchronized关键字
 * 锁对象。synchronized(this)和synchronized方法都是锁当前对象。
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_01 {
    private int count = 0;
    private Object object = new Object();

    // 1.synchronized代码块,锁的是object,临界资源对象
    public void testSync1(){
        synchronized(object){
            System.out.println(Thread.currentThread().getName()
                    + " count = " + count++);
        }
    }

    // 2.synchronized代码块,锁的是当前对象this
    public void testSync2(){
        synchronized(this){
            System.out.println(Thread.currentThread().getName()
                    + " count = " + count++);
        }
    }

    // 2.synchronized代码,锁的也是this
    public synchronized void testSync3(){
        System.out.println(Thread.currentThread().getName()
                + " count = " + count++);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final Test_01 t = new Test_01();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t.testSync3();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t.testSync3();
            }
        }).start();
    }

}

3.2锁class类对象

/**
 * synchronized关键字
 * 同步方法 - static
 * 静态同步方法,锁的是当前类型的类对象。在本代码中就是Test_02.class
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_02 {
    private static int staticCount = 0;

    public static synchronized void testSync4(){
        System.out.println(Thread.currentThread().getName()
                + " staticCount = " + staticCount++);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void testSync5(){
        synchronized(Test_02.class){
            System.out.println(Thread.currentThread().getName()
                    + " staticCount = " + staticCount++);
        }
    }

}

3.3 什么时候锁临界资源,什么时候锁当前对象?

如果在加锁的时候需要对当前对象的访问限定,建议锁临界资源(即锁一个临界资源),如果对当前锁级别比较高的话,就锁当前对象。建议都以同步代码块的方式进行开发,这样可以避免锁的范围太高

同步方法和非同步方法
提问:同步方法是否影响其他线程调用非同步方法,或调用其他锁资源的同步方法?

代码示例
m1是非同步方法,m2是同步方法,m3同步代码块,锁的临界资源,这段代码的目的是为了证明在调用同步方法m1时,m2,m3是否能够执行

/**
 * synchronized关键字
 * 同步方法 - 同步方法和非同步方法的调用
 * 同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
 */
package com.bernardlowe.concurrent.t01;

public class Test_04 {
    Object o = new Object();
    public synchronized void m1(){ // 重量级的访问操作。
        System.out.println("public synchronized void m1() start");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("public synchronized void m1() end");
    }

    public void m3(){
        synchronized(o){
            System.out.println("public void m3() start");
            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("public void m3() end");
        }
    }

    public void m2(){
        System.out.println("public void m2() start");
        try {
            Thread.sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("public void m2() end");
    }

    public static class MyThread01 implements Runnable{
        public MyThread01(int i, Test_04 t){
            this.i = i;
            this.t = t;
        }
        int i ;
        Test_04 t;
        public void run(){
            if(i == 0){
                t.m1();
            }else if (i > 0){
                t.m2();
            }else {
                t.m3();
            }
        }
    }

    public static void main(String[] args) {
        Test_04 t = new Test_04();
        new Thread(new MyThread01(0, t)).start();
        new Thread(new MyThread01(1, t)).start();
        new Thread(new MyThread01(-1, t)).start();
    }

}

执行结果

可以看出,在执行同步方法m1时,m2,m3不受影响,依然可以执行
结论:同步方法只影响锁定同一个锁对象的同步方法。不影响其他线程调用非同步方法,或调用其他锁资源的同步方法。
但有一点需要注意,尽量在商业开发中避免同步方法。使用同步代码块。 细粒度解决同步问题。

3.3 重入锁

这里重入锁分为两类:

  • 1.在同步方法里面调用其他同步方法
  • 2.子类同步方法覆盖父类同步方法

下面来看第一种:在同步方法里面调用其他同步方法
思考:调用m1()方法,m2()方法是否会执行?

/**
 *synchronized关键字
 *同步方法 - 调用其他同步方法
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_06 {

    synchronized void m1(){ // 锁this
        System.out.println("m1 start");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        m2();
        System.out.println("m1 end");
    }
    synchronized void m2(){ // 锁this
        System.out.println("m2 start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m2 end");
    }

    public static void main(String[] args) {

        new Test_06().m1();

    }

}  


结果:在m1()方法调用时,m2()方法依然可执行。多次调用同步代码,锁定同一个锁对象,是可重入的

第二种情况:子类同步方法覆盖父类同步方法
思考:子类同步方法m()中,调用父类同步方法m(),是否可重入?

/**
 * synchronized关键字
 * 同步方法 - 继承
 * 子类同步方法覆盖父类同步方法。可以指定调用父类的同步方法。
 * 相当于锁的重入。
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_07 {

    synchronized void m(){
        System.out.println("Super Class m start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Super Class m end");
    }

    public static void main(String[] args) {
        new Sub_Test_07().m();
    }

}

class Sub_Test_07 extends Test_07{
    synchronized void m(){
        System.out.println("Sub Class m start");
        super.m();
        System.out.println("Sub Class m end");
    }
}


结果:子类同步方法m()中,调用了父类同步方法m(),可以重入

3.4 锁与异常

思考:当同步方法或同步代码块中发生异常,是否会影响其他线程的执行?
下面来看一段代码

/**
 * synchronized关键字
 * 同步方法 - 锁与异常
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_08 {
    int i = 0;
    synchronized void m(){
        System.out.println(Thread.currentThread().getName() + " - start");
        while(true){
            i++;
            System.out.println(Thread.currentThread().getName() + " - " + i);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 当i==5时会抛出异常
            if(i == 5){
                i = 1/0;
            }
        }
    }

    public static void main(String[] args) {
        final Test_08 t = new Test_08();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t.m();
            }
        }, "t1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                t.m();
            }
        }, "t2").start();
    }

}

这段代码中先运行了两个线程t1、t2,当其中一个线程发生异常时,另外一个线程是否能继续执行

结果:当同步方法中发生异常的时候,自动释放锁资源。不会影响其他线程的执行。

思考: 同步业务逻辑中,如果发生异常如何处理?
比如上面会发生异常的代码中,可以这样

            if(i == 5){
                try {
                    i = 1/0;
                } catch (Exception e) {
                    i = 0;
                }
            }

3.5 锁对象变更问题

代码示例:
思考:当一个线程执行同步方法时,另一个线程修改了锁对象,是否还能执行同步代码块

/**
 * synchronized关键字
 * 锁对象变更问题
 */
package com.bernardlowe.concurrent.t01;

import java.util.concurrent.TimeUnit;

public class Test_13 {
    Object o = new Object();

    void m(){
        System.out.println(Thread.currentThread().getName() + " start");
        synchronized (o) {
            while(true){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " - " + o);
            }
        }
    }

    public static void main(String[] args) {
        final Test_13 t = new Test_13();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t.m();
            }
        }, "thread1").start();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                t.m();
            }
        }, "thread2");
        t.o = new Object();
        thread2.start();
    }

}


结论:可以看出,其他线程依然可以执行同步方法
因为同步代码一旦加锁后,那么会有一个临时的锁引用执行锁对象,和真实的引用无直接关联。在锁未释放之前,修改锁对象引用,不会影响同步代码的执行。

注意:不要使用静态常量作为锁对象
如下代码,因为String常量池的问题,s1,s1是同一个对象,所以m1,m2方法锁的是也同一个对象,m1同步方法被执行后,m2方法不会被执行

/**
 * synchronized关键字
 * 常量问题
 */
package com.bernardlowe.concurrent.t01;

public class Test_14 {
    String s1 = "hello";
    String s2 = "hello";
    // String s2 = new String("hello"); // new关键字,一定是在堆中创建一个新的对象。
    void m1(){
        synchronized (s1) {
            System.out.println("m1()");
            while(true){

            }
        }
    }

    void m2(){
        synchronized (s2) {
            System.out.println("m2()");
            while(true){

            }
        }
    }

    public static void main(String[] args) {
        final Test_14 t = new Test_14();
        new Thread(new Runnable() {
            @Override
            public void run() {
                t.m1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                t.m2();
            }
        }).start();
    }

}

多线程总结-同步之synchronized关键字的更多相关文章

  1. 多线程,线程同步,synchronized关键字的用法

    一.什么是多线程 Java多线程实现方式主要有四种:继承Thread类.实现Runnable接口.实现Callable接口通过FutureTask包装器来创建Thread线程.使用ExecutorSe ...

  2. Java:多线程,线程同步,synchronized关键字的用法(同步代码块、非静态同步方法、静态同步方法)

    关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨synchronized关键字. sy ...

  3. Java多线程(四)—— synchronized关键字续

    1.synchronized原理 在java中,每一个对象有且仅有一个同步锁.这也意味着,同步锁是依赖于对象而存在.当我们调用某对象的synchronized方法时,就获取了该对象的同步锁.例如,sy ...

  4. Java多线程(三)—— synchronized关键字详解

    一.多线程的同步 1.为什么要引入同步机制 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源.必须对这种潜在资源冲突进行预防. 解决方法:在线程使用一个资源时为其加锁即可. 访问资 ...

  5. [多线程] 线程中的synchronized关键字锁

    为什么要用锁? 在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实 ...

  6. Java基础-多线程-③线程同步之synchronized

    使用线程同步解决多线程安全问题 上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数据,且一个线程在操作(多为写操作)数据的过程中,另一个线程 ...

  7. Java多线程的同步机制(synchronized)

    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...

  8. Java 多线程(六) synchronized关键字详解

    多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题. 同步机制可以使用synchronized关键字实现. 当synchroniz ...

  9. 简单的互斥同步方式——synchronized关键字详解

    目录 1. 关于synchronized关键字 2. synchronized的原理和实现细节 2.1 synchronized可以用在那些地方 2.2 synchronized是如何实现线程互斥访问 ...

随机推荐

  1. Win8 Metro(C#)数字图像处理--2.51图像统计滤波算法

    原文:Win8 Metro(C#)数字图像处理--2.51图像统计滤波算法  [函数名称]   图像统计滤波   WriteableBitmap StatisticalFilter(Writeab ...

  2. Win10《芒果TV》商店版更新v3.2.0:全新播放体验,跟着爸爸,想去哪就去哪

    喜迎十一月黑五大促,跟着爸爸,想去哪就去哪,<芒果TV>UWP版迅速更新v3.2.0版,全新播放页华丽蜕变,新增互动评论.猜你喜欢.宽窄屏适配.多窗体模式切换. 芒果TV UWP V3.2 ...

  3. Redis 高可用之哨兵模式

    参考   : https://mp.weixin.qq.com/s/Z-PyNgiqYrm0ZYg0r6MVeQ 一.redis高可用解决方案 redis主从 优点:1.高可靠性,主从实时备份,有效解 ...

  4. Oracle 裁掉北京研发团队,相应职位撤回美国(收购了NetSuite,LogFire,Dyn)

    根据中国日报报道,2017年1月14日上午9点09分,甲骨文北京研发团队的同事收到了来自BU老大的一封邮件.邮件上提及,由于市场变化,甲骨文开始整合各研发中心资源公司在云计算方向发力,文末单独提出了甲 ...

  5. [转]UE的职责

    最近开始负责产品工作,从产品定位到需求文档再到原型设计,以及后续产品的路线规划,渐渐感觉忙不过来了.所以准备招一个UE/X来辅助. 以前做项目,也没接触过什么UE,所以对UE的工作不是很清楚,尤其是P ...

  6. 逆向工程mybatis-geneator.xml

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE generatorConfiguration ...

  7. 在 .pro里加入 QMAKE_CXXFLAGS += /MP 将并行编译(VC),加快编译速度(姚冬的办法),或者-j4参数(MinGW)

    但是只对VC编译器有效果. 另外还可以自己设置stdafx.h文件 http://www.zhihu.com/question/23045749 C:\Qt\Qt5.6.2_static\bin\qm ...

  8. request的跳转

    使用request.getRequestDispather(url).forword(request,response)方法跳转页面 地址栏的路径不会发生改变,在后续的ajax调用 使用window. ...

  9. SYN2306A型 GPS北斗双模授时板

    SYN2306A型 GPS北斗双模授时板 北斗gps时钟北斗授时设备北斗时钟同步系统使用说明视频链接: http://www.syn029.com/h-pd-211-0_310_36_-1.html ...

  10. Spring之ApplicationContext

    (1)ApplicationContext接口容器 ApplicationContext用于加载Spring的配置文件,在程序中充当“容器”的角色.其实现类有两个.通过Ctrl +T查看: A.配置文 ...