`

Java线程同步中关键字synchronized详述

    博客分类:
  • Java
阅读更多
synchronized关键可以修饰函数、函数内语句。无论它加上方法还是对象上,它取得的锁都是对象,而不是把一段代码或是函数当作锁。

1当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一段时间只能有一个线程得到执行,而另一个线程只有等当前线程执行完以后才能执行这块代码。
2当一个线程访问object中的一个synchronized(this)同步代码块时,其它线程仍可以访问这个object中是其它非synchronized (this)代码块。
3这里需要注意的是,当一个线程访问object的一个synchronized(this)代码块时,其它线程对这个object中其它synchronized (this)同步代码块的访问将被阻塞。
4以上所述也适用于其它的同步代码块,也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,这个线程就获得了object的对象锁。而且每个对象(即类实例)对应着一把锁,每个synchronized(this)都必须获得调用该代码块儿(可以函数,也可以是变量)的对象的锁才能执行,否则所属线程阻塞,方法一旦执行就会独占该锁,直到从方法返回时,也释放这个锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个对象,其所有声明为synchronized的成员函数中至多只有一个处于可执行状态(因为至多只有一个线程可以获取该对象的锁),从而避免了类成员变量的访问冲突。

synchronized方式的缺点:
由于synchronized锁定的是调用这个同步方法的对象,也就是说,当一个线程P1在不同的线程中执行这个方法时,它们之间会形成互斥,从而达到同步的效果。但这里需要注意的是,这个对象所性的Class的另一个对象却可以任意调用这个被加了synchronized关键字的方法。同步方法的实质是将synchronized作用于object reference,对于拿到了P1对象锁的线程才可以调用这个synchronized方法,而对于P2来说,P1与它毫不相干,程序也可能在这种情况下摆脱同步机制的控制,造成数据混乱。以下我们将对这种情况进行详细地说明:
首先我们先介绍synchronized关键字的两种加锁对象:对象和类——synchronized可以为资源加对象锁或是类锁,类锁对这个类的所有对象(实例)均起作用,而对象锁只是针对该类的一个指定的对象加锁,这个类的其它对象仍然可以使用已经对前一个对象加锁的synchronized方法。
在这里我们主要讨论的一个问题就是:“同一个类,不同实例调用同一个方法,会产生同步问题吗?
同步问题只和资源有关系,要看这个资源是不是静态的。同一个静态数据,你相同函数分属不同线程同时对其进行读写,CPU也不会产生错误,它会保证你代码的执行逻辑,而这个逻辑是否是你想要的,那就要看你需要什么样的同步了。即便你两个不同的代码,在CPU的不同的两个core里跑,同时写一个内存地址,Cache机制也会在L2里先锁定一个。然后更新,再share给另一个core,也不会出错,不然intel,amd就白养那么多人了。
因此,只要你没有两个代码共享的同一个资源或变量,就不会出现数据不一致的情况。而且同一个类的不同对象的调用有完全不同的堆栈,它们之间完全不相干。
以下我们以一个售票过程举例说明,在这里,我们的共享资源就是票的剩余张数。
package com.test;
 
public class ThreadSafeTest extends Thread implements Runnable {
    
   private static int num = 1;
 
    public ThreadSafeTest(String name) {
        setName(name);
    }
 
    public void run() {
        sell(getName());     
    }
   
    private synchronized void sell(String name){
        if (num > 0) {
            System. out.println(name + ": 检测票数大于0" );
            System. out.println(name + ": \t正在收款(大约5秒完成)。。。" );
            try {
                Thread. sleep(5000);
                System. out.println(name + ": \t打印票据,售票完成" );
                num--;
                printNumInfo();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System. out.println(name+": 没有票了,停止售票" );
        }
    }
    
   private static void printNumInfo() {
 
        System. out.println("系统:当前票数:" + num);
        if (num < 0) {
            System. out.println("警告:票数低于0,出现负数" );
        }
    }
 
    public static void main(String args[]) {
        try {
            new ThreadSafeTest("售票员李XX" ).start();
            Thread. sleep(2000);
            new ThreadSafeTest("售票员王X" ).start();
           
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
运行上述代码,我们得到的输出是:
售票员李XX: 检测票数大于0
售票员李XX:       正在收款(大约5秒完成)。。。
售票员王X: 检测票数大于0
售票员王X:  正在收款(大约5秒完成)。。。
售票员李XX:       打印票据,售票完成
系统:当前票数:0
售票员王X:  打印票据,售票完成
系统:当前票数:-1
警告:票数低于0,出现负数
根据输出结果,我们可以发现,剩余票数为-1,出现了同步错误的问题。之所以出现这种情况的原因是,我们建立的两个实例对象,对共享的静态资源static int num = 1同时进行了修改。那么我们将上面代码中方框内的修饰词static去掉,然后再运行程序,可以得到:
售票员李XX: 检测票数大于0
售票员李XX:       正在收款(大约5秒完成)。。。
售票员王X: 检测票数大于0
售票员王X:  正在收款(大约5秒完成)。。。
售票员李XX:       打印票据,售票完成
系统:当前票数:0
售票员王X:  打印票据,售票完成
系统:当前票数:0
对程度修改之后,程序运行貌似没有问题了,每个对象拥有各自不同的堆栈,分别独立运行。但这样却违背了我们希望多线程同时对共享资源的处理(去static后,num就从共享资源变成了每个实例各自拥有的成员变量),这显然不是我们想要的。
在以上两种代码中,采取的主要是对对象的锁定。由于我之前谈到的原因,当一个类的两个不同的实例对同一共享资源进行修改时,CPU为了保证程序的逻辑会默认这种做法,至于是不是想要的结果,这个只能由程序员自己来决定。因此,我们需要改变锁的作用范围,若作用对象只是实例,那么这种问题是无法避免的;只有当锁的作用范围是整个类的时候,才可能排除同一个类的不同实例对共享资源同时修改的问题。
package com.test;
 
public class ThreadSafeTest extends Thread implements Runnable {
    private static int num = 1;
 
    public ThreadSafeTest(String name) {
        setName(name);
    }
 
    public void run() {
        sell(getName());     
    }   
    
  private synchronized static void sell(String name){
 
        if (num > 0) {
            System. out.println(name + ": 检测票数大于0" );
            System. out.println(name + ": \t正在收款(大约5秒完成)。。。" );
            try {
                Thread. sleep(5000);
                System. out.println(name + ": \t打印票据,售票完成" );
                num--;
                printNumInfo();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System. out.println(name+": 没有票了,停止售票" );
        }
    }
 
    private static void printNumInfo() {
        System. out.println("系统:当前票数:" + num);
        if (num < 0) {
            System. out.println("警告:票数低于0,出现负数" );
        }
    }
 
    public static void main(String args[]) {
        try {
            new ThreadSafeTest("售票员李XX" ).start();
            Thread. sleep(2000);
            new ThreadSafeTest("售票员王X" ).start();
           
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
将程序做如上修改,可以得到运行结果:
售票员李XX: 检测票数大于0
售票员李XX:       正在收款(大约5秒完成)。。。
售票员李XX:       打印票据,售票完成
系统:当前票数:0
售票员王X: 没有票了,停止售票
对sell()方法加上了static修饰符,这样就将锁的作用对象变成了类,当该类的一个实例对共享变量进行操作时将会阻塞这个类的其它实例对其的操作。从而得到我们如期想要的结果。
总结:
1,synchronized关键字有两种用法:synchronized方法和synchronized块。
2,在Java中不单是类实例,每一个类也可以对应一把锁

在使用synchronized关键字时,有以下几点儿需要注意:
1,synchronized关键字不能被继承。虽然可以用synchronized来定义方法,但是synchronized却并不属于方法定义的一部分,所以synchronized关键字并不能被继承。如果父类中的某个方法使用了synchronized关键字,而子类中也覆盖了这个方法,默认情况下子类中的这个方法并不是同步的,必须显示的在子类的这个方法中加上synchronized关键字才可。当然,也可以在子类中调用父类中相应的方法,这样虽然子类中的方法并不是同步的,但子类调用了父类中的同步方法,也就相当子类方法也同步了。如,
在子类中加synchronized关键字:
class Parent { 
    public synchronized void method() {   } 
class Child extends Parent 
    public synchronized void method () {   } 
}
调用父类方法:
class Parent { 
    public synchronized void method() {   } 
class Child extends Parent { 
    public void method() { super.method();   } 
}
2,在接口方法定义时不能使用synchronized关键字。
3,构造方法不能使用synchronized关键字,但可以使用synchronized块来进行同步。
4,synchronized位置可以自由放置,但是不能放置在方法的返回类型后面。
5,synchronized关键字不可以用来同步变量,如下面代码是错误的:
public synchronized int n = 0; 
public static synchronized int n = 0;
6,虽然使用synchronized关键字是最安全的同步方法,但若是大量使用也会造成不必要的资源消耗以及性能损失。从表面上看synchronized锁定的是一个方法,但实际上锁定的却是一个类,比如,对于两个非静态方法method1()和method2()都使用了synchronized关键字,在执行其中的一个方法时,另一个方法是不能执行的。静态方法和非静态方法情况类似。但是静态方法和非静态方法之间不会相互影响,见如下代码:
public class MyThread1 extends Thread { 
    public String methodName 
 
    public static void method(String s) { 
        System. out .println(s); 
        while (true ); 
    } 
    public synchronized void method1() { 
        method( "非静态的method1方法" ); 
    } 
    public synchronized void method2() { 
        method( "非静态的method2方法" ); 
    } 
    public static synchronized void method3() { 
        method( "静态的method3方法" ); 
    } 
    public static synchronized void method4() { 
        method( "静态的method4方法" ); 
    } 
    public void run() { 
        try 
            getClass().getMethod( methodName ).invoke( this); 
        } 
        catch (Exception e) { 
        } 
    } 
    public static void main(String[] args) throws Exception { 
        MyThread1 myThread1 = new MyThread1(); 
        for (int i = 1; i <= 4; i++) { 
            myThread1. methodName "method" + String.valueOf (i); 
            new Thread(myThread1).start(); 
            sleep(100); 
        } 
    } 
}
运行结果为:
非静态的method1方法
静态的method3方法
从上面的运行结果可以看出,method2和method4在method1和method3运行完之前是不会运行的。因此,可以得出一个结论,如查在类中使用synchronized来定义非静态方法,那么将影响这个类中的所有synchronized定义的非静态方法;如果定义的静态方法,那么将影响这个类中所有以synchronized定义的静态方法。这有点儿像数据表中的表锁,当修改一条记录时,系统就将整个表都锁住了。因此,大量使用这种同步方法会使程序的性能大幅度地下降。

对共享资源的同步访问更加安全的技巧:
1,定义private的instance变量+它的get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象可以在外界绕过同步方法的控制而直接取得它,并且改动它。这也是JavaBean的标准实现之一。
2,如果instance变量是一个对象,如数组或ArrayList等,那上述方法仍然不安全,因为当外界通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且只返回这个private对象的clone()。这样,调用端得到的就只是对象副本的一个引用了。
 
 
 
 
参考资料:
分享到:
评论

相关推荐

    Java线程学习

    4. **线程同步机制**:`synchronized`关键字是Java中最基本的线程同步机制之一,它可以用于修饰方法或代码块,确保同一时刻只有一个线程可以访问被修饰的资源,从而避免数据竞争和不一致的问题。 通过上述知识点的...

    java并发编程与实践

    2. **同步机制**:详述了Java中同步的几种方法,如`synchronized`关键字、volatile变量、Lock接口(如ReentrantLock)以及Condition。这些机制用于防止数据竞争和确保线程安全。 3. **并发工具类**:Java并发包...

    Java程序设计 Java设计与应用

    7. **多线程编程**:介绍线程的概念,如何创建和管理线程,线程同步和通信(如synchronized关键字、wait()、notify()和notifyAll()方法)。 8. **网络编程**:涵盖Socket编程,服务器编程,以及TCP和UDP协议的使用...

    疯狂Java讲义(第三版)光盘源码

    6. **多线程编程**:探讨线程的概念,线程的创建方式(实现Runnable接口和继承Thread类),线程同步机制(synchronized关键字、wait()、notify()方法、volatile关键字),以及线程池的使用。 7. **网络编程**:涵盖...

    Java并发编程实践

    2. **同步机制**:详述Java中的synchronized关键字,包括对象锁和类锁的概念,以及如何使用它来避免数据竞争问题。同时,也会讲解 volatile 关键字的作用,它如何保证内存可见性以及防止指令重排序。 3. **等待/...

    JAVA的面试题-汇总!!!

    - 讨论线程同步机制,如wait(), notify()和notifyAll()方法。 - 阐述死锁、活锁和饥饿现象,以及如何避免它们。 6. **JVM相关** - 详解Java内存模型(JMM),包括堆内存、栈内存和方法区。 - 讲解垃圾收集...

    疯狂Java讲义精粹随书光盘

    6. **多线程**:深入探讨线程的创建、同步、通信和管理,包括synchronized关键字、wait()和notify()方法。 7. **反射机制**:如何在运行时动态地获取类信息并操作类的对象,包括Class类的使用、构造器的调用、方法...

    平安面试题目 java

    - 介绍Java中的线程同步机制,如synchronized关键字、wait()、notify()、notifyAll()方法。 - 线程池的工作原理是什么?ExecutorService、ThreadPoolExecutor、ScheduledThreadPoolExecutor如何使用? - volatile...

    JAVA经典实例.chm

    5. **多线程**:Java提供了丰富的多线程支持,包括Thread类、Runnable接口、同步机制(synchronized关键字、wait()、notify()和notifyAll()方法)以及并发工具类(如Semaphore、CyclicBarrier等)。 6. **网络编程*...

    java编程思想中文3

    书中有详尽的线程同步、并发控制(如synchronized关键字、volatile修饰符、Lock接口)等内容,帮助开发者创建高效的并发程序。 8. **输入/输出流**:Java的I/O流系统是处理数据传输的关键,涵盖了字符流、字节流、...

    良葛格java学习笔记

    7. **多线程**:讲解线程的创建和管理,包括Thread类和Runnable接口,线程同步机制(如synchronized关键字、wait()、notify()方法),以及并发工具类的使用。 8. **JVM原理**:简要介绍Java虚拟机的工作原理,包括...

    Java Concurrency in Practice

    3. **同步机制**:详述synchronized关键字、wait()、notify()和notifyAll()方法,以及如何正确使用它们实现线程间的通信和协作。同时,介绍了Lock接口和ReentrantLock类,对比分析它们与synchronized的区别和使用...

    Thinking in Java 中文版英文版

    4. **多线程**:Java支持多线程编程,书中详细讲解了线程的创建、同步、互斥和并发控制,包括synchronized关键字、wait/notify机制以及并发工具类。 5. **输入/输出**:Java的I/O系统强大而灵活,书中介绍了流的...

    Core Java. 8th java核心技术第八版英文

    8. **并发编程**:涵盖线程、同步机制(synchronized关键字、Lock接口)、并发集合(如ConcurrentHashMap)以及ExecutorService和Future接口,帮助开发者编写高效且安全的多线程程序。 9. **反射API**:解释如何在...

    core Java 11 Edition Java核心技术 卷一卷二

    1. **多线程编程**:讲解线程的创建、同步、并发工具,如synchronized关键字、volatile变量和java.util.concurrent包中的类。 2. **网络编程**:介绍套接字编程、HTTP协议,以及如何使用Java进行网络通信。 3. **...

    Java编程标准教程

    3. **多线程**:介绍线程的创建、同步控制(synchronized关键字、wait/notify机制)、线程池等。 4. **网络编程**:讲述Socket编程,包括客户端和服务器端的建立连接,数据传输等。 5. **反射机制**:讲解如何在运行...

    JAVA编程思想中文第3版

    书中涵盖了线程的创建与启动,同步机制(如synchronized关键字、wait/notify机制),以及并发工具类如Semaphore、CyclicBarrier等。 6. **网络编程**:Java提供了一套丰富的API用于网络编程,如Socket和...

    thinking in java(中文第三版)

    6. **多线程**:Java内置了对多线程的支持,书中讲解了线程的创建与同步,包括synchronized关键字、wait/notify机制以及并发工具类如Semaphore、CyclicBarrier等。 7. **输入/输出与网络编程**:这部分涵盖了Java的...

    《Java 程序设计》作者唐大仕.rar

    6. **多线程**:讲解如何创建和管理线程,以及同步机制,如synchronized关键字和wait/notify机制。 7. **反射机制**:通过反射可以在运行时动态地获取类的信息并进行操作,是Java的一个强大特性。 8. **Java API**...

    java深度历险+深入java虚拟机

    3. **多线程编程**:Java以其强大的多线程支持而闻名,书中会介绍线程的创建、同步、并发控制以及死锁等问题的解决方案。 4. **集合框架**:Java集合框架是其强大的特性之一,书中会详细解释List、Set、Map等各种...

Global site tag (gtag.js) - Google Analytics