多线程中synchronized关键字总结?

1.为什么需要这个关键字?

1.现在可以多个线程对同一片存储空间进行访问,这时存储空间里面的数据叫做共享数据。线程并发给我们带来效率的同时,也带了一些数据安全性的问题,数据安全性是一个很严重的问题,多个线程同时访问同一片数据区,很有可能把里面的数据弄的混乱。 所以Java语言提供了专门机制以解决这种数据安全性问题,有效避免了同一个数据对象被多个线程同时访问,从而导致数据的错乱的问题。
2.我们知道多线程可以很好的利用cpu资源,加快用户的响应时间,但是它也是一把双刃剑,在给我们带来这么多好处的同时又会带来相应的问题,就比如共享变量的访问问题,保证了线程对变量的可见性和排他性;【可见性:线程A修改了共享变量x,线程B则马上可以读到修改后的值;】【排他性:共享变量在任意时刻只能被一个线程访问;】

2.用在何处?

1.对象锁:线程A拿到了某个类的锁,这样此时A访问的那个synchronized方法,其他线程是无法访问的,并且这个类的其他加了synchronized修饰的方法都不能被其他线程访问的,因为synchronized此时锁住的是这个类的一个实例对象,所以对象下的所有方法都会被锁住,不过如果新new一个实例是可以被其他线程访问的。

//同步方法的写法
public synchronized void test(){}

//同步代码块的写法
public void test(){
    synchronized(this){
        System.out.println("Test");
    }
}

2.类锁

//同步方法的写法
public static synchronized  void incCount() {
            count++;
    }

//同步代码块的写法[SynTest是类名]
public  void incCount2() {
        synchronized (SynTest.class) {
            count++;
        }
    }

总结来说:当修饰静态方法的时候,锁定的是当前类的Class对象。
当修饰非静态方法的时候,锁定的是当前实例对象this。

3.注意点?

1.脏读:
原因:对写方法加锁,但是读方法未加锁;假设线程A调用写方法没去修改某个共享变量的值,由于此时写方法是加锁的,所以线程B无法访问,但是读方法未加锁,所以无需等待锁的释放,很可能在线程A还未将值刷新到主内存中就被线程B读取了,此时的数据为脏数据。

public class Test1 {
    private String name;
    private double balance;
    public synchronized void set(String name, double balance) {
        this.name = name;
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public double getBalance() {
        return this.balance;
    }

    public static void main(String[] args) throws InterruptedException {
        Test1 test = new Test1();
        //先是写方法,虽然加锁,但是对于读没有加锁,所以其并不会等待写的锁释放才能调用
        new Thread(() -> {
            test.set("zhangsna",100.0);
        }).start();
        //此时还没有设置完成,所以读取到的并不是正确的数据
        Optional.of(test.getBalance()).ifPresent(System.out::println);
        TimeUnit.SECONDS.sleep(2);
        Optional.of(test.getBalance()).ifPresent(System.out::println);
    }
}

2.synchronized是可重入锁:

public class Xttblog extends SuperXttblog {
    public static void main(String[] args) {
        Xttblog child = new Xttblog();
        child.doSomething();
    }
 
    public synchronized void doSomething() {
        System.out.println("child.doSomething()" + Thread.currentThread().getName());
        doAnotherThing(); // 调用自己类中其他的synchronized方法
    }
 
    private synchronized void doAnotherThing() {
        super.doSomething(); // 调用父类的synchronized方法
        System.out.println("child.doAnotherThing()" + Thread.currentThread().getName());
    }
}
 
class SuperXttblog {
    public synchronized void doSomething() {
        System.out.println("father.doSomething()" + Thread.currentThread().getName());
    }
}

证明了synchronized是可重入锁:这里的对象锁只有一个,就是 child 对象的锁,当执行 child.doSomething 时,该线程获得 child 对象的锁,在 doSomething 方法内执行 doAnotherThing 时再次请求child对象的锁,因为synchronized 是重入锁,所以可以得到该锁,继续在 doAnotherThing 里执行父类的 doSomething 方法时第三次请求 child 对象的锁,同样可得到。如果不是重入锁的话,那这后面这两次请求锁将会被一直阻塞,从而导致死锁。

可重入锁的实现原理:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

3.synchronized对于异常处理:
对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误,比如你现在执行一个队列任务,很多对象都去在等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕,就释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意,在编写代码的时候,一定要考虑周全。

4.锁对象的引用不能修改(核心):

public class Test4 {
    /**
     对于作为锁对象,对象的属性可以修改,但是对象的引用不能再被修改,
     所以一般作为锁对象,都需要将其设置为final。
     */
    Object o = new Object();
    public void m1() {
        synchronized (o) {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test4 test = new Test4();
        new Thread(() -> test.m1(),"t1").start();
        TimeUnit.SECONDS.sleep(2);
        //修改变量指向新的对象
        test.o = new Object();
        //如果不修改o引用,t2线程将只要t1不释放锁,将无法获取锁
        //但是将o指向另一个对象,使得t1的锁对象和t2的锁对象不是同一个
        new Thread(() -> test.m1(),"t2").start();
    }
}

注意:一般尽量不要使用字符串去当对象锁,容易产生死锁

private static final String Lock = "LOCK";
public void m2() {
    /**
      因为不同包不同类中的值相同的字符串常量引用的是同一个字符串对象。
      这就意味着外部任何的Class都可以包含指向同一个字符串对象的字符串常量,
      因此就有可能出现死锁的情况!
      一旦出现这种情况的死锁,是极难排查出来的。
     */
    synchronized (Lock) {
    }
}

5.synchronized不能锁Integer类型的对象:
原因分析:在对该代码进行反编译的时候,发现每次进行i++操作的时候,会调用Integer的一个valueOf的静态方法,都会new一个新的Integer对象返回,所以导致i每次计算完后都是不同的对象,回归synchoronized的本质,是一个内置的对象锁,被锁的必须是同一个对象才可在多线程中安全运行。

    private static class Worker implements Runnable{

        private Integer i;
        private Object o = new Object();

        public Worker(Integer i) {
            this.i=i;
        }

        @Override
        public void run() {
            synchronized (i) {
                Thread thread=Thread.currentThread();
                System.out.println(thread.getName()+"--@"
                        +System.identityHashCode(i));
                i++;
                System.out.println(thread.getName()+"-------"+i+"-@"
                        +System.identityHashCode(i));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName()+"-------"+i+"--@"
                        +System.identityHashCode(i));
            }

        }

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值