当多个线程并发访问同一个数据时,往往会产生线程安全问题,Java提供了线程锁机制来保证多线程的同步。
导致线程安全问题的原因:

1.是否有多线程环境。
2.多个线程间是否有共享数据。
3.是否同时有多条语句操作共享数据。

解决线程安全问题的方法:把操作共享数据的代码给锁起来,让任意时刻都只有一个线程执行。

同步代码块

用synchronized关键字把需要同步的代码包起来,当线程访问时,会去获取锁,这样其它线程就不能再同一时刻访问了。

public class MyRunnable implements Runnable {
	
	private Object obj;	
	
	public MyRunnable(Object obj) {
		this.obj=obj;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized(obj) {//多个线程必须用同一把锁(同一个对象)
			find();
		}
	}
	
	public void find() {
		//需要在多线程内执行的代码....
	}
	
	public static void main(String[] args) {
		Object object=new Object();
		new Thread(new MyRunnable(object)).start();//启动线程1
		new Thread(new MyRunnable(object)).start();//启动线程2
	}

}

同步方法

把synchronized关键字加在方法上,锁的对象是this,调用类本身。

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
			find();
	}
	
	public synchronized void find() {
		//需要在多线程内执行的代码....
	}
	
	public static void main(String[] args) {
		Object object=new Object();
		new Thread(new MyRunnable()).start();//启动线程1
		new Thread(new MyRunnable()).start();//启动线程2
	}

}

利用synchronized关键字给需要同步的代码加锁,可避免线程安全问题,但是有时候我们不需要锁上那么多代码,这样会影响效率,Java还提供了一种显示的Lock对象来解决线程安全问题。

Lock锁

能更加清晰了解何时加锁,何时释放锁。

public class LockRunnable implements Runnable {
	private int count=0;
	private Lock lock=new ReentrantLock();//建锁对象

	@Override
	public void run() {
		// TODO Auto-generated method stub
        lock.lock();//加锁
        try {
        	++count;
        	Thread.yield();
        	++count;
        	System.out.println(Thread.currentThread()+":"+count);
        }finally {
        	lock.unlock();//把释放锁的操作放在finally里,确保其一定会执行
        }
	}

}

显示的Lock锁在加锁和释放锁方面,相对于内建的synchronized锁,具有更细粒度的控制力。

死锁

通过上锁的方式来解决线程安全问题,但如果代码处理的不好,又会导致一个死锁问题,即当多个线程在执行过程中,因争夺资源产生的一种互相等待的现象,是需要通过修改代码来避免的。
哲学家就餐问题就是一个典型的死锁案例,这里贴个百度百科的描述:

哲学家就餐问题是假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。
哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。