1. synchronized的三种用法和区别
synchronized的三种应用方式:
1.作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
2.作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
3.作用于代码块,这需要指定加锁的对象,对所给的指定对象加锁,进入同步代码前 要获得指定对象的锁。
synchronized的三种应用方式
1.synchronized修饰实例方法(普通方法)
使用时,作用范围为整个函数,这里所谓的实例锁就是调用该实例方法(不包括静态方法)的对象。
public class SyncTest implements Runnable{
//共享资源变量
int count = 0;
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
increaseCount();
System.out.println(Thread.currentThread().getName()+":"+count++);
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
// SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1,"thread1");
Thread thread2 = new Thread(syncTest1, "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
*/
2.synchronized修饰静态方法
当创建两个对象创建两个线程时,都加的有锁,但是仍会出错
eg:
public class SyncTest implements Runnable{
//共享资源变量
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) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1,"thread1");
Thread thread2 = new Thread(syncTest2, "thread2");
thread1.start();
thread2.start();
}
/**
* 输出结果
thread1:0
thread2:0
thread1:1
thread2:1
thread1:2
thread2:2
thread1:3
thread2:3
thread1:4
thread2:4
*/
}
这个时候我们就要使用synchronized修饰静态方法来解决
public class SyncTest implements Runnable {
//共享资源变量
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++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1, "thread1");
Thread thread2 = new Thread(syncTest2, "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
*/
}
3.synchronized修饰代码块
在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。所以他的作用范围为synchronizd(obj){}的这个大括号中
public class SyncTest implements Runnable {
//共享资源变量
static int count = 0;
private byte[] mBytes = new byte[0];
@Override
public synchronized void run() {
increaseCount();
}
private void increaseCount() {
//假设省略了其他操作的代码。
//……………………
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1, "thread1");
Thread thread2 = new Thread(syncTest2, "thread2");
thread1.start();
thread2.start();
}
/**
* 输出结果
thread1:0
thread2:0
thread1:1
thread2:2
thread2:4
thread1:3
thread2:5
thread1:5
thread2:7
thread1:6
*/
}
总结:
1.修饰普通方法 一个对象中的加锁方法只允许一个线程访问。但要注意这种情况下锁的是访问该方法的实例对象, 如果多个线程不同对象访问该方法,则无法保证同步。
2.修饰静态方法 由于静态方法是类方法, 所以这种情况下锁的是包含这个方法的类,也就是类对象;这样如果多个线程不同对象访问该静态方法,也是可以保证同步的。
3.修饰代码块 其中普通代码块 如Synchronized(obj) 这里的obj 可以为类中的一个属性、也可以是当前的对象,它的同步效果和修饰普通方法一样;Synchronized方法 (obj.class)静态代码块它的同步效果和修饰静态方法类似。
2.synchronized和lock的区别?
主要相同点:Lock 能完成synchronized所实现的所有功能
主要不同点:Lock有比 synchronized更精确的线程语义和更好的性能。synchronized 会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally 从句中释放。
3.synchronized实现原理?
前提:每个对象都有一个对象头,对象头中有一个和synchronized相关的字段,该字段是一个指向monitor对象的指针,synchronized通过获取monitor对象的方式获取锁。(可以去这个链接查看一下更加详细的解释链接)
synchronized修饰代码块的底层原理
依赖于monitor对象的enter和exit操作,对应于monitorenter和monitorexit字节码指令,这两个指令分别指明了同步代码块的起始和结束位置,执行monitorenter时尝试获取对monitor的持有权,如果此时monitor的计数器为0,则获取到这个锁,并将计数器设置为1。
synchronized修饰方法的底层原理
方法级的同步是隐式的,不需要通过字节码指令来控制,JVM通过方法常量池中的方发表结构中的ACC_SYNCHRONIZED访问标志区分一个方法是否是同步方法,然后根据标志位的设置区获取monitor对象。
4.CAS和BAB?
4.1 简介
CAS原子操作是一条CPU指令,是解决多线程并发过程中加锁操作带来的性能损耗的一种方式 。
每个CAS原子操作都有三个相关的参数,分别是V/E/N,V表示要更新的变量,E表示期望的变量的值,N表示要修改成为的值。如果发现要更新的变量的值等于E则更新,否则不更新,可以直接退出,也可以执行若干个空循环后再判断是否满足要求。
4.2 优点
保证数据原子性,保证线程安全,代替加锁的性能损耗 ;
自旋锁的基础。
4.3 缺点
ABA问题,可以使用带有时间戳的对象引用或布尔类型的原子操作解决
循环时间长开销大的问题
只能保证一个共享变量的原子操作,可以通过原子操作类将多个变量合并为一个对象解决