1.1、Synchronized关键字简介
2.1、Synchronized的地位
2.2、不使用并发手段的后果
演示代码:
/**
*
* i++ 虽然是一行代码,但是实际上至少包含了一下三个动作
* 1,读取 i的值
* 2,计算 i 的值
* 3,把 i+1 的计算结果写回到内存中
*/
public class ShowUnsafe1 implements Runnable {
static ShowUnsafe1 r = new ShowUnsafe1();
static int i = 0;
@Override
public void run() {
for (int j = 0; j < 100000; j++) {
i++;
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(r);
Thread thread2 = new Thread(r);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main i =" + i);
}
}
两个线程进行10万次i++操作,最后结果小于20万。
3.1、Synchronized的两个用法
对象锁
包括方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)
类锁
指synchronized修饰静态的方法或指定锁为Class对象。
第一个用法:对象锁
代码演示:对象锁的形式1-同步代码块
/**
* synchronized 作用:能够保证在同一时刻最多只有一个线程执行该段代码,以保证并发安全的效果。
* synchronized 可重入
*
*/
public class SynchronizedObjectCodeBlock2 implements Runnable{
private static SynchronizedObjectCodeBlock2 instance1 = new SynchronizedObjectCodeBlock2();
private static SynchronizedObjectCodeBlock2 instance2 = new SynchronizedObjectCodeBlock2();
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()+"运行结束");
}
synchronized (lock2){
System.out.println("我是lock2对象锁的代码块的形式。我叫:"+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 thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance1);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive() ){
}
System.out.println("main finished");
}
}
结果:
我是lock1对象锁的代码块的形式。我叫:Thread-0
Thread-0运行结束
我是lock1对象锁的代码块的形式。我叫:Thread-1
我是lock2对象锁的代码块的形式。我叫:Thread-0
Thread-1运行结束
Thread-0运行结束
我是lock2对象锁的代码块的形式。我叫:Thread-1
Thread-1运行结束
main finished
代码演示:对象锁的形式2-普通方法锁
/**
* 对象锁示例 方法锁
*/
public class SynchronizedObjectMethod3 implements Runnable{
static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();
@Override
public void run() {
method();
}
private synchronized void method() {
System.out.println("我是 method 对象锁的代码块的形式。我叫:"+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();
System.out.println("main");
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("main finish");
}
}
结果:
main
我是 method 对象锁的代码块的形式。我叫:Thread-0
Thread-0运行结束
我是 method 对象锁的代码块的形式。我叫:Thread-1
Thread-1运行结束
main finish
第二个用法:类锁
概念(重要):Java类可能有很多个对象,但只有1个Class对象。
形式1:synchronized加在static方法上
形式2:synchronized(*.class)代码块
代码演示:类锁的形式1-静态方法锁
/**
* 类锁
*/
public class SynchronizedClassStatic4 implements Runnable{
private static synchronized void method(){
System.out.println("我是 method 类锁 我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("main finish");
}
}
结果:
我是 method 类锁 我叫:Thread-0
Thread-0运行结束
我是 method 类锁 我叫:Thread-1
Thread-1运行结束
main finish
代码演示:类锁的形式2-*.class
/**
* 类锁
*/
public class SynchronizedClass5 implements Runnable{
private void method(){
synchronized (SynchronizedClass5.class){
System.out.println("我是 *.class 类锁 我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
SynchronizedClass5 instance1 = new SynchronizedClass5();
SynchronizedClass5 instance2 = new SynchronizedClass5();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("main finish");
}
}
结果:
我是 *.class 类锁 我叫:Thread-0
Thread-0运行结束
我是 *.class 类锁 我叫:Thread-1
Thread-1运行结束
main finish
4.1、多线程访问同步方法的7种常见情况
1.两个线程同时访问一个对象的同步方法(可以实现同步效果)
/**
* 对象锁示例 方法锁
*/
public class SynchronizedObjectMethod3 implements Runnable{
static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();
@Override
public void run() {
method();
}
private synchronized void method() {
System.out.println("我是 method 对象锁的代码块的形式。我叫:"+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();
System.out.println("main");
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("main finish");
}
}
结果:
main
我是 method 对象锁的代码块的形式。我叫:Thread-0
Thread-0运行结束
我是 method 对象锁的代码块的形式。我叫:Thread-1
Thread-1运行结束
main finish
2.两个线程同时访问两个对象的同步方法(起不到同步的效果)
/**
* synchronized 作用:能够保证在同一时刻最多只有一个线程执行该段代码,以保证并发安全的效果。
* synchronized 可重入
*
*/
public class SynchronizedObjectCodeBlock2 implements Runnable{
private static SynchronizedObjectCodeBlock2 instance1 = new SynchronizedObjectCodeBlock2();
private static SynchronizedObjectCodeBlock2 instance2 = new SynchronizedObjectCodeBlock2();
@Override
public void run() {
synchronized (this){
System.out.println("我是lock1对象锁的代码块的形式。我叫:"+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 thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive() ){
}
System.out.println("main finished");
}
}
结果:
我是lock1对象锁的代码块的形式。我叫:Thread-0
我是lock1对象锁的代码块的形式。我叫:Thread-1
Thread-1运行结束
Thread-0运行结束
main finished
3.两个线程访问的是synchronized的静态方法(可以实现同步效果)
/**
* 类锁
*/
public class SynchronizedClassStatic4 implements Runnable{
private static synchronized void method(){
System.out.println("我是 method 类锁 我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("main finish");
}
}
结果:
我是 method 类锁 我叫:Thread-0
Thread-0运行结束
我是 method 类锁 我叫:Thread-1
Thread-1运行结束
main finish
4.同时访问同步方法与非同步方法(同步方法依然是最多只能被一个线程调用,非同步方法没有任何限制,谁来调用都可以,而且可以同时运行。)
/**
* 同时访问同步方法和非同步方法
*/
public class SynchronizedYesAndNo6 implements Runnable {
static SynchronizedYesAndNo6 instance = new SynchronizedYesAndNo6();
private synchronized void method1() {
System.out.println("我是 同步方法 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
private void method2() {
System.out.println("我是 普通方法 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
@Override
public void run() {
if(Thread.currentThread().getName().endsWith("Thread-0")){
method1();
}else {
method2();
}
}
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("main : finish");
}
}
结果:
我是 同步方法 的形式。我叫:Thread-0
我是 普通方法 的形式。我叫:Thread-1
Thread-0运行结束
Thread-1运行结束
main : finish
5.访问同一个对象的不同的普通同步方法(可以实现同步)
/**
* 访问同一个类的不同的普通同步方法
*/
public class SynchronizedDifferentMethod7 implements Runnable {
private static SynchronizedDifferentMethod7 instance = new SynchronizedDifferentMethod7();
private synchronized void method1() {
System.out.println("我是 同步方法1的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
private synchronized void method2() {
System.out.println("我是 同步方法2 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
@Override
public void run() {
if(Thread.currentThread().getName().endsWith("Thread-0")){
method1();
}else {
method2();
}
}
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("main finish");
}
}
结果:
我是 同步方法1的形式。我叫:Thread-0
Thread-0运行结束
我是 同步方法2 的形式。我叫:Thread-1
Thread-1运行结束
main finish
6.同时访问静态synchronized和非静态synchronized方法(起不到同步作用,因为静态synchronized的.class类锁和非静态synchronized方法的对象锁是不同的锁。)
public class SynchronizedAndNormal8 implements Runnable{
public static SynchronizedAndNormal8 instance = new SynchronizedAndNormal8();
private static synchronized void method1() {
System.out.println("我是 static 锁 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
private synchronized void method2() {
System.out.println("我是 同步方法 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
@Override
public void run() {
if(Thread.currentThread().getName().endsWith("Thread-0")){
method1();
}else {
method2();
}
}
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("main finish");
}
}
结果:
我是 同步方法 的形式。我叫:Thread-1
我是 static 锁 的形式。我叫:Thread-0
Thread-0运行结束
Thread-1运行结束
main finish
7.方法抛出异常后,会释放锁。
public class SynchronizedException9 implements Runnable{
public static SynchronizedException9 instance = new SynchronizedException9();
private synchronized void method1() {
System.out.println("我是 method1 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
throw new RuntimeException("抛出异常");
}
private synchronized void method2() {
System.out.println("我是 method2 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
@Override
public void run() {
if(Thread.currentThread().getName().endsWith("Thread-0")){
method1();
}else {
method2();
}
}
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("main finish");
}
}
结果:
我是 method1 的形式。我叫:Thread-0
Thread-0运行结束
我是 method2 的形式。我叫:Thread-1
Exception in thread "Thread-0" java.lang.RuntimeException: 抛出异常
at com.lvxiaosha.synchronized_practice.SynchronizedException9.method1(SynchronizedException9.java:17)
at com.lvxiaosha.synchronized_practice.SynchronizedException9.run(SynchronizedException9.java:33)
at java.base/java.lang.Thread.run(Thread.java:833)
Thread-1运行结束
main finish
Process finished with exit code 0
5.1、Synchronized的性质-可重入性质
- 可重入
- 不可中断
什么是可重入?
指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。
好处
避免死锁、提升封装性
可重入代码演示1:
/**
* 证明 synchronized 可重入的
*/
public class SynchronizedRecursion10 implements Runnable{
public static SynchronizedRecursion10 instance = new SynchronizedRecursion10();
int a = 0;
private synchronized void method1() {
System.out.println("我是 method1 a = "+a);
if(a == 0){
a++;
method1();
}
}
private synchronized void method2() {
System.out.println("我是 method2 的形式。我叫:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
@Override
public void run() {
if(Thread.currentThread().getName().endsWith("Thread-0")){
method1();
}else {
method2();
}
}
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("main finish");
}
}
运行结果:
我是 method1 a = 0
我是 method1 a = 1
我是 method2 的形式。我叫:Thread-1
Thread-1运行结束
main finish
可重入代码演示2:
/**
* 证明 synchronized 可重入的
*/
public class SynchronizedRecursion11 implements Runnable{
public static SynchronizedRecursion11 instance = new SynchronizedRecursion11();
private synchronized void method1() {
System.out.println("我是 method1 : "+ Thread.currentThread().getName());
method2();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private synchronized void method2() {
System.out.println("我是 method2 我叫:"+Thread.currentThread().getName());
}
@Override
public void run() {
if(Thread.currentThread().getName().endsWith("Thread-0")){
method1();
}else {
method2();
}
}
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("main finish");
}
}
运行结果:
我是 method1 : Thread-0
我是 method2 我叫:Thread-0
我是 method2 我叫:Thread-1
main finish
性质:不可中断
一旦这个锁已经被别人获得了,如果我还想获得,只能选择等待或者阻塞,知道别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远地等下去。
6.1、深入原理
加锁和释放锁的原理:看字节码
- 获取和释放锁的时机:进入和退出同步代码块(包括抛出异常)
- 等价代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* synchronized 能保证可见性
* 用 lock 形式表现
* synchronized 效率低 锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获得锁的线程。
* synchronized 不能中断。
* synchronized 不够灵活。
* 无法知道是否成功了获取了锁。
* 优先使用 synchronized 其次在使用lock
*/
public class SynchronizedToLock13 implements Runnable{
Lock lock = new ReentrantLock();
public static SynchronizedToLock13 instance = new SynchronizedToLock13();
private synchronized void method1() {
System.out.println("我是 synchronized : "+ Thread.currentThread().getName());
}
private void method2() {
lock.lock();
try{
System.out.println("我是 lock 我叫:"+Thread.currentThread().getName());
}finally {
lock.unlock();
}
}
@Override
public void run() {
if(Thread.currentThread().getName().endsWith("Thread-0")){
method1();
}else {
method2();
}
}
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("main finish");
}
}
结果:
我是 synchronized : Thread-0
我是 lock 我叫:Thread-1
main finish
看monitor指令
可以看到有1个monitorenter,表示获取锁,还有2个monitorexit,表示释放锁,其中1个是正常释放锁,另外1个表示抛出异常后释放锁。
可重入原理:加锁次数计数器
- JVM会记录被加锁的次数
- 第一次加锁时,次数从0变成1,之后如果再次加锁,就从1变成2,以此类推。
- 退出一层同步代码块时,计数减一,当计数为0的时候,代表锁释放。
保证可见性的原理:内存模型
- 一个线程执行的结果,另外的线程不一定可见
- 线程1操作x=5,之后线程2可能读取x=3
- synchronized可以保证可见性
7.1、Synchronized的缺陷
- 效率低:锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程。
- 不够灵活(读写锁更灵活):加锁和释放锁的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。
- 无法知道是否成功的获取到锁
使用注意点:锁的范围不宜过大、避免锁的嵌套。