通过一个实例,在多线程情况下去分析安全隐患并解决。

需求分析:有两个储户去银行存钱,每次存100,共存三次。

class Bank
{ 
	private int sum; //银行共有多少钱
	public void add(int num)
	{
		sum=sum+num;
		System.out.println("sum="+sum);
	}
}

class Customer implements Runnable  //顾客存钱的行为可以封装为线程任务
{
	public void run()
	{
		Bank b=new Bank();
		for(int x=0;x<3;x++)
		{
			b.add(100); //add()是Bank类对象的方法,所以要创建一个Bank类对象
		}
	}
}

class BankDemo
{
	public static void main(String[] args)
	{
		Customer c=new Customer();  //创建任务对象
		Thread t1=new Thread(c);    //创建线程
		Thread t2=new Thread(c);

		t1.start();
		t2.start();
	}
}

运行结果:

java让两个方法同步进行 java同步函数_同步函数

我们看到,sum=100出现两次,sum=200出现两次,而两位顾客每一次存钱100元都会导致sum增加100,即运行结果发生错误。这是因为我们在Customer类的run()方法中创建了一个Bank类对象,而两个线程(顾客)在每次执行任务代码(存钱)时都会重新创建Bank()对象,实则就创建了两个Bank类对象,而并分操作同一个银行。在Customer类中应该这样编写:

class Customer implements Runnable  //顾客存钱的行为可以封装为线程任务
{
	private Bank b=new Bank();  //保证多个线程操作同一个Bank类对象
	public void run()
	{
		
		for(int x=0;x<3;x++)
		{
			b.add(100); //add()是Bank类对象的方法,所以要创建一个Bank类对象
		}
	}
}

运行结果:

java让两个方法同步进行 java同步函数_Customer_02

现在显示的结果是正确的,但在现在的程序中仍然存在线程安全隐患。

通过分析,Bank类的b对象和Bank类的sum成员都是多线程的共享数据,Bank类的add()方法是线程任务代码。在本例中,对共享数据的操作不止一条,就可能存在安全问题。我们调用sleep()方法来验证这个问题:

class Bank
{ 
	private int sum; //银行共有多少钱
	public void add(int num)
	{
		sum=sum+num;
		try{Thread.sleep(10);}catch(InterruptedException e){}
		System.out.println("sum="+sum);
	}
}

运行结果:


java让两个方法同步进行 java同步函数_类对象_03


出现了两次sum=200,sum=400,sum=600,发生了安全问题。


所以用同步代码块将线程代码进行封装:

class Bank
{ 
	private int sum; 
	private Object obj=new Object();
	public void add(int num)
	{
		synchronized(obj) //此时不能直接使用new Object()来创建对象,原理如上所讲,即每个线程使用自己的锁。
		{
			sum=sum+num;
			try{Thread.sleep(10);}catch(InterruptedException e){}
			System.out.println("sum="+sum);
		}
	}
}

运行结果:

java让两个方法同步进行 java同步函数_同步函数_04



同步函数:在上面的代码中,函数add()和synchronized都对代码进行了封装,但synchronized是带有同步特性的封装。让函数具备同步性就解决了上述问题。就是将同步关键字作为函数的修饰符:

class Bank
{ 
	private int sum; 
	public synchronized void add(int num) //同步函数-->解决线程安全问题
	{			
		sum=sum+num;
		try{Thread.sleep(10);}catch(InterruptedException e){}
		System.out.println("sum="+sum);
		
	}
}

运行结果:

java让两个方法同步进行 java同步函数_同步函数_05

注:同步函数使用的锁是this。


引申:1.同步函数和同步代码块的区别:同步函数的锁是固定的this(当前对象),同步代码块的锁是任意的对象。

2.静态的同步函数使用的锁是,该函数所属字节码文件对象,可以用 getClass()方法获取,也可以用当前  类名.class 表示。