多线程-4-线程同步、线程安全问题及死锁研究

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weinabanta/article/details/29368179

多线程编程优点是执行效率高,缺点就是太容易出现错误情况,这是因为多条线程访问同一资源,而线程代码有多条同时Java线程调度具有随机性造成的,当然如果在编程时能很好的注意这些问题,其实也是可以解决的。


0.知识体系




1.线程安全问题


下述代码就是有安全问题,安全问题出现的原因是因为CPU切换线程的不确定性造成的。

class BankAccount
{
	private int money;
	public BankAccount(int money){
		this.money = money;
	}
	public void getMoney(int num){
		if(money > num){
			money -= num;
			System.out.println(Thread.currentThread().getName() + " 取钱 " + num +" 元;" + "余额 " + money);
		}else{
			System.out.println("余额不足");
			System.exit(0);
		}
	}
}

class Customer implements Runnable
{
	private BankAccount account = null;
	public Customer(BankAccount account){
		this.account = account;
	}
	public void run(){
		while(true){
			account.getMoney(200);	
		}
	}
}

public class ThreadSynTest 
{
	public static void main(String[] args) 
	{
		
		BankAccount account = new BankAccount(1000);
		Customer ct1 = new Customer(account);
		Customer ct2 = new Customer(account);
		Thread t1 = new Thread(ct1);
		Thread t2 = new Thread(ct2);
		t1.start();
		t2.start();
	}
}


2.解决方案


1)同步代码块

上述代码 不具有同步安全性,两个并发程序在修改账户时,正好发生切换。为解决这个问题,多线程引入了同步监视器解决,使用同步监视器的通用方法是同步代码块。语法如下:

synchronized(obj){//obj就是同步监视器
     //code
}

线程开始执行同步代码之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码执行完成后,该线程释放对该同步监视器的锁定。阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

利用同步代码块保持同步如下:

class BankAccount
{
	private int money;
	public BankAccount(int money){
		this.money = money;
	}
	public void getMoney(int num){
		if(money > num){
			money -= num;
			System.out.println(Thread.currentThread().getName() + " 取钱 " + num +" 元;" + "余额 " + money);
		}else{
			System.out.println("余额不足");
			System.exit(0);
		}
	}
}

class Customer implements Runnable
{
	private BankAccount account = null;
	public Customer(BankAccount account){
		this.account = account;
	}
	public void run(){
		while(true){
			try{Thread.sleep(100);}catch(InterruptedException ie){}
			synchronized(account){//进入同步代码块之前必须先获得对account账户的锁定
				account.getMoney(200);	
			}
		}
	}
}

public class ThreadSynTest 
{
	public static void main(String[] args) 
	{
		
		BankAccount account = new BankAccount(1000);
		Customer ct1 = new Customer(account);
		Customer ct2 = new Customer(account);
		Thread t1 = new Thread(ct1);
		Thread t2 = new Thread(ct2);
		t1.start();
		t2.start();
	}
}


2)同步方法


与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字修饰某个方法,则该方法称为同步方法。对于同步方法而言,无须显式指定同步监视器,同步方法的同步监视this,也就是该对象本身。
通过使用同步方法方便实现线程安全的类,如下特征:
(1)该类的对象可以被多个线程安全地访问
(2)每个线程调用该对象的任意方法之后都将得到正确结果。
(3)每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。

synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器,属性等。

class BankAccount
{
	private int money;
	public BankAccount(int money){
		this.money = money;
	}
	public synchronized void getMoney(int num){
		if(money > num){
			money -= num;
			System.out.println(Thread.currentThread().getName() + " 取钱 " + num +" 元;" + "余额 " + money);
		}else{
			System.out.println("余额不足");
			System.exit(0);
		}
	}
}

class Customer implements Runnable
{
	private BankAccount account = null;
	public Customer(BankAccount account){
		this.account = account;
	}
	public void run(){
		while(true){
			try{Thread.sleep(100);}catch(InterruptedException ie){}
			synchronized(account){//进入同步代码块之前必须先获得对account账户的锁定
				account.getMoney(200);	
			}
		}
	}
}

public class ThreadSynTest 
{
	public static void main(String[] args) 
	{
		
		BankAccount account = new BankAccount(1000);
		Customer ct1 = new Customer(account);
		Customer ct2 = new Customer(account);
		Thread t1 = new Thread(ct1);
		Thread t2 = new Thread(ct2);
		t1.start();
		t2.start();
	}
}

3)同步锁

Java5开始,Java提供了一种功能更强大的线程同步机制,通过显式定义同步锁对象来实现同步,在这种机制下,同步锁使用Lock对象充当。
Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock实现允许更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。
Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是Java 5新提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类;为ReadWriteLock提供了ReentrantReadWriteLock实现类。

在实现线程安全控制中,比较常用的是ReentrantLock(可重入锁),使用该Lock对象可以显式地加锁、释放锁,通常使用ReentrantLock代码如下:

class className
{
	//定义锁对象
	private final ReentrantLock lock = new ReentrantLock();
	//定义需要保证线程安全的方法
	public void methodName()
	{
		//加锁
		lock.lock();
		try{
			//需要保证线程安全的代码
		}finally{
			lock.unlock();
		}
	}
}

使用同步锁后的代码如下:      

import java.util.concurrent.locks.ReentrantLock;

class BankAccount
{
	private ReentrantLock lock = new ReentrantLock();
	private int money;
	public BankAccount(int money){
		this.money = money;
	}
	public void getMoney(int num){
		lock.lock();
		try{
		if(money > num){
			money -= num;
			System.out.println(Thread.currentThread().getName() + " 取钱 " + num +" 元;" + "余额 " + money);
		}else{
			System.out.println("余额不足");
			System.exit(0);
		}
		}
		finally{
			lock.unlock();
		}
	}
}

class Customer implements Runnable
{
	private BankAccount account = null;
	public Customer(BankAccount account){
		this.account = account;
	}
	public void run(){
		while(true){
			try{Thread.sleep(100);}catch(InterruptedException ie){}
			synchronized(account){//进入同步代码块之前必须先获得对account账户的锁定
				account.getMoney(200);	
			}
		}
	}
}

public class ThreadSynTest 
{
	public static void main(String[] args) 
	{
		
		BankAccount account = new BankAccount(1000);
		Customer ct1 = new Customer(account);
		Customer ct2 = new Customer(account);
		Thread t1 = new Thread(ct1);
		Thread t2 = new Thread(ct2);
		t1.start();
		t2.start();
	}
}


3.死锁研究


1)产生死锁的原因


当两个程序相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,程序既不会发生任何异常也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

下面是一个死锁例子:

class Locks
{
	public static final Object lockA = new Object();
	public static final Object lockB = new Object();
}

class PublicTarget
{
	public static void showInfo(){
		System.out.println("----" + Thread.currentThread().getName() + " 拿到两锁----");
	}
}

class Accesser implements Runnable 
{
	private boolean flag = true;
	public void run(){
		if(flag){
			synchronized(Locks.lockA){
				flag = false;
				System.out.println(Thread.currentThread().getName() + " 拿到lockA锁");
				try{Thread.sleep(100);}catch(InterruptedException ex){}
				synchronized(Locks.lockB){
					System.out.println(Thread.currentThread().getName() + " 拿到lockB锁");
					PublicTarget.showInfo();
				}
			}
		}else{
			synchronized(Locks.lockB){
				flag = true;
				System.out.println(Thread.currentThread().getName() + " 拿到lockB锁");
				try{Thread.sleep(100);}catch(InterruptedException ex){}
				synchronized(Locks.lockA){
					System.out.println(Thread.currentThread().getName() + " 拿到lockA锁");
					PublicTarget.showInfo();
				}
			}
		}
	}
}

public class DeadLockTest{
	public static void main(String[] args){
		Accesser accesser = new Accesser();
		Thread a = new Thread(accesser);
		Thread b = new Thread(accesser);
		a.start();
		b.start();
	}
}


2)死锁的预防

待补充...

猜你喜欢

转载自blog.csdn.net/weinabanta/article/details/29368179