Java多线程与并发应用-(2)-线程互斥synchronized

首先请看下面的例子:

package com.lipeng;

public class SynchronizedDemo {
	public static void main(String[] args) {
		final Print print=new Print();
		for(int i=0;i<1000;i++)
		{
			new Thread(){
				@Override
				public void run() {
					print.printName("huangfeihong");
				}
			}.start();
			
			new Thread(){
				@Override
				public void run() {
					print.printName("zhangsanfeng");
				}
			}.start();
		}
	}
}

class Print
{
	/**
	 * 打印名字
	 * @param name
	 */
	public  void printName(String name)
	{
		for(int i=0;i<name.length();i++)
		{
			System.out.print(name.charAt(i));
		}
		System.out.println();
	}
}

例子中,Print类的作用是打印一个名字,但是一个一个字符打印的,主程序中开启两个线程,分别打印

1000次huangfeihong和zhangsanfeng.

结果如下:


以上答案并不是我们期望看到的。那么为什么会出现这种情况呢?

多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,

一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该

账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内

存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B

内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们

要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性

对应到以上的例子,多个线程共同使用一个print对象,因为CPU随机调度线程,当前执行的线程在运行期间

可能随时进入可运行状态,切换到下一个线程,无法有序的运行,所以某个线程的printName方法可能随时被打断,

这样就造成了以上结果。

那么,java中如何保持互斥呢,即如何保持某段代码的原子性操作呢?即如何保证一段代码运行期间不会被其他

线程打断呢?

方法一:使用synchronized块。

public  void printName(String name)
	{
		synchronized (this) {
			for(int i=0;i<name.length();i++)
			{
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}
		
	}

synchronized(AAA)块相当于给AAA对象加了一把锁,当多个线程调用此方法时,某线程运行到synchronized

块时,如果AAA的同步锁没有被占用,则获得AAA的同步锁,直到synchronized块的代码执行完毕,执行期间其他

线程运行到此处时,发现AAA的同步锁已经被占用,则无法运行块内的方法,必须等待拥有锁的线程释放锁,抢到锁

之后代码才能继续向下执行。

方法二:synchronized方法

public  synchronized void printName(String name)
	{
		for(int i=0;i<name.length();i++)
		{
			System0.out.print(name.charAt(i));
		}
		System.out.println();
	}

1. 普通的成员synchronized方法,将锁作用于当前对象。即只要多个线程之间是同一个对象调用此方法,他们之

间就是互斥的。

2. static synchronized方法 ,将锁作用于类.class,即作用于全部这个类的实例,所以,多个线程之间,此类的

全部实例调用此static synchronized方法都是互斥的。

方法三:使用同步锁Lock,此方法以后的文章中详解:

关于synchronized需要注意的:

1. 什么情况下方法或者代码块需要时synchronized的?

多个线程同时操作(一般为写操作,包括修改,删除,状态改变等)同一部分数据时。注意。操作的为任何类型

局数据。包括:

1.1 线程操作对象的成员变量。

1.2 数据库数据。

1.3 缓存数据。包括,java内存,第三方内存,redis等的数据

1.4 文件中的数据。

。。。。

举例:我在项目中使用过的例子:

需求:要求同时启动多个线程对数据库表A中的某些数据进行处理。启动一个线程去取数据(id),40个线程去

处理数据。(通过id查询其他表,并生成新的数据)为了保证处理过的数据不再重复处理,并且记录处理成功和失败的

数据以便后续处理。我做了以下操作

(1) 根据条件(sql 中status=0)去数据库中取数据。取到后将status置为status=1,直到根据status=0 order by 

id取不到数据,任务完成。

(2) 处理数据,大概每条数据10秒钟。

(3) 如果处理成功将status=2。

(4) 如果处理失败将status=3.

其中取id的步骤(1)为synchronized方法,为什么呢?

因为(1)中分两步 取status=0的数据,然后将status=1,如果没有synchronized,则如果一个线程取到id=1的

数据,还没有设置status=1,就切换到其他线程,那么其他线程执行的时候,又会取到此id=1的数据。造成了对id=1

的数据的重复处理。从而产生多余数据或者错误数据。也降低了程序的效率。很明显不是我们想要的。所以多个线程

执行这个步骤必须使用synchronized.

伪代码如下:

package com.lipeng;

public class SynchronizedDemo2 {
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		for(int i=0;i<40;i++)
		{
			new Thread(new Task()).start();
		}
	}
	
}

class Task implements Runnable
{
	//文中注释都是针对不加同步锁synchronized的情况
	@Override
	public void run() {
		while(true)
		{
			synchronized (Task.class) {
				System.out.println("到数据库中取status为0 的product");
				Product product=this.getProductIdOfStatus0();//where status=0 order by somefield
				if(product==null)//假设product这次取到的id是123,如果在这行被切换到其他线程,因为123的status还是0,所以其他线程取到的id还是123,
				{							//则会对id为123的product重复操作。影响性能,甚至造成错误
					return;
				}
				product.setStatus(1);//product 的status设置为1并提交到db 表示正在处理
				this.handleProduct(product);//如果在这行被切换到其他线程,不会造成错误,因为切换到其他线程后所做的操作与此线程互不影响,
														//其他线程去数据库取数据(取status=0)也不会取到因为此时123的product的status已经变为1了。
			}
			
			
		}
		
		
	}

	private void handleProduct(Product product) {
		try {
			Thread.sleep(1000);
			System.out.println("耗时的操作的各种业务。。。。。。。。。");
			product.setStatus(2);//"将product的status设置为21并提交到db,表示处理成功
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			product.setStatus(3);//将product的status设置为31并提交到db,表示处理失败。
		}
		
	}

	private Product getProductIdOfStatus0() {
		// TODO Auto-generated method stub
		Product product=new Product();
		return product;
	}
}

class Product
{
	private Integer id;
	private Integer status;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public Integer getStatus() {
		return status;
	}
	public void setStatus(Integer status) {
		this.status = status;
	}
	
}

2.1 同一个类中两个普通synchronized方法之间互斥吗?

互斥。只要是同一个监视对象就互斥。

2.2 两个普通方法中都使用synchronized块,且用了同一个监视对象,那么这两个块之间互斥吗?两个方

法互斥吗?

互斥。只要是同一个监视对象就互斥。

2.3 一个普通synchronized方法和一个static synchronized方法之间互斥吗?

不互斥。只要是同一个监视对象就互斥。

3. 在写线程同步的代码的时候,synchronized监视的对象必须要和调用wait()方法和notify()方法的对象一致。

4. 同步锁必须作用在需要互斥的多个线程间的共享对象,像下面的代码是没有意义的

{
    Object lock = new Object();
    synchronized (lock) {
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
        }
    }
}

5.使用synchronized在某些情况下会造成死锁,死锁问题以后会说明。使用synchronized修饰的方法或者代码

块可以看成是一个原子操作。

6. 因为只要是同一个监视对象就互斥。那么一个类中不应该有过多的synchronized方法,因为他们之间都互相

互斥,运行效率太低。那么如何解决呢,使用synchronize块监视按需求不同的方法监视不同的对象,将方法分

组监视不同的对象(用成员做监视器即可)

7. 每个锁对(JLS中叫monitor)都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁

的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,

反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒,这个涉及到线程间的通信,下一篇博文

会说明。看我们的例子,当第一个线程执行输出方法时,获得同步锁,执行输出方法,恰好此时第二个线程也

要执行输出方法,但发现同步锁没有被释放,第二个线程就会进入就绪队列,等待锁被释放。一个线程执行互

斥代码过程如下:

        1. 获得同步锁;

        2. 清空工作内存;

        3. 从主内存拷贝对象副本到工作内存;

        4. 执行代码(计算或者输出等);

        5. 刷新主内存数据;

        6. 释放同步锁。

        所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

部分内容转自:http://blog.csdn.net/ghsau/article/details/7424694

















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值