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

      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方法还是异步调用

    











      


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值