一般认为Synchronized锁定的是这段代码块,但事实上,Synchronized锁定的是当前对象。不仅如此Synchronized锁定的是heap内存中的这个对象而不是这个对象引用。
一般我们不使用字符串常量来作为锁对象,因为这样会使得线程莫名的阻塞。看起来是两个字符串的引用但是他们指向的是同一段的内存。例如有个lib的jar包,我们看不到源码,但是它方法种锁定了“hello”这个字符串,我们在自己的代码中也锁定了“hello”,效果就很美妙,进程会一直等待。
关于线程安全:加synchronized关键字之后不一定能实现线程安全,具体还要看锁定的对象是否唯一。堆内存中的地址是否一样
锁定的对象有两种情况:①类的实例 ②类的字节码(.class)
synchronized关键字修饰普通方法等同于synchronized(this)
synchronize关键字修饰静态方法锁定的是类的.class文件,静态方法中synchronize锁定代码块,锁定的对象不能是类的实例,只能是类的.class文件。原理如同在静态方法中不能直接调用非静态方法。类的.class文件是唯一的,所以说synchronize修饰静态方法或者锁定的对象是类的.class文件的时候,在多线程中是可以实现线程安全的
1.同步方法和非同步方法是可以同时调用的
2.对业务写方法加锁,同时也要对业务读方法加锁,否则容易产生脏读问题
3.一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁,也就是说synchronized获得的锁是可重入的(这里是继承中有可能发生的情形,子类调用父类的同步方法)
程序在执行过程中,如果出现异常,默认情况锁会被释放
锁定某对象o,如果o的属性发生改变,不影响锁的使用
但是如果o变成另外一个对象,则锁定的对象发生改变
应该避免将锁定对象的引用变成另外一个对象
1.业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
2.采用细粒度的锁,可以是线程争用时间变短,从而提高效率
线程通信通常:一种是读取共享内存,还有一种就是线程之间互相通信。Java的线程采用的是读取共享内存。
volatile 关键字,使一个变量在多个线程间可见
A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
使用volatile关键字,会让所有线程都会读到变量的修改值
使用volatile,将会强制所有线程都去堆内存中读取running的值
volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
synchronize可以保证可见性和原子性,volatile只能保证可见性
++/--操作(其他操作)不是一个原子操作,所以在需要保证原子的++/--时可以通过上锁来解决问题,也可以通过
AtomXXX类来解决问题
AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); //count++
使用门闩机制
使用Latch(门闩)替代wait notify来进行通知
好处是通信方式简单,同时也可以指定等待时间
使用await和countdown方法替代wait和notify
CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了
这时应该考虑countdownlatch/cyclicbarrier/semaphore
首先new一个门闩CountDownLatch latch = new CountDownLatch(1);
里面有一个参数,同时他有一个方法就是latch.await();
它会插在代码之间阻拦代码的执行。同时latch.countDown();
方法会减少门闩的数量当门闩的数量减少为0的时候,这时门会自动打开,这时候latch.await();会向下执行。
整个过程不涉及锁的机制,高效得实现了线程之间的通信。
@author mashibing
学习来自哔哩哔哩,马士兵老师高并发编程
网友评论