JAVA 线程与锁

多线程

有时候,我们需要下载文件,使用单线程的时候只能由这个线程进行下载任务,不能完全发挥所有计算机资源,如果我们使用的是多线程的话,那么我们就可以把文件进行分隔,每个线程同时下载文件的一部分,这样就能充分发挥计算机资源,这是多线程中的一个典型应用了。


某些特殊情况下,可能多个线程需要用到计算机的同一个资源,访问这些资源的代码段叫做临界区,这些资源叫做临界资源。比如我们生活中常遇到的存钱和取钱问题,账户里的钱是一个数字,我们不能同时对其进行取和存操作,否则会发生错误。在存的时候不允许进行取钱。取的时候 也不能存钱。这里就用到了锁。

package test;


public class Demo {
	public int money = 10000;

	public static void main(String[] Args) {
		new Demo().test();
	}

	private void test() {
		// TODO Auto-generated method stub
		Thread t1 = new Thread(new drawmoney());
		Thread t2 = new Thread(new savemoney());
		t1.start();
		t2.start();

	}

	class drawmoney implements Runnable {
		@Override
		public void run() {
			while (true) {
				money--;
				System.out.println("取钱:" + money);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			// TODO Auto-generated method stub

		}

	}

	class savemoney implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			while (true) {
				money++;
				System.out.println("存钱:" + money);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		}

	}

}
如同上面这个程序按照常理讲结果应该是取钱:9999,然后存钱10000这样交替进行。我们实际运行后会发现不是我们想的这样:结果如下

存钱:10000
取钱:9999
取钱:9999
存钱:10000
存钱:10001
完全乱了。两次取钱都是9999,第一次存钱 应该是10001,也不对

这时候我们该用上传说中的锁了
package test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
	Lock drawmoney = new ReentrantLock();
	public int money = 10000;

	public static void main(String[] Args) {
		new Demo().test();
	}

	private void test() {
		// TODO Auto-generated method stub
		Thread t1 = new Thread(new drawmoney());
		Thread t2 = new Thread(new savemoney());
		t1.start();
		t2.start();
	}

	class drawmoney implements Runnable {
		@Override
		public void run() {
			while (true) {
				drawmoney.lock();
				money--;
				System.out.println("取钱:" + money);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				drawmoney.unlock();
			}
		}
	}

	class savemoney implements Runnable {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while (true) {
				drawmoney.lock();
				money++;
				System.out.println("存钱:" + money);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				drawmoney.unlock();
			}

		}

	}

}

再看一下加完锁后的运行结果:
取钱:9999
取钱:9998
取钱:9997
取钱:9996
存钱:9997
存钱:9998
存钱:9999
看上去虽然不是交替进行,但这结果确实是对的。这种情况可能是因为取钱的线程是先创建开始 运行的,并且时间片轮转的时候并不能做到平等运行。在这里java也为这种情况提供了解决办法,java中有一个带有公平策略的锁。我们只要去 Lock drawmoney = new ReentrantLock(true);这行代码中加入true这个参数就可以了。
运行结果:
取钱:9999
存钱:10000
取钱:9999
存钱:10000
取钱:9999
存钱:10000
但是这种锁将大大降低性能,所以并不推荐使用。

死锁

如果我们在取钱这个线程里加一个条件,比如说只有钱大于10002时才能取钱 。这时当取钱获得了这个锁并开始进行操作的时候在这里卡住了,而存钱这个线程并没有获得锁又不能进行操作,这时死锁就发生了
class drawmoney implements Runnable {
		@Override
		public void run() {
			while (true) {
				drawmoney.lock();
				if (money >= 10002) {
					money--;
					System.out.println("取钱:" + money);
				}
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				drawmoney.unlock();
			}
		}
	}

这里的解决办法是用Condition这个类。看名字就知道这是为了新的条件而创造的,当一个线程因为条件不满足而无法继续执行的时候,通过这个条件对象,可是先放弃当前锁,让它加入等待集中,直到能给它有机会来重新满足条件的线程来解除它的阻塞。
package test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
	Lock drawmoney = new ReentrantLock();
	Condition changed = drawmoney.newCondition();
	public int money = 10000;

	public static void main(String[] Args) {
		new Demo().test();
	}

	private void test() {
		// TODO Auto-generated method stub
		Thread t1 = new Thread(new drawmoney());
		Thread t2 = new Thread(new savemoney());
		t1.start();
		t2.start();
	}

	class drawmoney implements Runnable {
		@Override
		public void run() {
			while (true) {
				drawmoney.lock();
				while (money < 10005) {
					try {
						changed.await();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				money--;
				System.out.println("取钱:" + money);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				drawmoney.unlock();
			}
		}
	}

	class savemoney implements Runnable {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while (true) {
				drawmoney.lock();
				money++;
				System.out.println("存钱:" + money);
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				changed.signalAll();
				drawmoney.unlock();
				
			}

		}

	}

}

需要注意的是,调用signalall()这个方法时,应该在获得锁对象之后 。还有一个sign()方法 ,这个方法是从等待集中随机选择一个线程来解除阻塞。而且它并不会立即激活这个线程,而是等待线程通过竞争实现对对象的访问。

上面这种加锁的方法显得有些笨重 ,其实java中也已经提供了更轻便的方法 。synchronized关键字来解决这个问题。不过如果我们知道了上述方法的话,那么对于synchronized的理解会更加容易。
对于刚才那种形式我们就可以写成如下程序
package test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
	public int money = 10000;
	Lock drawmoney = new ReentrantLock();

	public static void main(String[] Args) {
		new Demo().test();
	}

	private void test() {
		// TODO Auto-generated method stub
		Thread t1 = new Thread(new drawmoney());
		Thread t2 = new Thread(new savemoney());
		t1.start();
		t2.start();
	}

	class drawmoney implements Runnable {
		@Override
		public void run() {
			while (true) {
				synchronized (drawmoney) {
					money--;
					System.out.println("取钱:" + money);
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

			}
		}
	}

	class savemoney implements Runnable {
		@Override
		public void run() {
			// TODO Auto-generated method stub
			while (true) {
				synchronized (drawmoney) {
					money++;
					System.out.println("存钱:" + money);
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

				}

			}

		}

	}

}
同时也允许用在方法上。
比如public synchronized void drawmoney()这样之后只允许一个线程来调用这个方法了。

读/写锁

声明方法 :
ReentrantReadWriteLock lock2 = new ReentrantReadWriteLock();
	Lock readmoney = lock2.readLock();
	Lock writemoney = lock2.writeLock();

用法和上面一样,需要注意的是。
读锁可以被多个读操作共用。但会排斥所有写操作。
写锁则和普通锁一样,排斥所有其它操作。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值