Java并发编程(3)——synchronized

本文详细介绍了Java中的synchronized关键字如何实现线程同步,包括修饰方法、静态方法和代码块的不同应用场景,并通过具体示例展示了其锁定机制的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Java中可以使用synchronized关键字来修饰方法、静态方法和代码块,synchronized能够隐式的获取和释放锁,从而保证在同一时刻,只有一个线程在方法或代码块中。

public class SynchronizedDemo {

    public static int num = 0;

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        new Thread(new DemoRunnable(demo), "T1").start();
        new Thread(new DemoRunnable(demo), "T2").start();
    }

    public synchronized void test(){
        for(int i=0; i<3; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" num = "+num++);
        }
    }

    static class DemoRunnable implements Runnable{

        private SynchronizedDemo demo;

        public DemoRunnable(SynchronizedDemo demo){
            this.demo = demo;
        }

        @Override
        public void run() {
            demo.test();
        }

    }
}

对test()使用synchronized修饰时,输出为:

T1 num = 0
T1 num = 1
T1 num = 2
T1 num = 3
T1 num = 4
T2 num = 5
T2 num = 6
T2 num = 7
T2 num = 8
T2 num = 9

当去掉synchronized时,输出为:

T2 num = 1
T1 num = 0
T1 num = 2
T2 num = 3
T1 num = 5
T2 num = 4
T1 num = 6
T2 num = 6
T2 num = 7
T1 num = 7

当synchronized修饰方法时,锁的对象是类的当前实例。
我们对上面的代码中的test()进行改写:

public synchronized void test(){
    System.out.println(Thread.currentThread().getName()+" START time= "+new Date());
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+" END   time= "+new Date());        
}

两个线程使用同一个 demo 对象,查看输出可知只有等待其中一个线程结束后,另一个线程才执行test():

public static void main(String[] args) {
    SynchronizedDemo demo = new SynchronizedDemo();
    new Thread(new DemoRunnable(demo), "T1").start();
    new Thread(new DemoRunnable(demo), "T2").start();
}   

输出为:

T1 START time= Mon Aug 14 13:02:40 CST 2017
T1 END   time= Mon Aug 14 13:02:42 CST 2017
T2 START time= Mon Aug 14 13:02:42 CST 2017
T2 END   time= Mon Aug 14 13:02:44 CST 2017

两个线程分别使用一个 demo对象,则两个线程的开始和结束互不影响:

public static void main(String[] args) {
    new Thread(new DemoRunnable(new SynchronizedDemo()), "T1").start();
    new Thread(new DemoRunnable(new SynchronizedDemo()), "T2").start();
}

输出为:

T1 START time= Mon Aug 14 12:58:58 CST 2017
T2 START time= Mon Aug 14 12:58:58 CST 2017
T2 END   time= Mon Aug 14 12:59:00 CST 2017
T1 END   time= Mon Aug 14 12:59:00 CST 2017

当synchronized修饰静态方法时,锁的对象是当前类的class对象
我们将上面的test() 改为静态方法,然后执行上面的测试代码,发现两个线程先后执行。
两种测试方法输出都为:

T1 START time= Mon Aug 14 13:04:06 CST 2017
T1 END   time= Mon Aug 14 13:04:08 CST 2017
T2 START time= Mon Aug 14 13:04:08 CST 2017
T2 END   time= Mon Aug 14 13:04:10 CST 2017

当synchronized修饰代码块时,锁的对象是指定的对象。
修改上面的test():

public Object lock = new Object();

public static void main(String[] args) {
    SynchronizedDemo demo = new SynchronizedDemo();
    new Thread(new DemoRunnable(demo), "T1").start();
    new Thread(new DemoRunnable(demo), "T2").start();
}

public void test(){
    System.out.println(Thread.currentThread().getName()+" START time= "+new Date());

    synchronized(lock){
        System.out.println(Thread.currentThread().getName()+" code block START time= "+new Date());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" code block end   time= "+new Date());
    }

    System.out.println(Thread.currentThread().getName()+" END   time= "+new Date());        
}

输出结果为:

T2 START time= Mon Aug 14 15:07:26 CST 2017
T2 code block START time= Mon Aug 14 15:07:26 CST 2017
T1 START time= Mon Aug 14 15:07:26 CST 2017
T2 code block end   time= Mon Aug 14 15:07:28 CST 2017
T1 code block START time= Mon Aug 14 15:07:28 CST 2017
T2 END   time= Mon Aug 14 15:07:28 CST 2017
T1 code block end   time= Mon Aug 14 15:07:30 CST 2017
T1 END   time= Mon Aug 14 15:07:30 CST 2017

由输出可以看出对于同步代码块,同一时间只能有一个线程访问。

由synchronized修饰的同步块,如果抛出异常,则锁自动释放。
修改上面的test方法:

public static void main(String[] args) throws InterruptedException {
        SynchronizedDemo demo = new SynchronizedDemo();
        new Thread(new DemoRunnable(demo), "T1").start();
        Thread.sleep(50);//延时创建线程2,使线程1先执行
        new Thread(new DemoRunnable(demo), "T2").start();
    }

    public void test(){
        System.out.println(Thread.currentThread().getName()+" START time= "+new Date());

        synchronized(lock){
            System.out.println(Thread.currentThread().getName()+" code block START time= "+new Date());
            try {
                //使线程1抛出异常
                if("T1".equals(Thread.currentThread().getName())){
                    throw new RuntimeException();
                }
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" code block end   time= "+new Date());
        }

        System.out.println(Thread.currentThread().getName()+" END   time= "+new Date());        
    }

由输出结果可以看出,线程T1抛出异常,没有继续执行,而线程T2直接获取了锁。

synchroniced同步块对于同一个线程来说是可重入的,不回出现自己把自己锁死的问题。
下面的例子说明了这个问题:

public class SynchronizedReentrantTest {

    public static void main(String[] args) {
        test();
    }

    public static synchronized void test(){
        System.out.println("abc");
        demo();
        System.out.println("ghi");
    }

    public static synchronized void demo(){
        System.out.println("def");
    }
}

输出结果:

abc
def
ghi

在代码中,test方法已经持有了class对象的锁,在没有释放的情况下,依然执行了demo方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值