JAVA 线程与锁

来源:互联网 发布:淘宝网哪家手机店靠谱 编辑:程序博客网 时间:2024/06/04 23:33

多线程

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


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

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 stubThread t1 = new Thread(new drawmoney());Thread t2 = new Thread(new savemoney());t1.start();t2.start();}class drawmoney implements Runnable {@Overridepublic void run() {while (true) {money--;System.out.println("取钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// TODO Auto-generated method stub}}class savemoney implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stubwhile (true) {money++;System.out.println("存钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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 stubThread t1 = new Thread(new drawmoney());Thread t2 = new Thread(new savemoney());t1.start();t2.start();}class drawmoney implements Runnable {@Overridepublic void run() {while (true) {drawmoney.lock();money--;System.out.println("取钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}drawmoney.unlock();}}}class savemoney implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stubwhile (true) {drawmoney.lock();money++;System.out.println("存钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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 {@Overridepublic 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 blocke.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 stubThread t1 = new Thread(new drawmoney());Thread t2 = new Thread(new savemoney());t1.start();t2.start();}class drawmoney implements Runnable {@Overridepublic void run() {while (true) {drawmoney.lock();while (money < 10005) {try {changed.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}money--;System.out.println("取钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}drawmoney.unlock();}}}class savemoney implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stubwhile (true) {drawmoney.lock();money++;System.out.println("存钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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 stubThread t1 = new Thread(new drawmoney());Thread t2 = new Thread(new savemoney());t1.start();t2.start();}class drawmoney implements Runnable {@Overridepublic void run() {while (true) {synchronized (drawmoney) {money--;System.out.println("取钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}class savemoney implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stubwhile (true) {synchronized (drawmoney) {money++;System.out.println("存钱:" + money);try {Thread.sleep(500);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}}
同时也允许用在方法上。
比如public synchronized void drawmoney()这样之后只允许一个线程来调用这个方法了。

读/写锁

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

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



0 0
原创粉丝点击