synchronized同步方法
首先需要了解到:在方法内部的变量是线程安全的,不会存在非线程安全问题
实例变量非线程安全问题
例如:
class Test {
private int num;
public void change(String string) {
try {
if(string.equals("a")) {
num = 100;
System.out.println("a is ok");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b is ok");
}
System.out.println(string + "num = " + num);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
(注意,Test对象仅创建一个;如果创建多个Test对象,使线程与业务对象满足一对一则不会存在争抢)
当创建两个线程,分别调用该类的方法(先传入a,后传入b)num = 200 就会将之前的num = 100 覆盖造成非线程安全问题
解决: 在方法上使用该关键字–>同步方法 synchronized public void change(String string),只有一个执行完成后,另一个才可以进入执行
synchronized在字节码指令中的原理
在同步方法中使用synchronized关键字实现同步的原因是使用了flag标记ACC_SYNCHRONIZED,当调用方法时,会检查ACC_SYNCHRONIZED访问标志是否设置,如果设置了,执行线程时先持有同步锁,然后才会执行方法,执行完成后释放方法
如果使用synchronized代码块,则使用monitorenter和monitorexit指令进行同步处理
将synchronized方法与对象作为锁
1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
2)A线程先持有object对象的Lock锁,B线程此时调用对象的synchronized类型的方法,则需要等待,也就是同步
3)在方法声明处添加synchronized并不是锁方法,而是锁当前类的对象
4)java中只有“将对象作为锁”,并没有“锁方法”
5)java中,“锁”就是“对象”,“对象”可以映射成锁,哪个线程拿到这个锁就可以执行synchronized方法
6)如果在X对象中使用了synchronized关键字声明非静态方法,则X对象就被当成锁
脏读
发生原因:在读取实例变量时,此值已经被其他线程更改过了
例如:setValue(int a, int b)使用synchronized修饰,getValue()不修饰,那么getValue()可以任意调用,就可能存在脏读
synchronized锁重入
在使用synchronized时,当一个线程得到一个锁对象后,再次请求此对象锁是可以得到该对象锁的。
public class Service {
synchronized public void service1() {
System.out.println("service1");
service2();
}
synchronized public void service2() {
System.out.println("service2");
}
}
当调用Service的实例对象的service1()时,输出:
service1
service2
注意:锁重入支持继承环境,当存在父子类继承关系时,子类可以通过锁重入调用父类的同步方法(子类若不添加synchronized即为非同步方法,可任意调用)
出现异常,自动释放锁
当一个线程执行出现异常时会自动将持有的对象锁释放,其他线程可以正常获取锁进入同步方法。(调用sleep()和suspend()不释放锁)
synchronized同步代码块
可以将任意对象作为锁(synchronized (this)),多个线程调用同一个对象的同步方法会阻塞,调用不同对象的同步方法不会阻塞。(java对象的内存地址是否相同)
public void obj2() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
注意:
1)当多个线程同时执行synchronized (this){ }同步代码块时呈同步效果
2)当其他线程执行x对象中synchronized 同步方法时呈同步效果
3)当其他线程执行x对象里面的synchronized (this){ }代码块时呈同步效果
类Class的单例性
内存中只需存在一份Class对象
public class Student {
private String name;
private int age;
//getter and setter
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
System.out.println(s1.getClass() == s1.getClass());
System.out.println(s1.getClass() == s2.getClass());
System.out.println(s1.getClass() == s3.getClass());
}
}
结果为:
true
true
true
静态的同步方法和同步代码块
关键字synchronized应用在static静态方法上,就是对当前的 * .class 文件对应的Class类对象进行持锁,由上一个部分可知Class类对象是单例的,所以有:
1)静态同步方法可以对类的所有对象实例起作用
2)静态同步代码块可以对类的所有对象实例起作用
多线程死锁
不同的线程都在等待根本不可能被释放的锁,从而导致任务都无法完成
可以通过jdk自带工具检测:
1)进入jdk的bin目录下,打开命令窗口
2)执行jps,得到运行线程的id
3)执行jstack -l id,可以检测是否发生了死锁
volatile关键字
可见性:可以保证主内存和工作内存直接产生交互,进行读写操作,保证可见性
(synchronized通过写线程冲刷处理器缓存和读线程刷新处理器缓存保证可见性)
原子性:仅能保证变量写操作的原子性,不能保证读写操作的原子性,它提示线程每次从共享内存中去读取变量,而非从私有内存读取
(synchronized通过互斥来保障原子性,互斥是指一个锁一次只能被一个线程所持有,所以,临界区代码只能被一个线程执行,即保障了原子性)
有序性:禁止指令重排序(通过插入内存屏障),即volatile之前的代码不可以重排到volatile之后,volatile之后的代码不可以重排到volatile之前
(synchronized相似)