在Java语言中,引入对象互斥锁的概念,保证共享数据操作的完整性。每个对象都应用于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问对象。

1.关键字synchronized

Java以提供关键字synchronized的形式,为防止资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

那么什么时候使用同步呢?如果你正在写的一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另外一个线程这写过的变量,那么你必须使用同步,并且,读写线程都必须使用相同的监视器锁住同步。

2. synchronized使用

1)        在方法内部使用

synchronized (this)
		{
			num++;
			try
			{
				Thread.sleep(1);
			} catch (InterruptedException e)
			{
			}
			System.out.println(name + ",你是第 " + num + " 个使用timer的线程");
		}

2)        synchronized还可以放在方法声明中,表明整个方法为同步方法。

public synchronized void add(String name)
	{
			num++;
			try
			{
				Thread.sleep(1);
			} catch (InterruptedException e)
			{
			}
			System.out.println(name + ",你是第 " + num + " 个使用timer的线程");
	
	}

3.  synchronized例子

timer.add(Thread.currentThread().getName())的方法。由于t1和t2都在调用调用这个方法,如果我们不加上同步锁的话,那么你会发现在,结果num的值都是2.因为t1在调用时,t2也在调用,而在t1休眠1秒的期间,num被改了,所以t1醒来后num就成了1。所以加在synchronized的关键字之后,也就是当t1在调用add的方法时,Timer对象的add方法是被锁住的,即时t1在sleep(),但上锁并没有放掉,当t2也要调用时,t1的锁没有放掉,t2只能等待了;等t1处理好了,num输出就是1,而t2开始执行输出的值就是2.

package com.owen.thread;

/**
 * 使用同步线,也就是锁住线程,一个一个来执行
 * @author OwenWilliam 2019-7-22
 * @since
 * @version v1.0.0
 *
 */
public class TestSync implements Runnable
{

	
	Timer timer = new Timer();
	public static void main(String[] args)
	{
		//当new出TestSync的对象时,这个对象自身包含了对象Timer,而Timer中有num和add方法
		//所以线程调用TestSync,就是调用 Timer对象
		TestSync test = new TestSync();
		Thread t1 = new Thread(test);
		Thread t2 = new Thread(test);
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		t2.start();

	}

	@Override
	public void run()
	{

		timer.add(Thread.currentThread().getName());
	}

}

/**
 * 创建Timer对象,对象使用了锁
 * @author OwenWilliam 2016-7-22
 * @since
 * @version v1.0.0
 *
 */
class Timer 
{
	private static int num = 0;
	public void add(String name)
	{
		
		//当前对象锁住,也就是它在执行时,其它要进来的线程先排队
		//这方法也可以这样写:public synchronized void add(String name):代表锁住当前方法的对象
		synchronized (this)
		{
			num++;
			try
			{
				Thread.sleep(1);
			} catch (InterruptedException e)
			{
			}
			System.out.println(name + ",你是第 " + num + " 个使用timer的线程");
		}
	}
}

4.死锁

死锁是这样的一种情况,某个任务在等待另外一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待另外一个任务释放锁。这得到了一个任务之间相互等待的连续循环,没有哪个线程能继续。这被称为死锁。

下面的例子,我们定义了两个对象。当对象当我们锁住o1的时候,我们就让当前休眠5秒,5秒过后当前线程需要锁住o2的对象才可以执行完线程;同样的我们锁住o2的时候,我也让当前线程休眠5秒,当线程休眠过后,当前线程需要锁住o1才能结束当前线程。也就是例子中含有两个线程,同时都在执行,可是问题就在于,每一个线程都需要另一个对象锁住后才能完成线程。你会发现,线程t1需要再次锁住o2时,o2此时正在被t2线程锁住,所以t1需要等待;而t2需要锁住o1时候,o1对象被t1线程淮子,所t2需要等待。两个线程都在等待对方放掉锁住的对象,可是谁都不放,这个时候就进入死锁状态。

package com.owen.thread;

/**
 * 例子主要使用的功能就是死锁的概念。
 *
 * @author OwenWilliam
 *
 */
public class TestDeadLock implements Runnable
{

	public int flag = 1;
	static Object o1 = new Object();
	static Object o2 = new Object();
	
	@Override
	public void run()
	{
		System.out.println("flag = " + flag);
		
		//如果是这个的话,那么锁住o1对象,同量如果可发锁住o2的对象,那么方法就会执行完了
		//可惜o2已经被锁了,拿不到这个锁了。这样就死锁了。
		if (flag == 1)
		{
			//锁住o1对象
			synchronized(o1)
			{
				try
				{
					Thread.sleep(500);
				} catch (Exception e)
				{
					e.printStackTrace();
				}
				//锁住o2对象
				synchronized(o2)
				{
					System.out.println("1");
				}
			}
		}
		
		//锁住o2对象,如果再能锁住o1对象,那么方法就可以执行完了。可惜o1已经被锁住了,拿不到这个锁了。
		if (flag == 0)
		{
			synchronized(o2)
			{
				try
				{
					Thread.sleep(500);
				} catch (Exception e)
				{
					e.printStackTrace();
				}
				
				synchronized(o1)
				{
					System.out.println("0");
				}
			}
		}
		
	}

	public static void main(String[] args)
	{
		TestDeadLock td1 = new TestDeadLock();
		TestDeadLock td2 = new TestDeadLock();
		td1.flag = 1;
		td2.flag = 0;
		Thread t1 = new Thread(td1);
		Thread t2 = new Thread(td2);
		t1.start();
		t2.start();
	}
}

5.特殊同步应用

    下面的例子中,我们在一个线程内定义了两个方法,其中一个方法是同步方法m1(),另外一个只是普通的方法m2()。同时,我们将m1()方法放入到run的方法中,而m2并没有。这个时候,我们在主线程调用start()的方法时,只会调用m1()的方法,但是我们在调用start()方法同时,我们也去调用m2()的方法。由于m1()方法会休眠5秒,而 m2()方法只会休眠2.5秒。这个时候就会出现的问题是当m1()方法休眠过后,它的值b被修改为2000了。这个是我们所不愿意看到的,既然用到了synchronized的关键字,我们当然希望它方法内部的所有操作都是其它方法不能调用的。这个时候,我们也在m2()的方法加上synchronized关键字就可以解决问题了。这个方法也就说明,如果某个方法使用了synchronized关键字,那么同时也要考虑它内部的参数是否在其它方法被调用,如果是,那么是否其它方法也需要用锁处理。

package com.owen.thread;

/**
 * 一个方法使用synchronized,代表该方法已经被锁了,内部的方法不可改变。
 * 但其它普通方法依然可以调用
 * @author OwenWilliam 2016-7-23
 * @since
 * @version v1.0.0
 *
 */
public class TestLock implements Runnable
{

	int b = 100;
	//m1()方法为同步方法锁住了,代表内部的方法是其它线程不可访问的
	public synchronized void m1()
	{
		b = 1000;
		try
		{
			Thread.sleep(5000);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		System.out.println("b = " + b);
	}
	
	//普通方法,即使m1()方法锁住了,但是b公共的,m2()还是可以执行,b可以改变。
	//当m1()醒来时,b的值为2000了。
	public void m2() 
	{
		try
		{
			Thread.sleep(2500);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		b = 2000;
	}
	@Override
	public void run()
	{
		try
		{
			m1();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
	}

	public static void main(String[] args)
	{
		TestLock tl = new TestLock();
		Thread t = new Thread(tl);
		//在执行m1()时,再调用m2()
		t.start();
		tl.m2();
	}

}

6.生产者与消费者

    请考虑这样的一个馒头店,它一个厨师做馒头和消费者吃馒头。厨师我们命名是Producer,消费者是Customer.那么厨师是做馒头的,所以我们命名馒头为WoTou。还有一个问题,厨师做出来的馒头需要放到一个篮子里,消费者需要从这个篮子里拿馒头吃。这个篮子我们命名为SyncStack。这个篮子就是栈,先进后出。所以我们就定义了这些对象来富丽生产者与消费者的例子。这里,生产者生产的馒头放到一个篮子里,只要篮子满了,那么就要调用Object的wait()方法,也就是当前的线程处理于等待状态,它的锁也暂时给了其它线程用了。同时,生产者还要叫醒消费来吃馒头,我们使用notify()方法,如果有多个消费者都要唤醒,那么可以用notifyAll()。当然,消费者也是一样的,当消费者消费了篮子里所有的馒头后,它也需要wait(),同时使用notify()或notifyAll()方法叫醒生产者。例子如下。

  

package com.owen.thread;

/**
 * 生产者与消费者的应用:生产者:Producer 消费者:Consumer 生产产品:WoTou 产生所放处:SyncStack
 * @author OwenWilliam 2016-7-23
 * @since
 * @version v1.0.0
 *
 */
public class ProducerConsumer
{
	public static void main(String[] args)
	{
		SyncStack ss = new SyncStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);
		new Thread(p).start();
		new Thread(p).start();
		new Thread(p).start();
		new Thread(c).start();
	}
}

/**
 * 产品
 * @author OwenWilliam
 *
 */
class WoTou
{
	int id;

	WoTou(int id)
	{
		this.id = id;
	}

	public String toString()
	{
		return "WoTou : " + id;
	}
}

/**
 * 放生产的产品容器
 * @author OwenWilliam
 *
 */
class SyncStack
{
	int index = 0;
	WoTou[] arrWT = new WoTou[6];

	/**
	 * 生产者放产品
	 * @param wt
	 */
	public synchronized void push(WoTou wt)
	{
		//如果满了应该wait,而且只能用while,如果是if的话,抛出异常还会继续执行
		while (index == arrWT.length)
		{
			try
			{
				//这里的wait是Object对象的方法,说明,该对象的线程wait,这时候是将锁放掉,等醒来时继续去找锁
				//与Thread的sleep不同的是,它要睡眠时,是抱着锁的
				this.wait();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		
		//生产的产品在容器中已经满了,它就wait,那么就要叫醒消费者消费
		this.notifyAll();
		arrWT[index] = wt;
		index++;
	}

	/**
	 * 消费都拿产品
	 * @return
	 */
	public synchronized WoTou pop()
	{
		//如果消费者消费空了,应该wait,而且只能用while,如果是if的话,抛出异常还会继续执行
		while (index == 0)
		{
			try
			{
				//这里的wait是Object对象的方法,说明,该对象的线程wait,这时候是将锁放掉,等醒来时继续去找锁
				//与Thread的sleep不同的是,它要睡眠时,是抱着锁的
				this.wait();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		//叫醒生产者生产
		this.notifyAll();
		index--;
		return arrWT[index];
	}
}

/**
 * 生产者
 * @author OwenWilliam
 *
 */
class Producer implements Runnable
{
	SyncStack ss = null;

	/**
	 * 生产者生产的产品所放处
	 * @param ss
	 */
	Producer(SyncStack ss)
	{
		this.ss = ss;
	}

	public void run()
	{
		for (int i = 0; i < 20; i++)
		{
			WoTou wt = new WoTou(i);
			ss.push(wt);
			System.out.println("生产了:" + wt);
			try
			{
				Thread.sleep((int) (Math.random() * 200));
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

/**
 * 消费者
 * @author OwenWilliam
 *
 */
class Consumer implements Runnable
{
	SyncStack ss = null;

	/**
	 * 消费者要从哪拿产品
	 * @param ss
	 */
	Consumer(SyncStack ss)
	{
		this.ss = ss;
	}

	public void run()
	{
		for (int i = 0; i < 20; i++)
		{
			WoTou wt = ss.pop();
			System.out.println("消费了: " + wt);
			try
			{
				Thread.sleep((int) (Math.random() * 1000));
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}