Synchronized三种用法

本文详细介绍了Java中线程同步的三种方式:实例方法、静态方法和同步代码块,通过示例代码解释了它们的工作原理和线程安全问题。对于实例方法,锁是当前实例;对于静态方法,锁是类的class对象;同步代码块则可以指定任意对象作为锁,用于精确控制同步范围。通过理解这些概念,可以更好地解决多线程环境中的数据一致性问题。
摘要由CSDN通过智能技术生成

java中的线程同步锁可以是任意对象。

三种用法:

1.作用于实例方法。当前实例枷锁,进入同步代码前要获取当前实例的锁。

2.作用于静态方法。当前类加锁,进入同步代码前要获取当前类对象的锁。

3.作用于代码块。需要指定加锁的对象,对所给的对象加锁,进入同步代码前要获取当前对象的锁。

一.作用于实例方法

     

public class ThreadTest extends Thread{

    int count = 0;
    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+count++);
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest1 = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        Thread thread1 = new Thread(threadTest1,"thread1");
        Thread thread2 = new Thread(threadTest1,"thread2");
        thread1.start();
        thread2.start();
    }
}

thread1----0
thread1----1
thread1----2
thread1----3
thread1----4
thread2----5
thread2----6
thread2----7
thread2----8
thread2----9

         代码中开启了两个线程去操作一个共享变量count,count++是先读取值再写值。如果第一个线程执行这个过程,第二个线程拿到之前的count值,那么就造成线程不安全了。所以在run方法上面加上synchronized,获取对象锁,代码中的实例对象就是threadTest1。

        当一个线程正在访问一个对象synchronized实例方法时,别的线程时访问不了的,当然也访问不 了该对象的synchronized方法,但是可以访问其他未加锁的方法。

         线程thread1访问的实例对象threadTest1的synchronized方法(当前对象锁是threadTest1),线程thread2访问的实例对象threadTest2的synchronized方法(当前对象锁是threadTest1),这样是允许的,因为两个实例对象不同,此时如果线程访问非共享数据,线程是安全的,如果两个线程访问的是共享数据,那么线程就不安全了。

public class ThreadTest extends Thread{

    int count = 0;
    @SneakyThrows
    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+count++);
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest1 = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        Thread thread1 = new Thread(threadTest1,"thread1");
        Thread thread2 = new Thread(threadTest2,"thread2");
        thread1.start();
        thread2.start();
    }
}

thread1----0
thread1----1
thread1----2
thread1----3
thread1----4
thread2----0
thread2----1
thread2----2
thread2----3
thread2----4

         两个线程同时去拿共享变量count。但是new了两个实例,这就意味着存在两个实例锁,thread1和thread2分别进入了threadTest1和threadTest2的实例锁,当然线程就不安全了。

二. 作用于静态方法

         静态方法不属于当前实例,而是属性类的,那么这个锁就是类的class对象锁。

public class ThreadTest extends Thread{

    static int count = 0;
    @Override
    public synchronized void run() {
        increaseCount();
    }

    private synchronized static void increaseCount(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+count++);
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest1 = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        Thread thread1 = new Thread(threadTest1,"thread1");
        Thread thread2 = new Thread(threadTest2,"thread2");
        thread1.start();
        thread2.start();
    }
}

thread1----0
thread1----1
thread1----2
thread1----3
thread1----4
thread2----5
thread2----6
thread2----7
thread2----8
thread2----9

         同样new了两个不同实例,缺线程安全了。静态方法不属于当前实例,而是属于类,所以这个方法里其实就用了同一把锁,这个锁就是类的class对象锁。

三.作用于代码块

        使用场景:在某些情况下,我们编写的方法体可能很大,同时存在一些耗时的操作,而需要同步的代码只有一小部分,如果直接对整个方法进行同步操作,性能会降低。此时我们可以使用同步代码块对需要同步的代码包裹,这样就不需要对整个方法进行同步操作了。所以它的操作范围为synchronized(object){}的这个大括号里。 

 

public class ThreadTest extends Thread{

    static int count = 0;
    @Override
    public void run() {
        increaseCount();
    }

    private void increaseCount(){
        synchronized (this){
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+"----"+count++);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest1 = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        Thread thread1 = new Thread(threadTest1,"thread1");
        Thread thread2 = new Thread(threadTest2,"thread2");
        thread1.start();
        thread2.start();
    }
}

thread1----0
thread2----0
thread2----1
thread1----1
thread1----2
thread2----3
thread2----4
thread1----5
thread2----6
thread1----7

        从输出结果来看,线程不安全,因为我们指定锁为this,指向的是调用这个方法的实例对象。我们new了两个不同的对象threadTest1和threadTest2,所以有两把锁。,分别进入自己传入的对象锁的线程执行increaseCount方法,所以线程不安全。如果想保证线程安全,则在synchronized后面括号写入ThreadTest.class,表示这个类作为锁,这样尽管不同的实例对象调用该方法,线程也是安全的。

总结:

 1.修饰普通方法(实例方法),一个对象中的加锁方法只能允许一个线程访问,但是要注意这种情况下锁的是该方法的实例对象,如果多个线程不同对象访问该方法,则线程不安全。

2.修饰静态方法,由于静态方法是类方法,所以这种情况下锁的是包含这个方法的类,也就是类class对象,如果多个线程不同对象访问该静态方法,也可以保证线程安全。

3.修饰代码块,其中普通代码块参数可以是类的一个属性也可以是当前对象,它的同步效果和修饰普通方法一样,如果代码块参数是当前class类对象,则于修饰静态方法类似。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值