SynChronized
官方定义
防止线程看绕和内存一致性的错误,如果一个对象对多个线程课件,则该对象的变量的所有读取或写入方法都是通过同步方法来完成的。
作用
能保证在同一时刻最多只有一个线程执行该段代码,以达到并发安全的效果。
地位
◆ Java的关键字,被Java原生支持
◆ 最基本的互斥同步手段
◆ 并发编程中元老级别角色
不使用的后果
通过一个小Demo体现
public class Threadtest implements Runnable{
static Threadtest instance = new Threadtest();
static int i=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(i);
}
public void run() {
for (int j=0;j<10000;j++){
i++;
}
}
}
最终输出的计算结果少于预期值20000
Synchronized的两个用法
作为对象锁使用
{
◇ 方法锁(默认锁对象是this当前实例对象)
◇ 同步代码块锁(自动锁定对象)
}
作为类锁使用
{
◇ 静态方法锁
◇ Class对象
}
类锁补充:只能在同一时刻被一个对象拥有,一个Java类可能有很多对象,但只有一个Class对象
Synchronized的性质
◇ 可重入
指的是同一线程的外层函数获得锁喉,内层函数可以直接再次获取该锁。又称递归锁
好处:
避免死锁
提升封装性
何为死锁? 既想拿到外部的新锁,又不释放本身的锁,造成永久等待的现象。
◇ 不可中断
一旦这个锁被别人获得,如果自己本身还想获得,只能选择等待或者阻塞,直到别的线程释放掉这个锁,如果不释放,那将永远等待。
缺陷
◆ 效率低
锁的释放情况少[代码块执行结束、抛异常、同步对象的wait方法],识图获得锁时不能设定超时时间,不能中断一个正在试图获取锁的线程
◆ 不够灵活
很难掌握加锁解锁的时机
以读写锁为例,读数据时不加锁,写数据时才加
◆ 无法知道是否成功拿到锁
针对这些缺陷,Lock锁应运而生
可以主动设置加锁解锁、设置超时时间等
Synchronized的原理
加锁和释放锁的底层是通过JVM字节码的monitor实现的
可重入:通过加锁次数计时器来实现
线程拿到锁后,计数器+1,当相同线程在此对象上再次获得该锁时,再+1。任务离开时,计数-1。JVM负责跟踪对象被加锁的次数。
Synchronized如何保证可见性?
一旦代码块或方法被Synchronized修饰,在执行完毕后,被锁住的对象做的任何修改,在锁的释放前,都要从线程内存写回到主内存中,不会存在线程内存和主线程内存不一致的情况。由此保证可见性。
使用Synchronized注意点:
如何选择Synchronized和lock关键字?
- 在有线程包的情况下优先使用包
- 优先使用Synchronized,好处是减少代码量
- 在特有情况下需要用到lock独有特性时,使用lock
多线程访问同步方法的7种情况
◇ 两个线程同时访问一个对象的同步方法
public class Demo1 implements Runnable {
static Demo1 instance = new Demo1 ();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("-----finished-----");
}
public void run() {
method();
}
public synchronized void method(){
System.out.println("我是对象锁的方法修饰符形式,我叫"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
结果
我是对象锁的方法修饰符形式,我叫Thread-0
Thread-0运行结束
我是对象锁的方法修饰符形式,我叫Thread-1
Thread-1运行结束
-----finished-----
解析
因为拿到的是同一把锁[对象锁里的方法锁]
故线程安全
◇ 两个线程访问的是两个对象的同步方法
public class Demo2 implements Runnable{
static Demo2 instance1 = new Demo2();
static Demo2 instance2= new Demo2();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("-----finished-----");
}
public void run() {
Synchronized(this){
System.out.println("我是对象锁的代码块形式,我叫"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
}
结果
我是对象锁的方法修饰符形式,我叫Thread-0
我是对象锁的代码块形式,我叫Thread-1
Thread-1运行结束
Thread-0运行结束
-----finished-----
解析
因为他们的采用锁对象不是同一个,所以线程互不干扰。
◇ 两个线程访问的是Synchronized的静态方法
public class SynchronizedClassStatic4 implements Runnable{
static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
static SynchronizedClassStatic4 instance2= new SynchronizedClassStatic4();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("-----finished-----");
}
public void run() {
method();
}
public static synchronized void method(){
System.out.println("我是类锁的第一种形式,static,我叫"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("finished");
}
}
结果
我是类锁的第一种形式,static,我叫Thread-0
finished
我是类锁的第一种形式,static,我叫Thread-1
finished
-----finished-----
解析
即使是两个实例,但是由于方法是静态方法,所以实际上隶属于同一把类锁,线程安全。
◇ 同时访问同步方法和非同步方法
public class Method2Difference implements Runnable{
static Method2Difference instance = new Method2Difference();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("-----finished-----");
}
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1() {
System.out.println("我是加锁的方法1 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
public void method2(){
System.out.println("我是没加锁的方法2 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
}
结果
我是加锁的方法1 我叫Thread-0
我是没加锁的方法2 我叫Thread-1
Thread-0运行完毕
Thread-1运行完毕
-----finished-----
解析
Synchronized只作用于指定方法中,其他没加修饰符的方法不受影响。
◇ 访问同一个对象的不同的普通同步方法
public class DifferentMethod implements Runnable{
static DifferentMethod instance = new DifferentMethod();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("-----finished-----");
}
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1() {
System.out.println("我是加锁的方法1 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
public synchronized void method2(){
System.out.println("我是加锁的方法2 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
}
结果
我是加锁的方法1 我叫Thread-0
Thread-0运行完毕
我是加锁的方法2 我叫Thread-1
Thread-1运行完毕
-----finished-----
解析
同一个实例,这两个方法拿到的锁(this)是一样的,故没法同时执行。
◇ 同时访问静态Synchronized和非静态Synchronized方法
public class Method2Difference implements Runnable{
static Method2Difference instance = new Method2Difference();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("-----finished-----");
}
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized static void method1() {
System.out.println("我是静态加锁的方法1 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
public synchronized void method2(){
System.out.println("我是非静态加锁的方法2 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
}
结果
我是非静态加锁的方法2 我叫Thread-1
我是静态加锁的方法1 我叫Thread-0
Thread-0运行完毕
Thread-1运行完毕
-----finished-----
解析
他们拿到的锁不一样,一个类锁,一个对象锁
◇ 方法抛出异常后,会释放锁
public class ExceptionMethod implements Runnable{
static ExceptionMethod instance = new ExceptionMethod ();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
while (t1.isAlive() || t2.isAlive()){
}
System.out.println("-----finished-----");
}
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")){
method1();
}else {
method2();
}
}
public synchronized void method1() {
System.out.println("我是抛异常的方法1 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
throw new Exception ();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
public synchronized void method2(){
System.out.println("我是加锁的方法2 我叫" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行完毕");
}
}
真正的异常不该用Exception,而是用RunTimeException,加在catch后面
结果
我是抛异常的方法1 我叫Thread-0
java.lang.Exception
at newnew.ExceptionMethod.method1(ExceptionMethod.java:31)
at newnew.ExceptionMethod.run(ExceptionMethod.java:21)
at java.lang.Thread.run(Thread.java:748)
Thread-0运行完毕
我是加锁的方法2 我叫Thread-1
Thread-1运行完毕
-----finished-----