欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java并发编程学习(八) synchronized详解

程序员文章站 2022-06-04 14:21:00
...

      synchronized实现同步的基础:java中的每一个对象都可以作为锁,具体表现为三种形式

1>  对于普通的同步方法,锁是当前实例

2>  对于静态同步方法,锁是当前类的class对象

3>  对于同步方法块, 锁是synchronized ,synchronize(obj) {} ,obj 是锁对象

     当一个线程试图访问同步代码块时候,它首先必须得到锁,退出或者抛出异常必须释放锁。

从jvm中得知synchronized的实现原理,jvm是基于进入和退出monitor来实现方法同步和代码块同步的。在多线程并发中,非线程安全存在实例变量和静态变量的访问,方法中的变量不会存在线程安全问题

   1. synchronize同步方法

    多个线程修改同一个实例变量,存在交叉,就是线程不安全

public class ParentDemo {
	private int i = 0;
	public void test(){
		if(Thread.currentThread().getName().equals("A")){
			i = 100;
			System.out.println("A set over");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			i = 200;
			System.out.println("B set over ");
			
		}
		System.out.println("Thread name "+Thread.currentThread().getName()+",i="+i);
	}
	public static void main(String[] args) {
		final ParentDemo d = new ParentDemo();
		Thread t = new Thread(new Runnable() {
			
			@Override
			public void run() {
				d.test();
			}
		},"A");
		
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				d.test();
			}
		},"B");
		
		t.start();
		t1.start();
	}
}
方法前,加上synchronize,保证任意时刻只有一个线程执行方法

      每个线程都有自己独立的栈空间,如果线程间需要通信,就需要借助jmm内存模型,构建线程间的happens-before原则,实现共享变量的内存可见性。

     synchronized作用:修饰方法和同步块来使用,确保多个线程在同一个时刻,只能有一个线程在同步块和方法中,保证线程访问的可见性和排他性

    synchronized方法的锁是对象的锁

线程锁的是对象,synchronized方法一定是排队执行的,只有共享资源的访问需要同步,不是共享资源,没有同步的必要。
public class ParentDemo {
	private int i = 0;
	public synchronized void test(){
		if(Thread.currentThread().getName().equals("A")){
			i = 100;
			System.out.println("A set over");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			i = 200;
			System.out.println("B set over ");
			
		}
		System.out.println("Thread name "+Thread.currentThread().getName()+",i="+i);
	}
	public static void main(String[] args) {
		final ParentDemo d = new ParentDemo();
		Thread t = new Thread(new Runnable() {
			
			@Override
			public void run() {
				d.test();
			}
		},"A");
		
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				d.test();
			}
		},"B");
		
		t.start();
		t1.start();
	}
}  

 synchronized中的脏读问题

使用synchronized在赋值的时候进行了同步,但是如果取值的时候,可能出现被其他线程改过的情况,就会出现脏读问题.
public class PublicVar {
	private String username="A";
	private String password="AA";
	synchronized public void setValue(String username,String password){
		this.username = username;
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.password = password;
		System.out.println(Thread.currentThread().getName()+" set value username = "+username+",password="+password);
	}
	public void getValue (){
		System.out.println(Thread.currentThread().getName()+" get value username =  "+this.username+",password="+this.password);
	}
	public static void main(String[] args) {
		final  PublicVar pv = new PublicVar();
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				pv.setValue("B", "BB");
			}
		},"B");
		t.start();
		try {
			Thread.sleep(20);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		pv.getValue();
	}
}
执行结果:
main get value username =  B,password=AA
B set value username = B,password=BB
主线程读取到的值,部分值,已经被线程B改过出现了脏读,解决脏读方案,在getvalue上加上synchronized

关于synchronized还需要知道几点:
1. 线程A调用对象的synchronized方法时候,因为是获取对象的锁,其他线程调用同一个对象的其他synchronized方法,必须等到前面的线程释放锁,才能获取锁继续执行。、
2. 线程A调用对象的synchronized方法,线程B可以调用同一个对象的非synchronized方法不用等待,因为不用去获取锁。
   synchronized的重入锁特性

线程访问一个对象的synchronized方法块中,可以再次调用本类的其他synchronized方法,也就是再次请求这个对象锁,是可行的,也就是自己能够再次获取自己的内部锁,可重入锁也支持父子类继承的环境中。

   

/**
 * 重入锁特性:调用本类的synchronize方法
 * @author zhouy
 *
 */
public class ParentDemo {
	public synchronized void service1(){
		System.out.println("service1");
		service2();
	}
	public synchronized void service2(){
		System.out.println("service2");
	}
	public static void main(String[] args) {
		ParentDemo d = new ParentDemo();
		d.service1();
	}
}

说明: 一个线程执行service1时候,在去获取service2时候,此时对象的锁还没有释放,当再次取得这个对象的锁,

还是可以获取到,并不会死锁,说明锁是可以重入的

/**
 * 重入锁:父子继承中的重入锁
 * @author zhouy
 *
 */
public class Main {
	public int i = 10;
	synchronized public void operateTMainMethod(){
		try {
			i--;
			System.out.println("main print i = "+i);
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		Sub sub = new Sub();
		sub.operateIsSubMethod();
	}
}
class Sub extends Main{
	synchronized public void operateIsSubMethod(){
		try{
			while(i>0){
				i--;
				System.out.println("sub print i = "+i);
				Thread.sleep(1000);
				operateTMainMethod();
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

说明:当存在父子继承关系时,子类完全可以通过"重入锁"调用父类的同步方法

      出现异常,锁自动释放   

public class Service {
	synchronized public void testMethod(){
		if(Thread.currentThread().getName().equals("a")){
			System.out.println("ThreadName = "+Thread.currentThread().getName()+" run beginTime = "+
		System.currentTimeMillis());
			int i=1;
			while(i==1){
				if((Math.random()+"").substring(0, 8).equals("0.123456")){
					System.out.println("ThreadName = "+Thread.currentThread().getName()+"exception  run beginTime = "+System.currentTimeMillis());
					Integer.parseInt("a");
				}
			}
		}else{
			System.out.println("ThreadB run Time = "+System.currentTimeMillis());
		}
	}
	public static void main(String[] args) {
		Service service = new Service();
		Thread1 t1 =new Thread1(service);
		t1.setName("a");
		t1.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		Thread2 t2 = new Thread2(service);
		t2.setName("b");
		t2.start();
	}
}
class Thread1 extends Thread{
	private Service service;
	public Thread1(Service service){
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.testMethod();
	}
}
class Thread2 extends Thread{
	private Service service;
	public Thread2(Service service){
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.testMethod();
	}
}
打印:
ThreadName = a run beginTime = 1514860827406
ThreadName = aexception  run beginTime = 1514860827797
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:492)
	at java.lang.Integer.parseInt(Integer.java:527)
	at cn.itcast.test4.Service.testMethod(Service.java:12)
	at cn.itcast.test4.Thread1.run(Service.java:43)
ThreadB run Time = 1514860828407
说明: 线程a抛出异常,锁释放,线程b拿到锁继续执行

      同步不具有继承性

/**
 * 同步不可以继承
 * @author zhouy
 *
 */
public class Main {
	synchronized public void serviceMethod(){
		System.out.println("in main 下一步 sleep begin threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("in main 下一步 sleep end threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
	}
	public static void main(String[] args) {
		 final Sub sub = new Sub();
		Thread A = new Thread(new Runnable() {
			@Override
			public void run() {
				sub.serviceMethod();
			}
		},"A");
		A.start();
		Thread B = new Thread(new Runnable() {
			@Override
			public void run() {
				sub.serviceMethod();
			}
		},"B");
		B.start();
	}
}
class Sub extends Main{
	public void serviceMethod (){
		System.out.println("in sub 下一步 sleep begin threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("in sub 下一步 sleep end threadname = "+Thread.currentThread().getName()+" time = "+System.currentTimeMillis());
		super.serviceMethod();
	}
}

执行结果: sub中打印 sub的 输出是异步的,只有main是同步的,说明同步并没有继承
in sub 下一步 sleep begin threadname = A time = 1514862243251
in sub 下一步 sleep begin threadname = B time = 1514862243251
in sub 下一步 sleep end threadname = B time = 1514862244257
in main 下一步 sleep begin threadname = B time = 1514862244257
in sub 下一步 sleep end threadname = A time = 1514862244257
in main 下一步 sleep end threadname = B time = 1514862245272
in main 下一步 sleep begin threadname = A time = 1514862245272
in main 下一步 sleep end threadname = A time = 1514862246275

2.synchronized同步语句块

synchronized方法的弊端

    如果线程A调用同步方法执行长时间的任务,线程B则需要等待较长的时间,这种情况下,使用synchronized同步语句块控制需要同步的任务,方法内的其他任务做异步处理。
synchronized同步语句块怎么使用:
synchronized(object ){}   object 可以是当期对象this,也可以是new 出来的任意object,例如
object obj = new object() ;  synchronized(obj) {.......},也就是同步语句块中锁对象可以不是当前类的对象,
可以是自定的对象,这点和synchronized方法获取的是类对象的锁不同
public class ObjectService {
	public void serviceMethod(){
		Object object = new Object();
		synchronized (object) {
			System.out.println(Thread.currentThread().getName()
					+" begin time " +System.currentTimeMillis());
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" end time "+
			System.currentTimeMillis());
		}
	}
	public static void main(String[] args) {
		final ObjectService os = new ObjectService();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				os.serviceMethod();
			}
		},"A");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				os.serviceMethod();
			}
		},"B");
		t1.start();
		t2.start();
	}
}

如何使用同步块解决同步方法的弊端:一半同步,一半异步

public class Task {
	private String getData1;
	private String getData2;
	public void dolongTask(){
		System.out.println(Thread.currentThread().getName()+" begin ....");
		
		String privateData1 = "执行任务1 获取值:"+Thread.currentThread().getName();
		String privateData2 = "执行任务2获取值:"+Thread.currentThread().getName();
		
		synchronized (this) {
			this.getData1 = privateData1;
			this.getData2 = privateData2;
		}
		
		System.out.println(privateData1);
		System.out.println(privateData2);
		System.out.println(Thread.currentThread().getName()+" end....");
	}
执行结果:
B begin ....
A begin ....
执行任务1 获取值:B
执行任务2获取值:B
执行任务1 获取值:A
执行任务2获取值:A
B end....
A end....
note: 多个线程访问这个方法的效率增加了

synchronized代码的同步性常见的使用场景

1. 锁对象是当前对象,同步语句块是同步执行的
使用同步synchronized(this)代码块时候,需要注意当一个线程访问object的一个synchronized(this){}同步块时候,其他线程对同一object的其他synchronized(this){}的访问将被阻塞,因为获取的对象锁是同一个,synchronized排他性导致这两个语句块会同步执行
public class Task {
	public void doServiceA(){
		synchronized (this) {
			System.out.println("A begin ....");
			System.out.println("A end ......");
		}
	}
	
	public void doServiceB(){
		synchronized (this) {
			System.out.println("B begin ....");
			System.out.println("B end ......");
		}
	}
	
	public static void main(String[] args) {
		final Task os = new Task();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				os.doServiceA();
			}
		},"A");
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				os.doServiceB();
			}
		},"B");
		t1.start();
		t2.start();
	}
}
执行结果:
A begin ....
A end ......
B begin ....
B end ......
synchronized方法一样,synchronized(this){}同步块也是锁定当前对象的,doserviceB方法加上synchronized修饰,,将同步语句块去除,执行结果也是一样的.
A begin ....
A end ......
B begin ....
B end ......

2.同步块中将任对象作为对象监视器

同步块中使用非this对象,这样就避免和synchronized方法抢锁,效率高

public class SService {
	private String username;
	private String password;
	private String anything = new String();
	public void setUserNamePassword(String username,String password){
		synchronized (anything) {
			System.out.println(Thread.currentThread().getName()+"进入同步方法块。。。。。。。");
			this.username = username;
			this.password = password;
			System.out.println(Thread.currentThread().getName()+"离开同步方法块。。。。。。。。");
		}
	}
}

注意非this同步块和synchronized方法持有的对象锁不同,两个线程调用不同的方法,由于锁不同,执行时异步的

同步语句块中的脏读问题

非同步方法中使用synchronized同步方法块,并不能保证线程调用方法的顺序,虽然同步块中是有序的,也会出现脏读问题。

class MyOneList{
	private List<String> list = new ArrayList<>();
	synchronized public void add(String data){list.add(data);}
	synchronized public int getSize(){return list.size();}
}
public class MyService {
	public MyOneList addServiceMethod(MyOneList list,String data){
		if(list.getSize()<1){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			list.add(data);
		}
		return list;
	}
	public static void main(String[] args) throws InterruptedException {
		final MyOneList list = new MyOneList();
		final MyService ms = new MyService();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				ms.addServiceMethod(list, "A");
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				ms.addServiceMethod(list, "B");
			}
		});
		t1.start();t2.start();
		Thread.sleep(3000);
		System.out.println("list size = "+list.getSize());
	}
}
执行结果 : list size =2 ;这就出现脏读,解决,addServiceMethod 访问 list加上同步块

关于synchronize的结论:
1> 当多个线程同时执行synchronized(X){}同步块是呈同步效果
2> 当其他线程执行X对象的synchronized方法时候呈同步效果
3> 当其他线程执行X对象方法里面的synchronize(this){}是同步效果,但是需要注意其他线程调用X对象的非synchronized方法还是异步调用