synchronized作用范围及用法

本文详细介绍了Java中多线程同步机制的实现方法,包括synchronized关键字的应用、同步方法和同步代码块的区别等,并通过具体示例展示了如何避免多线程环境下共享资源导致的数据错误。

synchronized作用范围及用法

转载自 http://www.cnblogs.com/welcoming/p/3375664.html  对原文有修改

1、多线程的同步:

1.1、同步机制:

在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生。所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问。

1.2、共享成员变量的例子:
成员变量与局部变量:

成员变量:

如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的。

局部变量:

如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝。他们之间的局部变量互不影响。

下面举例说明:

实现了Runnable的线程类:

复制代码
class MyThread3 implements Runnable{

    //两个线程操作同一个对象,共享成员变量
    //int i;
    @Override
    public void run() {
        //两个线程操作同一个对象,各自保存局部变量的拷贝
        int i = 0;
        while(i<100){
            System.out.println(i);
            i++;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}在main方法中用两个线程操作同一个对象:

public static void main(String[] args) {

    MyThread3 myThread = new MyThread3();
    //下面两个线程对同一个对象(Runnable的实现类对象)进行操作
    Thread thread = new Thread(myThread);
    Thread thread2 = new Thread(myThread);
    //各自保存局部变量的拷贝,互不影响,输出200个数字
    thread.start();
    thread2.start();
}
复制代码

这里如果把i变成成员变量,则输出100个数字。

1.3、共享资源导致的读取错误

下面举个例子,两个线程共用一个Number对象,通过Number类的getNumber方法获取数据,读取数据并改写时,发现了重复读操作:

首先创建一个Number类:

复制代码
class Number{
    private int number = 10;
    public String getNumber(int i){
        if(number > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            number -= i;
            return "取出"+i+"成功,剩余数量:"+number;
        }
        return "取出"+i+"失败,剩余数量:"+number;
    }
}线程类,在线程类中的私有属性包含了Number类的引用:

class MyThread4 extends Thread{

    //两个线程操作同一个对象,共享成员变量
    Number number;
    public MyThread4(Number number){
        this.number = number;
    }
    @Override
    public void run() {
        System.out.println(number.getNumber(8));
    }
}在main函数中创建两个线程类,包含了同一个Number类实例的引用:

public static void main(String[] args) {

    Number number = new Number();
    //两个线程操作同一个对象,共享对象number的成员变量number
    MyThread4 myThread = new MyThread4(number);
    MyThread4 myThread2 = new MyThread4(number);
    myThread.start();
    myThread2.start();
}
复制代码

 

这样,当第一个线程读取Number中的number变量时先保存下来再休眠0.1秒,然后第二个线程再读取number变量并保存,此时两个线程保存了同样的数字,在修改时,也就导致修改了同一个数字两次。

2、同步机制的实现:
2.1、使用synchronized关键字创建synchronized方法:

使用synchronized关键字,该关键字修饰的方法叫做同步方法。

Java中每个对象都有一个锁或者称为监视器,当访问某个对象的synchronized方法时,表示将该对象上锁,而不仅仅是为该方法上锁。

这样如果一个对象的synchronized方法被某个线程执行时,其他线程无法访问该对象的任何synchronized方法(但是可以调用其他非synchronized的方法)。直至该synchronized方法执行完。

静态的synchronized方法调用情况:

当调用一个对象的静态synchronized方法时,它锁定的并不是synchronized方法所在的对象,而是synchronized方法所在对象对应的Class对象。这样,其他线程就不能调用该类的其他静态synchronized方法了,但是可以调用非静态的synchronized方法。

结论(原文):执行静态synchronized方法锁方法所在对象,执行非静态synchronized方法锁方法所在对象对应的Class对象。

结论(修改,作者应该是写反了):执行非静态synchronized方法锁方法所在对象,执行静态synchronized方法锁方法所在对象对应的Class对象。

(理解就是,有多个静态的synchronized方法,同时只能只能执行一个静态synchronized方法,不影响其他的非静态synchronized方法(包括非静态的synchronized方法)执行。 有多个synchronized方法时,同时只能执行一个synchronized方法,不影响其他非synchronized方法执行。)


下面是多线程调用静态的方法的例子,由于锁定了方法所在对象对应的Class对象,其他线程无法调用该方法所在对象其他的静态synchronized方法:

复制代码
/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
    //这时如果某个线程调用该方法,
    //将锁定synchronized方法所在对象对应的class对象,
    //而不是锁定synchronized方法所在对象
    public synchronized static void execute(){
        for(int i = 0; i<100; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("compute1:execute1 " + i++);
        }
    }
    public synchronized static void execute2(){
        for(int i = 0; i<100; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("compute1:execute2 " + i++);
        }
    }
}main方法中两个线程分别调用同一个对象的两个static synchronized方法:

class Thread1 extends Thread{
        Compute com;
        public Thread1(Compute com){
            this.com = com;
        }
        @Override 
        public void run(){
            com.execute();
        }
    }
    
    class Thread2 extends Thread{
        Compute com;
        public Thread2(Compute com){
            this.com = com;
        }
        @Override 
        public void run(){
            com.execute2();
        }
    }

public static void main(String[] args) {
    Compute1 com = new Compute1();
    Thread thread1 = new Thread1(com);
    Thread thread2 = new Thread2(com);
    thread1.start();
    thread2.start();
}
复制代码

 

一次只能调用一个静态方法,直到执行完成。

2.2、使用synchronized创建同步代码块:

通过使用synchronized同步代码块,锁定一个对象,该对象作为可执行的标志从而达到同步的效果:

复制代码
/**
 * 定义一个类,包含了线程类需要调用的方法
 */
class Compute1{
    //通过同步代码块锁定object1对象进行锁定了其他同样的synchronized代码块
    private Object object1 = new Object();
    public void execute(){
        synchronized(object1){
            for(int i = 0; i<100; i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("compute1:execute1 " + i++);
            }
        }

    }
    public synchronized void execute2(){
        synchronized(object1){
            for(int i = 0; i<100; i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("compute1:execute2 " + i++);
            }
        }
    }
}
复制代码

 

如果想要使用synchronized同步代码块达到和使用synchronized方法同样的效果,可以锁定this引用:

synchronized(this){
    …
}
2.3、synchronized方法和synchronized同步代码块的区别:

synchronized同步代码块只是锁定了该代码块,代码块外面的代码还是可以被访问的。

synchronized方法是粗粒度的并发控制,某一个时刻只能有一个线程执行该synchronized方法。

synchronized同步代码块是细粒度的并发控制,只会将块中的代码同步,代码块之外的代码可以被其他线程同时访问。

`synchronized` 作用在类方法(静态方法)和对象方法(非静态方法)上有以下区别: ### 锁的对象不同 - **类方法**:当 `synchronized` 作用于静态方法时,实际上是对当前类的 `class` 对象加锁。这意味着一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们是同一个类的实例对象。例如: ```java public class SynchronizedExample { public static synchronized void staticMethod1() { // 静态同步方法1 } public static synchronized void staticMethod2() { // 静态同步方法2 } } ``` 在上述代码中,`staticMethod1` 和 `staticMethod2` 是同一个类的静态同步方法,它们使用的是同一把锁(类的 `class` 对象),因此它们之间是互斥的[^1]。 - **对象方法**:`synchronized` 作用在非静态方法上时,锁的是当前对象实例,即对象锁。不同对象实例调用各自的同步方法时,不会相互影响。示例如下: ```java public class SynchronizedExample { public synchronized void instanceMethod1() { // 非静态同步方法1 } public synchronized void instanceMethod2() { // 非静态同步方法2 } } ``` 这里的 `instanceMethod1` 和 `instanceMethod2` 是对象方法,锁的是调用该方法的对象实例。不同对象调用这两个方法不会产生互斥,因为它们使用的是不同对象的锁[^2]。 ### 锁的范围和互斥性 - **类方法**:所有静态同步方法互斥,无论是通过类调用还是对象调用。静态方法上加 `synchronized` 与在非静态方法内加 `synchronized(Xxx.class)` 效果一致,都是类锁。例如: ```java public class SynchronizedExample { public static synchronized void staticMethod() { // 静态同步方法 } public void nonStaticMethod() { synchronized (SynchronizedExample.class) { // 等同于静态同步方法的锁 } } } ``` 上述代码中,`staticMethod` 和 `nonStaticMethod` 里的同步块使用的是同一把类锁,它们之间会互斥[^2]。 - **对象方法**:对象锁仅锁当前对象,不同对象之间不会互斥。一个对象的静态方法和非静态方法,因为不是同一把锁,所以可以同时获取,不会冲突。例如: ```java public class SynchronizedExample { public static synchronized void staticMethod() { // 静态同步方法 } public synchronized void nonStaticMethod() { // 非静态同步方法 } } ``` 对于同一个对象,`staticMethod` 和 `nonStaticMethod` 可以同时执行,因为它们使用的不是同一把锁[^1]。 ### 初始化时的区别 不能用 `synchronized` 修饰方法外面的语句块(类语句块),会遇到编译错误。这涉及 Java 里对象初始化的知识,`synchronized` 锁住的是对象,初始化对象时,JVM 在对象初始化完成之前会调用方法外面的语句块,此时对象还不存在,所以不存在锁[^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值