Java多线程编程— 线程同步问题

一、   互斥锁的概念

       我们知道,一个进程中的多个线程是可以共享这个进程的系统资源的。如果多个线程同时修改统一个资源(对象)就会导致这个资源的不稳定性和某一时刻的不准确性。

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

       关键字synchronized来与对象的互斥锁来联系,当某个对象被synchronized修饰时,表明在任一时刻只能有一个线程访问该对象。


二、    synchronized的使用

  这里只介绍它的简单应用

  2.1  锁定某个对象




  2.2  锁定某个方法



  示例程序如下:

  

public class TestThreadSync implements Runnable {
	int i = 10;

	// 锁定method1方法
	public synchronized void method1() {
		i = 1000;
		try {
			Thread.sleep(5000);
			System.out.println("i= " + i);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void method2() {
		System.out.println(i);
	}

	public void run() {
		method1();
	}

	public static void main(String[] args) throws Exception {

		// 1、启动线程thread会调用method1方法,并锁定
		TestThreadSync ts = new TestThreadSync();
		Thread thread = new Thread(ts);
		thread.start();

		// 2、虽然锁定了method1方法,但我们依然可以访问没有的锁定的method2方法,并读出i现有的值
		Thread.sleep(1000);

		ts.method2();// 输出为1000,而不是10
	}
}

三、    死锁

  我们都学过著名的“哲学家就餐”的死锁问题,这里我们用一个简单示例来模拟一个死锁。示例程序如下:

  定义线程类:

/**
 * 线程死锁问题
 * 
 * zhipeng
 * 
 */
public class TestDeadLock implements Runnable {
	public int flag = 1;
	// 这里注意,一定要为静态
	public static Object o1 = new Object(), o2 = new Object();

	@Override
	public void run() {
		// 如果flag==1那么线程会先锁定对象01,然后休眠5毫秒,然后它想继续锁定对象o2,然后才能执行完并释放对象o1的锁
		if (flag == 1) {
			synchronized (o1) {
				try {
					Thread.sleep(500);
				} catch (Exception e) {
					e.printStackTrace();
				}

				synchronized (o2) {
					System.out.println(1);
				}
			}
		}
		// 如果flag==0那么线程会先锁定对象02,然后休眠5毫秒,然后它想继续锁定对象o1,然后才能执行完并释放对象02的锁
		if (flag == 0) {
			synchronized (o2) {
				try {
					Thread.sleep(500);
				} catch (Exception e) {
					e.printStackTrace();
				}

				synchronized (o1) {
					System.out.println(0);
				}
			}
		}
	}

}

  测试方法:

	/**
	 * zhipeng
	 * 
	 * 同时启动两个线程,形成死锁
	 */
	public static void main(String[] args) {

		TestDeadLock td1 = new TestDeadLock();
		TestDeadLock td2 = new TestDeadLock();

		td1.flag = 1;
		td2.flag = 0;

		Thread thread1 = new Thread(td1);
		Thread thread2 = new Thread(td2);
		// 两个线程并发执行,则thread1线程会先锁定对象o1,thread2线程会先锁定对象o2。然后thread1等待锁定对象o2,thread2等待锁定对象o1,这样两者就会形成一个死锁,导致程序无法执行
		thread1.start();
		thread2.start();

	}

  运行结果:



四、    生产者消费者问题

  生产者消费者问题,是经典的线程同步问题。这里其实主要涉及到多个线程间的相互通信,主要涉及到wait()、notify()—唤醒、notifyAll()—唤醒多个线程,这几个方法。

  4.1   wait与sleep的区别

  sleep为Thread类的静态方法,线程sleep时不会释放掉资源的锁,sleep一段时间后线程会自动醒来。

  wait是Object类的方法,线程wait是会释放掉资源的锁,但线程一旦wait 不会自动醒来,需要另一个线程来唤醒(notify/notifyAll)它。

  4.2   模拟生产者消费者(共有5个类)



  1、定义馒头类:

/**
 * 窝头类
 * 
 * @author wangzhipeng
 * 
 */
public class WoTo {
	private int id;

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

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

  2、定义馒头仓库类:

/**
 * 用数组模拟窝头的【仓库】类
 * 
 * @author wangzhipeng
 * 
 */
public class SyncStack {
	int index = 0;
	WoTo[] arrywWoTo = new WoTo[6];

	/**
	 * 生产者生产馒头
	 */
	public synchronized void push(WoTo woTo) {
		while (index == arrywWoTo.length) {
			try {
				this.wait();// 关键:1、如果仓库放满了馒头,则当前(生产者)线程wait睡眠
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notify();// 关键:2、然后唤醒消费者线程进行消费(如果有多个消费者则用notifyAll())
		arrywWoTo[index] = woTo;
		index++;
	}

	/**
	 * 消费者消费馒头
	 */
	public synchronized WoTo pop() {
		while (index == 0) {
			try {
				this.wait();// 关键:1、如果仓库没有了 馒头,则当前(消费者)线程wait睡眠
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notify();// 关键:2、然后唤醒生产者线程进行生产(如果有多个生产者则用notifyAll())
		index--;
		return arrywWoTo[index];
	}
}

     这个类是整个环节的核心,里面包含生产馒头、消费馒头两个方法。注意看里面的代码wait()与notify()。


  3、定义生产者(线程)类:

/**
 * 生产者线程类
 * 
 * @author wangzhipeng
 * 
 */
public class Producer implements Runnable {
	SyncStack ss = null;

	Producer(SyncStack ss) {
		this.ss = ss;
	}

	// 生产馒头,向馒头工厂SyncStack中存放馒头
	public void run() {
		for (int i = 0; i < 20; i++) {
			WoTo woTo = new WoTo(i);
			ss.push(woTo);
			System.out.println("生产了馒头:" + i);
			try {
				Thread.sleep((int) (Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

  4、定义消费者(线程)类:

/**
 * 消费者线程类
 * 
 * @author wangzhipeng
 * 
 */
public class Consumer implements Runnable {
	SyncStack ss = null;

	Consumer(SyncStack ss) {
		this.ss = ss;
	}

	// 消费馒头,从馒头工厂SyncStack中取出馒头
	public void run() {
		for (int i = 0; i < 20; i++) {
			WoTo woTo = ss.pop();
			System.out.println("消费了馒头:" + woTo);
			try {
				Thread.sleep((int) (Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

  5、测试程序类:

public class ProducerConsumer {

	/**
	 * 生产者-消费者,测试类
	 * 
	 */
	public static void main(String[] args) {
		SyncStack ss = new SyncStack();// 初始化馒头工厂类
		Producer p = new Producer(ss);// 初始化1个生产者(可以多个)
		Consumer c = new Consumer(ss);// 初始化1个消费者

		new Thread(p).start();// 启动生产者线程类,开始生产馒头
		new Thread(c).start();// 启动消费者线程类,开始消费馒头
	}

}

五、    总结

  多线程操作时我们需要遵循一个原则:允许多个线程同时读,不允许多个线程同时写。如果需要多线程操作,那么我们就需要考虑清楚如何加锁,要避免死锁问题;还要屡清楚多个线程间如何通信,即线程同步问题等。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值