Java之线程的同步与死锁

处理线程的同步与死锁的核心问题在于每个线程对象轮番抢占共享资源带来的问题。

1. 同步问题的引入:多个线程同时卖票

class MyThread implements Runnable{
	//卖票的总票数
	private int ticket=5;
	@Override
	public void run() {
		while(this.ticket>0) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("当前线程:"+Thread.currentThread().getName()+"---还有"+(--this.ticket)+"张票");
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		//实例化MyThread类的对象
		MyThread myThread=new MyThread();
		//创建第一个线程
		Thread thread1=new Thread(myThread,"线程1");
		thread1.start();
		//创建第二个线程
		Thread thread2=new Thread(myThread,"线程2");
		thread2.start();
		//创建第三个线程
		Thread thread3=new Thread(myThread,"线程3");
		thread3.start();
	}
}

运行结果如下所示:

当前线程:线程3---还有4张票
当前线程:线程2---还有3张票
当前线程:线程1---还有2张票
当前线程:线程2---还有1张票
当前线程:线程3---还有0张票
当前线程:线程1---还有-1张票

可以看出:票数出现了负数,这就是线程不同步产生的问题,称这种问题为不同步操作(异步操作),即线程不安全操作。

异步处理:效率高,但线程不安全,会产生不正确数据。

同步处理:效率低,但线程安全,不会产生不正确数据。

2. 同步处理(synchronized关键字实现同步处理)

        所谓的同步指的是所有的线程不是一起进入到方法中执行,而是按照顺序一个一个执行,synchronized关键字就是主要用于同步处理。使用synchronized关键字处理同步问题有两种模式:

(1)使用同步代码块

如果使用同步代码块必须设置一个要锁定的对象,所以一般锁定的是当前对象:this!下面利用同步代码块实现多个线程同时卖票的同步处理问题!

class MyThread implements Runnable{
	//卖票的总票数
	private int ticket=5;
	@Override
	public void run() {
		for(int i=0;i<this.ticket;i++){
			synchronized(this) { //同步代码块,有且只有一个线程能进入
				if(this.ticket>0) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("当前线程:"+Thread.currentThread().getName()+"---还有"+(--this.ticket)+"张票");
				}
			}
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		//实例化MyThread类的对象
		MyThread myThread=new MyThread();
		//创建第一个线程
		Thread thread1=new Thread(myThread,"线程1");
		thread1.start();
		//创建第二个线程
		Thread thread2=new Thread(myThread,"线程2");
		thread2.start();
		//创建第三个线程
		Thread thread3=new Thread(myThread,"线程3");
		thread3.start();
	}
}

运行结果如下所示:

当前线程:线程2---还有4张票
当前线程:线程2---还有3张票
当前线程:线程2---还有2张票
当前线程:线程1---还有1张票
当前线程:线程3---还有0张票

可以看出:以上通过同步代码块可以实现同步处理,但这一种方法是在方法里进行上锁,即进入方法里依然可以有多个线程,下面在方法上进行上锁。

(2)同步方法

使用同步方法后,有且仅有一个线程能进入到该方法中。下面利用同步方法实现多个线程同时卖票的同步处理问题!

class MyThread implements Runnable{
	//卖票的总票数
	private int ticket=5;
	@Override
	public void run() {
		for(int i=0;i<this.ticket;i++){
			this.sale(); //同步方法
		}
	}
	//同步方法
	public synchronized void sale() {  
		if(this.ticket>0) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("当前线程:"+Thread.currentThread().getName()+"---还有"+(--this.ticket)+"张票");
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		//实例化MyThread类的对象
		MyThread myThread=new MyThread();
		//创建第一个线程
		Thread thread1=new Thread(myThread,"线程1");
		thread1.start();
		//创建第二个线程
		Thread thread2=new Thread(myThread,"线程2");
		thread2.start();
		//创建第三个线程
		Thread thread3=new Thread(myThread,"线程3");
		thread3.start();
	}
}

运行结果如下所示:

当前线程:线程2---还有4张票
当前线程:线程1---还有3张票
当前线程:线程1---还有2张票
当前线程:线程3---还有1张票
当前线程:线程2---还有0张票

可以看出:通过同步方法也可以实现同步操作。

3. 关于synchronized的额外说明

(1)观察synchronized关键字多个对象时,代码如下:

class Demo{
	public synchronized void test() {
		System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
	}
}
class MyThread implements Runnable{
	@Override
	public void run() {
		Demo demo=new Demo();
		demo.test();
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		//实例化MyThread类的对象
		MyThread myThread=new MyThread();
		//创建第一个线程
		Thread thread1=new Thread(myThread,"线程1");
		thread1.start();
		//创建第二个线程
		Thread thread2=new Thread(myThread,"线程2");
		thread2.start();
		//创建第三个线程
		Thread thread3=new Thread(myThread,"线程3");
		thread3.start();
	}
}

运行结果如下所示:

当前线程:线程1开始
当前线程:线程2开始
当前线程:线程3开始
当前线程:线程1结束
当前线程:线程2结束
当前线程:线程3结束

从上述代码和运行结果中可以看出,使用了同步方法但并没有起到同步处理的作用。实际上,synchronized(this)以及非static的synchronized方法只能防止多个线程同时执行同一个对象的同步代码段,即synchronized(this)锁住的只是括号里的当前对象而不是代码,非static的synchronized方法所的也是对象本身this而不是代码。

下面使用全局锁修改上述代码,从而实现同步处理:

(1)使用类,锁同步代码块

class Demo{
	public void test() {
		synchronized(Demo.class) {
			System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
		}
	}
}
class MyThread implements Runnable{
	@Override
	public void run() {
		Demo demo=new Demo();
		demo.test();
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		//实例化MyThread类的对象
		MyThread myThread=new MyThread();
		//创建第一个线程
		Thread thread1=new Thread(myThread,"线程1");
		thread1.start();
		//创建第二个线程
		Thread thread2=new Thread(myThread,"线程2");
		thread2.start();
		//创建第三个线程
		Thread thread3=new Thread(myThread,"线程3");
		thread3.start();
	}
}

某次运行结果如下所示:

当前线程:线程2开始
当前线程:线程2结束
当前线程:线程3开始
当前线程:线程3结束
当前线程:线程1开始
当前线程:线程1结束

(2)使用类方法,锁同步方法

class Demo{
	public static synchronized void test() {
			System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
	}
}
class MyThread implements Runnable{
	@Override
	public void run() {
		Demo demo=new Demo();
		demo.test();
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		//实例化MyThread类的对象
		MyThread myThread=new MyThread();
		//创建第一个线程
		Thread thread1=new Thread(myThread,"线程1");
		thread1.start();
		//创建第二个线程
		Thread thread2=new Thread(myThread,"线程2");
		thread2.start();
		//创建第三个线程
		Thread thread3=new Thread(myThread,"线程3");
		thread3.start();
	}
}

某次运行结果如下所示:

当前线程:线程3开始
当前线程:线程3结束
当前线程:线程2开始
当前线程:线程2结束
当前线程:线程1开始
当前线程:线程1结束

总结:

(1)synchronized(Demo.class)锁住的是代码段,锁住了多个对象的同一方法,使用这种全局锁,锁的是类而不是this。

(2)static synchronized的类同步方法,由于static方法可以直接使用类名.方法名调用,方法中不能使用this对象,所以它锁的不是this,而是类的Class对象。故static synchronized的类同步方法也相当于全局锁,也相当于锁住了代码段。

4. JDK1.5提供的Lock锁

先来看看Lock接口的源码中的抽象方法:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

我们只注意Lock接口的两个抽象方法lock()和unlock(),lock()是上锁,unlock()是解锁。

再来看看实现Lock接口的ReentrantLock类的部分源码:

public class ReentrantLock implements Lock, java.io.Serializable {
    ...
    public void lock() {
        sync.lock();
    }
    public void unlock() {
        sync.release(1);
    }
    ...
}

使用ReentrantLock进行多线程卖票的同步处理,代码如下所示:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyThread implements Runnable{
	//卖的总票数
	private int ticket=10;
	//实例化RentrantLock的对象
	private Lock ticketLock=new ReentrantLock();
	@Override
	public void run() {
		for(int i=0;i<this.ticket;i++) {
			//调用lock()方法上锁
			ticketLock.lock();
			try {
				if(this.ticket>0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("当前线程:"+Thread.currentThread().getName()+"---还有"+(--this.ticket)+"张票");
				}
			} finally {
				//调用unlock()方法解锁
				ticketLock.unlock();
			}
			
		}
		
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException{
		//实例化MyThread类的对象
		MyThread myThread=new MyThread();
		//创建第一个线程
		Thread thread1=new Thread(myThread,"线程1");
		thread1.start();
		//创建第二个线程
		Thread thread2=new Thread(myThread,"线程2");
		thread2.start();
		//创建第三个线程
		Thread thread3=new Thread(myThread,"线程3");
		thread3.start();
	}
}

某次运行结果如下所示:

当前线程:线程2---还有9张票
当前线程:线程2---还有8张票
当前线程:线程2---还有7张票
当前线程:线程2---还有6张票
当前线程:线程2---还有5张票
当前线程:线程1---还有4张票
当前线程:线程1---还有3张票
当前线程:线程1---还有2张票
当前线程:线程3---还有1张票

通过运行结果可知,利用Lock接口的lock()和unlock()方法实现了多线程的同步问题。

Lock锁仅限于了解,提倡使用synchronized实现多线程的同步问题。

5. 死锁

同步的本质在于:一个线程等待另外一个线程执行完毕后才可以继续执行,但是若几个线程出现了相互等待的问题,那么就会造成死锁。下面示范死锁的出现:

class Pen {
	private String pen = "笔" ;
	public String getPen() {
		return pen;
	}
} 
class Book {
	private String book = "本" ;
	public String getBook() {
		return book;
	}
} 
public class Test1 {
	private static Pen pen = new Pen() ;
	private static Book book = new Book() ;
	public static void main(String[] args) {
		new Test1().deadLock();
	} 
	public void deadLock() {
		Thread thread1 = new Thread(new Runnable() { // 笔线程
			@Override
			public void run() {
				synchronized (pen) {
					System.out.println(Thread.currentThread()+" :我有笔,我就不给你");
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (book) {
						System.out.println(Thread.currentThread()+" :把你的本给我!");
					}
				}
			}
		},"Pen") ;
		Thread thread2 = new Thread(new Runnable() { // 本子线程
			@Override
			public void run() {
				synchronized (book) {
					System.out.println(Thread.currentThread()+" :我有本子,我就不给你!");
					synchronized (pen) {
						System.out.println(Thread.currentThread()+" :把你的笔给我!");
					}
				}
			}
		},"Book") ;
		thread1.start();
		thread2.start();
	}
}

运行结果如下:

Thread[Pen,5,main] :我有笔,我就不给你
Thread[Book,5,main] :我有本子,我就不给你!
该运行并没有结束,进入死循环,一直等待下去。

故死锁一旦出现,整个程序就将会等待执行,所以产生死锁属于严重性问题。过多的同步会造成死锁,对于资源的上锁一定不要成环,一旦成环就会产生死锁!

猜你喜欢

转载自blog.csdn.net/tongxuexie/article/details/80151562