synchronized 同步,同步方法,同步代码块,同步实例
一、synchronized有啥用呢?
一句话,解决多线程中的并发问题。
具体在什么情况下使用到呢?
(1)资源共享
(2)多个线程同时操作同一个资源
当多个线程中出现以上两种情况的时候,被操作的共同资源则会混乱,出现非预期结果,
二、并发编程的原则
1. 原子性
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2. 可见性
简单来说,就是某些情况下,线程对于一些资源变量的修改并不会立马刷新到内存中,而是暂时存放在缓存,寄存器中。
这导致的最直接的问题就是,对共享变量的修改,另一个线程看不到。
Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。
3. 有序性
在Java内存模型中,允许编译器和处理器对指令进行重新排序,但是重新排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
我们在看看JVM关于synchronized的两条规定:
1)线程解锁前(退出synchronized代码块之前),必须把共享变量的最新值刷新到主内存中,也就是说线程退出synchronized代码块值后,主内存中保存的共享变量的值已经是最新的了
2)线程加锁时(进入synchronized代码块之后),将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
两者结合:线程解锁前对共享变量的修改在下次加锁时对其他线程可见
根据以上推出线程执行互斥代码的过程:
1>获得互斥锁(进入synchronized代码块)
2>清空工作内存
3>从主内存拷贝变量的最新副本到工作内存
4>执行代码
5>将更改后的共享变量的值刷新到主内存
6>释放互斥锁(退出synchronized代码块)
二、synchronized的三种应用方式
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
1、实例方法
锁的是当前实例对象 ,进入同步代码前要获得当前实例的锁,此作用域内的synchronized锁 ,可以防止多个线程同时访 问这个对象的synchronized方法。synchronized(this) 和 synchronized 方法一样,锁定的都是当前对象,所以如果已调用其中一个synchronized方法,则该对象中其他synchronized(this) 或者 synchronized 方法都会被阻塞,因为它们使用的是同一个对象锁
注意:
1)一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法,但是可以访问其他非同步的方法
2)不同对象实例的synchronized方法是不相干预的。也就是说,其它线程可以同时访问此类下的另一个对象实例中的synchronized方法;
使用举例:
public class MethodSync {
/*
* 测试 synchronized 修饰方法时锁定的是调用该方法的对象
*/
public synchronized void method(String name){
System.out.println(name + " Start a sync method");
try{
Thread.sleep(300);
}catch(InterruptedException e){
}
System.out.println(name + " End the sync method");
}
}
2、静态同步方法
锁的是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
此作用域下,可以防止多个线程同时访问这个类中的synchronized方法。也就是说此种修饰,可以对此类的所有对象实例起作用。
使用举例:
public synchronized static void doLongTimeTaskA() {}
3、同步代码块
锁的是括号里面的对象,给指定对象加锁,进入同步代码库前要获得给定对象的锁。这个地方是跟第一种 实例方法锁 是一样的,锁的都是对象,但是代码块更灵活
使用实例:
public class Task {
public void doLongTimeTask() throws InterruptedException {
for (int i = 0; i < 100; i++) {
System.out.println("nosynchronized threadName=" + Thread.currentThread().getName() + " i=" + (i + 1));
Thread.sleep(100);
}
System.out.println("-----------------------");
synchronized (this) {
for (int i = 0; i < 100; i++) {
System.out.println("synchronized threadName=" + Thread.currentThread().getName() + " i=" + (i + 1));
Thread.sleep(100);
}
}
}
}
注意:
这里synchronized (this) 包起来的代码块是同步的,这个方法里的其他内容是异步的。所以如果同时有多个线程执行
doLongTimeTask() ,则会看到同步代码块里的内容会被阻塞,这个方法里的其他内容则会异步执行