Synchronized性质:可重入、不可中断
Synchronized的作用
- 同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的
- 能够保证在同一时刻 最多只有一个线程执行该段代码,以达到保证并发安全的效果
Synchronized的地位
- Synchronized是Java的关键字,被Java语言原生支持
- 是最基本的护持同步手段
- 是并发编程中的元老级角色,是并发编程的必学内容
不用并发手段会发生什么后果
代码实战:两个线程同时a++,最后结果会比预计的少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public class DisappearRequest1 implements Runnable{ static DisappearRequest1 instance = new DisappearRequest1(); static int count = 0;
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance);
t1.start(); t2.start(); t1.join(); t2.join();
System.out.println(count); }
@Override public void run() { for (int j = 0; j < 100000; j++) { count++; } } }
|
运行结果
1 2 3 4
| 第一次运行:140623 第二次运行:142235 第三次运行:125453 第四次运行:186434
|
理应的值应该是20000,但为什么实际结果不一致?
我们来看具体步骤
分析:
t1运行
线程t1读取到count=9,它可能因为一些原因阻塞了。
t2运行
t2读取到count=9,它使count++,但使没有写入内存,t2阻塞
t1继续运行
因为t2改变的count的值,但是没有将它写入内存,所以t1读到的还是9
t1将count=10写入内存,t2也将count=10写入内存
综上所诉就是我们所说的:线程不安全;
synchronized的两个用法:第一个用法
对象锁
包括 方法锁 (默认锁对象为this当前实例对象)和同步代码块锁 (自己指定锁对象)
类锁
指synchronized修饰 静态 的方法或指定锁为 Class对象
对象锁实例
- 代码块形式:手动指定锁对象
- 方法锁形式:synchronized修饰普通方法,锁对象默认为this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class SynchronizedObjectCodeBlock2 implements Runnable{
static SynchronizedObjectCodeBlock2 instance=new SynchronizedObjectCodeBlock2(); @Override public void run() {
synchronized (this) { System.out.println("我是对象锁的代码块形式" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束 "); } }
public static void main(String[] args) { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); while (t1.isAlive()||t2.isAlive()){
} System.out.println("finished"); } }
|
1 2 3 4 5 6 7
| 运行结果:
我是对象锁的代码块形式Thread-0 Thread-0 运行结束 我是对象锁的代码块形式Thread-1 Thread-1 运行结束 finished
|
将代码修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| Object lock1=new Object(); Object lock2=new Object();
@Override public void run() {
synchronized (lock1) { System.out.println("我是lock1" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " lock1运行结束 "); } synchronized (lock2) { System.out.println("我是lock2" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " lock2运行结束 "); } }
|
运行结果
1 2 3 4 5 6 7 8 9
| 我是lock1Thread-0 Thread-0 lock1运行结束 我是lock2Thread-0 我是lock1Thread-1 Thread-0 lock2运行结束 Thread-1 lock1运行结束 我是lock2Thread-1 Thread-1 lock2运行结束 finished
|
这里不难看出两个线程时并行的。
当吧lock2改为lock1是,则是顺序运行的,代码就不赘述了。
1 2 3 4 5 6 7 8 9
| 我是lock1Thread-0 Thread-0 lock1运行结束 我是lock1Thread-0 Thread-0 lock1运行结束 我是lock1Thread-1 Thread-1 lock1运行结束 我是lock1Thread-1 Thread-1 lock1运行结束 finished
|
注意一哈 idea的调试,超级好用
方法锁实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
public class SynchronizedObjectMethod3 implements Runnable{
static SynchronizedObjectMethod3 instance=new SynchronizedObjectMethod3();
public static void main(String[] args) { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start(); t2.start(); while (t1.isAlive()||t2.isAlive()){
} System.out.println("Finished"); } @Override public void run() {
method(); }
public synchronized void method(){ System.out.println("我是对象锁的方法修饰符形式,我叫"+Thread.currentThread().getName());
try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("运行结束"); } }
|
1 2 3 4 5
| 我是对象锁的方法修饰符形式,我叫Thread-0 运行结束 我是对象锁的方法修饰符形式,我叫Thread-1 运行结束 Finished
|
总结:
- 代码块形式:手动指定锁对象
- 方法锁形式:synchronized修饰普通方法,锁对象默认为this
synchronized的两个用法:第二个用法:类锁
- 概念(重要):Java类可能有很多对象,但只有1个Class对象
- 本质:所以所谓的类锁,不过是Class对象的锁而已。(概念而已,帮助我们理解所谓的类锁)
- 用法和效果:类锁只能在同一时刻被一个对象拥有。
- 形式1: synchronized加在static方法上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
public class SynchronizedClassStatic4 implements Runnable { static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4(); static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
@Override public void run() { method();
}
public synchronized void method() { System.out.println("我是类锁的第一种形式static,我叫 " + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束"); }
public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()) {
} System.out.println("Finished"); } }
|
1 2 3 4 5
| 我是类锁的第一种形式static,我叫 Thread-1 我是类锁的第一种形式static,我叫 Thread-0 Thread-1 运行结束 Thread-0 运行结束 Finished
|
这是没有给method方法加static情况的运行结果,可以看出两个线程,是并行的
加上static后
1 2 3 4 5 6 7 8 9
| public static synchronized void method() { System.out.println("我是类锁的第一种形式static,我叫 " + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 运行结束"); }
|
1 2 3 4 5
| 我是类锁的第一种形式static,我叫 Thread-0 Thread-0 运行结束 我是类锁的第一种形式static,我叫 Thread-1 Thread-1 运行结束 Finished
|
- 形式2:synchronized(*.class)代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import java.util.TreeMap;
public class SynchronizedClassClass5 implements Runnable {
static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5(); static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();
@Override public void run() {
try { method(); } catch (InterruptedException e) { e.printStackTrace(); } }
private void method() throws InterruptedException { synchronized (SynchronizedClassClass5.class) {
System.out.println("我是类锁的第二种形式:synchronized(*.class).我叫 " + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 运行结束");
}
}
public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()) {
} System.out.println("Finished");
} }
|
1 2 3 4 5
| 我是类锁的第二种形式:synchronized(*.class).我叫 Thread-1 Thread-1 运行结束 我是类锁的第二种形式:synchronized(*.class).我叫 Thread-0 Thread-0 运行结束 Finished
|
可以将 synchronizedClassClass5.class 改成this ,线程就还会是并行的
可见 不管类有多少个实例,只要类被锁上了,你所有的线程都要一个一个的串行运行
原理:加解锁原理、可重入原理、可见性原理
Synchronized的缺陷:效率低、不够灵活、无法预判是否成功获取到锁
如何选择Lock或Synchronized