在多线程的并发情况下,提供的线程安全的解决方案,提供了线程同步的方式
Synchronized的使用
关键字Synchronized的使用可以修改代码块或者同步方法,确保多个线程在同一时刻,只能有一个线程处于方法或者同步代码块中,保证线程的可见性和互斥性
//修饰代码块
public void test1(Object obj) {
synchronized (obj){
//do something
}
}
Synchronized锁住的是某个obj的实例,加锁在代码块中
//修饰普通方法
public synchronized void test2() {
//do something
}
//修饰普通方法
public synchronized void test2() {
//do something
}
Synchronized加在普通方法上,锁住的是当前的对象实例
//修饰静态方法
public static synchronized void test3() {
//do something
}
Synchronized加在静态代码块中,锁住的是当前的class实例,因为class数据在方法区,因此静态方法锁住的来的全局锁
Synchronized的特点
Synchronize修饰的方法或者代码块,在同一时刻JVM只允许一个线程进入执行
Synchronized的锁机制可以做到在多线程中的原子性,有序性,可见性
● 确保代码的同步执行,即不同线程间互斥
● 确保对共享变量的修改能够及时可见(可见性)
● 有效解决指定重排序(顺序性)
统计1秒中计数结果,Synchronized版实现
private static volatile boolean flag = true;
public static synchronized boolean getFlag(){
return TestThread.flag;
}
public static synchronized void setFlag(boolean flag){
TestThread.flag = flag;
}
public static void main(String[] args) throws IOException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Integer i = 0;
while (TestThread.getFlag()) {
// System.out.println(i++);
i++;
}
System.out.println("线程1统计结束:"+i);
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改标志位
TestThread.setFlag(false);
System.out.println("线程2睡眠结束");
}
});
thread.start();
thread1.start();
}
Synchronize的原理
Synchronize修饰的代码块或者是方法,在字节码层面研究其实现
对于同步代码块实现是通过monitorenter和monitorexit指令,而同步方法的修饰上是用到ACC_SYNCHRONIZED来完成
以上两种形式,无论采用那种形式,本质上是获取一个对象的监视器(Monitor)进行获取,而获取是排他的,也就是同一时刻只能有一个线程来获取Synchronized的对象的监视器
synchronized允许使用任何的一个对象作为同步的内容,因此任意一个对象都应该拥有自己的监视器(monitor),当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。
从上图中我们可以看到,任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。