synchronized关键字修饰方法案例解析

synchronized在java多线程编程中是一个常见的也是相对轻量级的关键字,能够保证执行操作的原子性,有序性,以及可靠性,从而保证了并发安全。这篇文章主要讲解synchronized修饰方法时,synchronized是如何保证线程的安全的。

synchronized修饰普通方法时,两个线程对象能否同时调用呢?示例代码:

public class MultiThread {
    private Integer num = 0;

    public synchronized void printNum(String str) {
        if ("a".equals(str)) {
            num = 100;
            System.out.println("tag " + str + ", set num over");
        } else {
            num = 200;
            System.out.println("tag " + str + ", set num over");
        }
        System.out.println("tag " + str + ", num = " + num);
    }

    public static void main(String[] args) {
        MultiThread m1 = new MultiThread();
        MultiThread m2 = new MultiThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                m1.printNum("a");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m2.printNum("b");
            }
        });
        t1.start();
        t2.start();
    }
}

这里synchronized修饰的时普通方法,看一下测试结果:

tag a, set num over
tag b, set num over
tag a, num = 100
tag b, num = 200

 这里说明了两个线程对象是可以同时进入到这个方法的,那么synchronized关键字在这里锁的是什么,下面我们再看一个例子,用synchronized关键字来修饰static方法,示例代码:

public class MultiThread {
    private static Integer num = 0;

    public static synchronized void printNum(String str) throws InterruptedException {
        if ("a".equals(str)) {
            num = 100;
            System.out.println("tag " + str + ", set num over");
            sleep(1000);
        } else {
            num = 200;
            System.out.println("tag " + str + ", set num over");
            sleep(1000);
        }
        System.out.println("tag " + str + ", num = " + num);
    }

    public static void main(String[] args) {
        MultiThread m1 = new MultiThread();
        MultiThread m2 = new MultiThread();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    m1.printNum("a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    m2.printNum("b");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
    }
}

 为了效果明显一些,我们这里让其休眠一秒,来看一下测试结果:

tag b, set num over
tag b, num = 200
tag a, set num over
tag a, num = 100

 这里可以看出synchronized修饰static方法时,两个线程对象没有同时进入到这个方法里。这里来解释一下,synchronized关键字修饰普通方法时,取得的锁是对象锁,而不是把一段代码或者方法当作锁,哪个线程先执行synchronized关键字的方法,那个线程就持有该方法所属对象的锁,两个对象同时执行,那么线程获得的就是两个不同的锁,它们之间是互不影响的,但是synchronized关键字修饰static方法时,代表锁定的时当前这个.class类,这是类级别的锁,此时两个对象执行就是互斥的,必须要等其中一个执行完毕释放锁,另一个对象才能执行。

当一个类中两个方法同时被synchronized修饰,同一个对象,能够同时调用这两个方法呢?示例代码:

public class MultiThread2 {
    private Integer num = 0;
    public synchronized void method1() {
        num = 1;
        System.out.println(Thread.currentThread().getName() + " , num = " + num);
        try {
            sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void method2() {
        num = 2;
        System.out.println(Thread.currentThread().getName() + " , num = " + num);
         try {
            sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MultiThread2 mt = new MultiThread2();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                mt.method1();
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                mt.method2();
            }
        },"t2");
        t1.start();
        t2.start();
    }
}

来看一下测试结果,这里为了区别,每个方法都休眠了3秒钟。

t2 , num = 2
t1 , num = 2

这里线程t1在执行完method1()后,等待了3秒,线程t2才执行method2()方法,可以看出,同一个对象在访问多个synchronized关键字修饰的方法时,线程间不是同时执行的,必须等待该线程释放对象锁,hashtable以及vector中方法都被synchronized关键字修饰,也是利用这种方式,保证了并发场景下的线程安全。

接下来我们看一下synchronized的可重入的特性,示例代码:

public class MultiThread3 {
    private Integer num = 0;
    public synchronized void method1(){
        num++;
        System.out.println("num = " + num);
        method2();
    }

    public synchronized void method2() {
        num++;
        System.out.println("num = " + num);
        method3();
    }

    public synchronized void method3() {
        num++;
        System.out.println("num = " + num);
    }

    public static void main(String[] args) {
        MultiThread3 mt = new MultiThread3();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                mt.method1();
            }
        });
        t1.start();
    }
}

测试结果:

num = 1
num = 2
num = 3

 这里即使每个方法都用synchronized所修饰,在方法间的相互调用上,也是可以重新获得锁的,而不会像我们想象中发生死锁情况,接下来再来看一个例子:

public class MainThread {
    protected Integer num = 10;

    public synchronized void printMainNum(){
        num--;
        System.out.println("main print num : " + num);
        try {
            sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class SubThread extends MainThread{
    public synchronized void printSubNum(){
        while (num > 0) {
            num--;
            System.out.println("sub print num : " + num);
            printMainNum();
        }
    }

    public static void main(String[] args) {
        SubThread sub = new SubThread();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                sub.printSubNum();
            }
        });
        thread.start();
    }
}

这里subThread继承了父类MainThread,同时子类方法中调用了父类的方法,来看一下测试结果:

sub print num : 9
main print num : 8
sub print num : 7
main print num : 6
sub print num : 5
main print num : 4
sub print num : 3
main print num : 2
sub print num : 1
main print num : 0

这里同样,子类在调用父类方法时,父类也可以拿到锁,这里就要提到synchronized的可重入的特性。synchronized就是一种重入锁,重入锁是为每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法,当某个线程请求成功后,JVM会记下该锁的持有线程,并且将计数器置为1,此时其它线程请求该锁,则必须等待;而如果同一个线程再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增,当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁。所以以上两个示例是不会发生死锁的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值