关闭

多线程编程-Synchronized同步方法(二)

标签: synchronized同步方法synchronized可重入锁
17人阅读 评论(0) 收藏 举报
分类:

2.1 synchronized 同步方法

    “非线程安全”会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是读到的数据其实是被更改过的。

    “线程安全”就是获取的实例变量的值是经过同步处理的,不会出现脏读的现象。

 

2.1.1 方法内的变量是线程安全的

    “非线程安全”的问题存在于“实例变量”中,如果是方法内部的私有变量,就是“线程安全”的。

测试代码:

public class HasSelfPrivateNum {
	public void addI(String username){
		try{
			int num =0;
			if(username.equals("a")){
				num =100;
				System.out.println("a set over");
				Thread.sleep(1000);
			}else{
				num =200;
				System.out.println("b set over");
			}
		}catch (InterruptedException e){
			e.printStackTrace();
		}
	}
}
public class ThreadA extends Thread {
	private HasSelfPrivateNum numRef;
	public ThreadA(HasSelfPrivateNum numRef){
		super();
		this.numRef = numRef;
	}
	
	@Override
	public void run(){
		super.run();
		numRef.addI("a");
	}
}

public class ThreadB extends Thread {
	private HasSelfPrivateNum numRef;
	public ThreadB(HasSelfPrivateNum numRef){
		super();
		this.numRef = numRef;
	}
	
	@Override
	public void run(){
		super.run();
		numRef.addI("b");
	}
}

public class ThreadSafeDemo {

	public static void main(String[] args) {
		HasSelfPrivateNum numRef = new HasSelfPrivateNum();
		ThreadA thread_a = new ThreadA(numRef);
		thread_a.start();
		
		ThreadB thread_b = new ThreadB(numRef);
		thread_b.start();		
	}
}

运行结果:

a set over

b set over

方法中的变量不存在“非线程安全”的问题。


2.1.2 实例变量非线程安全

多线程访问的对象中如果有多个实例变量,运行的结果可能出现交叉的情况。

沿用2.1.1中代码,仅修改HasSelfPrivateNum.java文件,将intnum =0;移到addI()方法的外面。

public class HasSelfPrivateNum {
	int num =0;
	public void addI(String username){
		try{			
			if(username.equals("a")){
				num =100;
				System.out.println("a set over");
				Thread.sleep(500);
			}else{
				num =200;
				System.out.println("b set over");
			}
			System.out.println(username+",,,num="+num);
		}catch (InterruptedException e){
			e.printStackTrace();
		}
	}
}

运行结果:

a set over

b set over

b,,,num=200

a,,,num=200

两个线程同时操作对象中的实例变量,出现了“非线程安全”的问题。

避免这个问题的做法是在addI前加关键字synchronized。

public class HasSelfPrivateNum {
	int num =0;
	synchronized public void addI(String username){
		try{			
			if(username.equals("a")){
				num =100;
				System.out.println("a set over");
				Thread.sleep(500);
			}else{
				num =200;
				System.out.println("b set over");
			}
			System.out.println(username+",,,num="+num);
		}catch (InterruptedException e){
			e.printStackTrace();
		}
	}
}
运行结果:

a set over
a,,,num=100
b set over
b,,,num=200

由于是同步方法,所以是顺序访问。

2.1.3 多个对象多个锁

沿用2.1.2的代码,仅修改ThreadSafeDemo.java,让两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,也就是为两个线程绑定了不同的实例对象。

public class ThreadSafeDemo {

	public static void main(String[] args) {
		HasSelfPrivateNum numRefA = new HasSelfPrivateNum();
		HasSelfPrivateNum numRefB = new HasSelfPrivateNum();
		ThreadA thread_a = new ThreadA(numRefA);
		thread_a.start();
		
		ThreadB thread_b = new ThreadB(numRefB);
		thread_b.start();		
	}
}

运行结果:

a set over

b set over

b,,,num=200

a,,,num=100

虽然是同一个同步的方法,却是异步的方式运行的。因为synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁,所以,在一个线程先执行synchronized方法时,这个线程就持有了该方法所属对象的锁,那么其他要访问这个方法的线程只能等待,调用synchronized方法一定是排队运行的(只要是同一个锁对象下synchronized修饰的方法,而不论是不是同一个synchronized方法),但是这个前提是多个线程访问的同一个对象。

当访问的是多个对象时,就产生了多个锁,同步操作就变成了异步执行了。


2.1.4 前面的同步是在赋值时进行的,读取时也可能出现意外,这就是“脏读”。

测试代码:

public class PublicVar {
	public String username = "A";
	public String password = "AA";
	
	synchronized public void setValue(String username, String password){
		try{
			this.username = username;
			Thread.sleep(1000);
			this.password = password;
			System.out.println("setValue,thread name="+Thread.currentThread().getName()+
					",username="+username+",password="+password);
		}catch (InterruptedException e){
			e.printStackTrace();
		}
	}
	
	public void getValue(){
		System.out.println("getValue,thread name="+Thread.currentThread().getName()+
				",username="+username+",password="+password);
	}
}
public class ThreadA extends Thread {
	private PublicVar numRef;
	public ThreadA(PublicVar numRef){
		super();
		this.numRef = numRef;
	}
	
	@Override
	public void run(){
		super.run();
		numRef.setValue("B","BB");
	}
}
public class ThreadSafeDemo {

	public static void main(String[] args) throws InterruptedException {
		PublicVar numRef = new PublicVar();
		ThreadA thread_a = new ThreadA(numRef);
		thread_a.start();
		Thread.sleep(200);
		numRef.getValue();
	}
}

运行结果:

getValue,threadname=main,username=B,password=AA

setValue,threadname=Thread-0,username=B,password=BB

第一行输出出现“脏读”,原因是getValue方法没有同步,需要加上synchronized关键字。

运行结果:

setValue,threadname=Thread-0,username=B,password=BB

getValue,threadname=main,username=B,password=BB


2.1.5 synchronized锁重入

关键字synchronized拥有锁重入的功能,就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。在一个synchronized方法内部调用本类的其他synchronized方法时,是永远可以得到锁的。

测试代码:

public class ReEntrySynchronized {
	synchronized public void service_1(){
		System.out.println("service_1");
		service_2();
		System.out.println("service_1--end");
	}

	synchronized public void service_2(){
		System.out.println("service_2");
		service_3();
	}
	
	synchronized public void service_3(){
		System.out.println("service_3");
	}
}
public class ThreadA extends Thread {
	@Override
	public void run(){
		ReEntrySynchronized rs = new ReEntrySynchronized();
		rs.service_1();
	}
}
public class ThreadSafeDemo {
	public static void main(String[] args) throws InterruptedException {
		ThreadA thread_a = new ThreadA();
		thread_a.start();
	}
}

运行结果:

service_1

service_2

service_3

service_1—end

 

“可重入锁”:自己可以再次获取自己的内部锁,如一个线程获取了某个对象的锁,在这个对象锁还没释放时,当这个线程再次获取这个对象的锁时还是可以获取的,如果不可锁重入的话,就会导致死锁。

 

2.1.6 可重入锁支持在父子类的继承环境中,也就是说子类可以通过“可重入锁”调用父类的同步方法。

测试代码:

public class ReEntrySynchronized {
	public int i= 10;
	
	synchronized public void service_1(){
		i--;
		System.out.println("service_1,i="+i);
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class ReEntryLock extends ReEntrySynchronized {
	synchronized public void service_sub(){
		while(i > 0){
			i--;
			System.out.println("service_1,i="+i);
			try {
				Thread.sleep(100);
				service_1();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
}
public class ThreadA extends Thread {
	@Override
	public void run(){
		ReEntryLock rs = new ReEntryLock();
		rs.service_sub();
	}
}
public class ThreadSafeDemo {

	public static void main(String[] args) throws InterruptedException {
		ThreadA thread_a = new ThreadA();
		thread_a.start();
	}
}

运行结果:

service_1,i=9

service_1,i=8

service_1,i=7

service_1,i=6

service_1,i=5

service_1,i=4

service_1,i=3

service_1,i=2

service_1,i=1

service_1,i=0

虽然“可重入锁”可以存在父子继承关系中,但是同步是不可以继承的,就是说父类有一个synchronized修饰的同步方法,子类覆写的这个方法不会自动继承同步性质,想要子类的这个方法也具有同步性质,需要添加synchronized关键字。

2.1.7 出现异常,锁自动释放,当一个线程执行的代码出现异常时,其所持有的锁会自动释放。











0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

多线程下synchronized修饰static方法与非static方法的区别

一直对多线程的概念比较模糊,今天就写了个关于变量原子操作的小程序,好让自己加深一下理解 代码如下: package atomic; public class JoinThread extends...
  • hey_a_hao
  • hey_a_hao
  • 2013-08-21 18:06
  • 7644

JAVA多线程-对象及变量的并发访问(一)synchronized详解

一、synchronized同步方法 1.1 方法内的变量为线程安全     非线程安全的问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了...
  • qq_26504875
  • qq_26504875
  • 2016-02-20 09:07
  • 2276

java多线程、并发系列之 (synchronized)同步与加锁机制

SynchronizedJava中每个对象都有一个内置锁,当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获...
  • yujin753
  • yujin753
  • 2015-04-30 18:49
  • 8836

《多线程编程》学习之四:synchronized同步方法

“非线程安全”发生在多个线程对同一对象的实例变量进行并发访问时,结果是产生“脏读”(取得的数据是被别的线程更改过的)。 1.1 实例变量非线程安全       先看下面一个例子:为普通会员与vip会员...
  • studyhxz
  • studyhxz
  • 2016-10-19 17:34
  • 156

Java多线程编程2--同步锁定--synchronized同步方法、脏读、锁重入

1、方法内的变量为线程安全   “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题(这是方法内部的变量是私有的特性造成的,所得结果也就是“线程安全”的了。 ...
  • oChangWen
  • oChangWen
  • 2016-05-04 10:52
  • 1172

【转载】Java多线程编程2--同步锁定--synchronized同步方法、脏读、锁重入

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。1、方法内的变量为线程安全   “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题(这是方法...
  • a303549861
  • a303549861
  • 2016-11-03 00:41
  • 236

《多线程编程》学习之五:synchronized同步语句块,静态同步synchronized方法与synchronized(类名.class)代码块

在介绍同步语句块之前,先做一个实验,验证多个线程调用同一个同步方法是随机的。 例子一: 1)MyList.java 2)测试:         可见,在同步方法中的代码是同步打印的,但线程A和...
  • studyhxz
  • studyhxz
  • 2016-10-20 13:28
  • 614

多线程编程--5种方法实现线程同步

1:用Interlocked系列函数实现线程同步; 2:用CRITICAL_SECTION及其系列函数实现线程同步; 3:用RTL_SRWLOCK及其系列函数实现线程同步; 4:用事件内核对象实...
  • zhangyueweia
  • zhangyueweia
  • 2015-12-30 19:01
  • 175

Linux多线程编程及线程同步方法总结

参考资料 [1]在Linux中使用线程 [2]linux多线程设计 [3]Linux--线程编程
  • Q1302182594
  • Q1302182594
  • 2015-07-12 14:59
  • 725

多线程编程--5种方法实现线程同步

1:用Interlocked系列函数实现线程同步; 2:用CRITICAL_SECTION及其系列函数实现线程同步; 3:用RTL_SRWLOCK及其系列函数实现线程同步; 4:用事...
  • bible521125
  • bible521125
  • 2015-09-15 15:54
  • 313
    个人资料
    • 访问:35518次
    • 积分:1686
    • 等级:
    • 排名:千里之外
    • 原创:130篇
    • 转载:38篇
    • 译文:0篇
    • 评论:9条
    最新评论