Java之同步锁synchronized实例讲解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/QQB67G8COM/article/details/89632424

线程安全概念:当多个线程访问一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。
1.可以在任意对象及方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"
2.一般状况下,关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当作锁。


3.在静态方法上加上synchronized关键字,表示锁定.class类(或者
synchronized(xxx.class){
//代码块
}
),同时一间段内只能一个线程访问该类的所有对象。


4.synchronized(同步)和Asynchronized(异步)的问题:
概念:异步双方不需要共同的时钟,也就是接收方不知道发送方什么时候发送,所以在发送的信息中就要有提示接收方开始接收的信息,如开始位,同时在结束时有停止位。
多线程环境下,加synchronized关键字就是同步,不加synchronized就是异步,并发执行就是异步的体现。
例子:

public class AsyncAndSync{
	public synchronized void method1(){
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	//synchronized
	public void method2(){
		System.out.println(Thread.currentThread().getName());
	}

	/**
	 * t1线程先持有AsyncAndSync对象的锁,此时正在执行method1方法,如果method2没有被添加
	 * 关键字synchronized,那么t2线程就会同步和t1线程执行,因为访问ass对象中的method2方
	 * 法不需要锁,t2线程直接访问就可以了。如果method2方法也添加了关键字synchronized,那么t1和t2
	 * 就会顺序执行,因为t1已经获取了对象锁了,方法锁等同对象锁,对象锁是针对成员变量或成员
	 * 方法而进行锁定的,没有被synchronized修饰的成员变量或方法是不会被对象锁锁定的,同一个
	 * 对象同一时间段内只能有一把锁。
	 */
	public static void main(String[] args){
		final AsyncAndSync ass = new AsyncAndSync();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				ass.method1();
			}
		},"t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				ass.method2();
			}
		},"t2");
		
		t1.start();
		t2.start();
	}
}
显示结果:method2不加锁:t1和t2同时输出,method2加锁:t1和t2先后输出

5.如果是静态方法加上synchronized关键字就会变成类锁,无论有多少个对象进行访问,都得串行执行。


6.脏读 实例:
//业务整体需要使用完整的synchronized,保持业务的原子性。

public class DirtyRead {
	private String username = "ljj";
	private String password = "123456";
	
	public synchronized void setValue(String username, String password){
		this.username = username;
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
		this.password = password;
		System.out.println("setValue最终结果:username = " + username + " , password = " + password);
	}

	//synchronized
	public void getValue(){
		System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
	}
	
	/**
	 * 在getValue()没有使用synchronized关键字的时候,这样执行会导致数据脏读,t1线程在设置新值
	 * 的时候,整个设置过程需要2s,而在1s后main线程就开始读取数值了,此时读取的数值为对象属性的
	 * 旧数值,则为脏读。getValue()方法加上synchronized则课解决此问题
	 * 
	public static void main(String[] args) throws Exception{
		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("ljq", "obj");		
			}
		});
		t1.start();
		Thread.sleep(1000);
		dr.getValue();
	}
}

数据一致性读 Oracle数据库中规避脏读的手段的实例:
在这里插入图片描述


7.最简单的同步锁重入和死锁:

public class SyncReentry {

	public synchronized void method1(){
		System.out.println("method1..");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		method2();
	}
	public synchronized void method2(){
		System.out.println("method2..");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		method3();
	}
	public synchronized void method3(){
		System.out.println("method3..");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 锁的重入,如果在method3中调用method1就会形成最简单的死锁,
	 * 在设计程序的时候应该避免双方同时持有锁的状况
	 */
	public static void main(String[] args) {
		final SyncReentry sr = new SyncReentry();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				sr.method1();
			}
		});
		t1.start();
	}
}

8.锁的重入2:子类方法被加锁后调用父类方法,父类方法同样要加锁,不然保证不了线程的安全性。


9.锁对象改变问题:在对象锁中,不要改变字符串对象的值,当锁对象被改变了,也就是锁改变了,当前线程的锁就会被释放,加入是t1线程执行以下的代码块,t1在获取锁后将锁的对象改变就会导致锁对象被释放,t2线程就会获取锁,锁的对象为t1线程改变后的对象。
private String lock = “lock”;
synchronized (lock) {
lock = “change lock”;
}


10.同一个类中,不同的方法,不同的锁->只有一个对象被三个线程分别调用:

public class ObjectLock {
	public void method1(){
		synchronized (this) {	//对象锁
			...
		}
	}
	
	public void method2(){		//类锁
		synchronized (ObjectLock.class) {
			...
		}
	}
	
	private Object lock = new Object();
	public void method3(){		//任意对象锁
		synchronized (lock) {
			...
		}
	}
	
	public static void main(String[] args) {
		
		final ObjectLock objLock = new ObjectLock();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method1();
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method2();
			}
		});
		Thread t3 = new Thread(new Runnable() {
			@Override
			public void run() {
				objLock.method3();
			}
		});
		t1.start();
		t2.start();
		t3.start();	
	}
}
这种状况就是三个线程分别获得三把不同的锁,执行顺序不固定。

11.尽量减小锁的粒度,提高程序的性能,类锁是最耗性能的,对象锁其次,可以的话尽量将锁用在代码块身上,如下例,假设t1和t2线程先后调用Particle实例化的一个对象,假设synchronized锁住的是方法,t2就会等待t1释放锁才能进到方法体内,假如是按照如下的锁住块代码体,t2就会在a位置等待t1释放锁,事先就执行了一部分不需要锁锁住的代码,整个程序的运行速度等待了提升,性能得到了提高。
public class Particle{
void timeTask(){

//a位置
synchronized(this){

}
//b位置

}
}


12.不要用字符串常量作为锁,应该通过new String(“字符串常量”)的方式来,常量池的东西是全局的,任何一个地方出现了相同的代码,很容易导致问题,通过new 的方式就可以规避该类问题。另一方面,可能存在竞争不合法的问题,并不是程序不正确的问题。
synchronized (new String(“字符串常量”)) {…}

猜你喜欢

转载自blog.csdn.net/QQB67G8COM/article/details/89632424