线程学习(一)

线程学习


案例描述:你和你女朋友公用一张银行卡,你向银行卡中存钱、你女朋友取钱

初级应用:使用Runnable实现线程


public class Test {
    public static void main(String[] args) {
        //1创建银行卡
        BankCard card = new BankCard();//0
        //2创建存钱和取钱功能
        AddMoney add = new AddMoney(card);
        SubMoney sub = new SubMoney(card);
        //3创建线程对象
        Thread shaobao = new Thread(add, "少泊");
        Thread xiaohai = new Thread(sub, "小海");
        //4启动
        shaobao.start();
        xiaohai.start();
    }
}



public class BankCard {
    private double money;

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}



public class AddMoney implements Runnable {
    private BankCard card;

    public AddMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        //存
        for (int i = 0; i < 10; i++) {
            card.setMoney(card.getMoney() + 1000);
            System.out.println(Thread.currentThread().getName() + "存了1000元,余额是:" + card.getMoney());
        }
    }
}



public class SubMoney implements Runnable {
    private BankCard card;

    public SubMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        //取
        for (int i = 0; i < 10; i++) {
            if (card.getMoney() >= 1000) {
                card.setMoney(card.getMoney() - 1000);
                System.out.println(Thread.currentThread().getName() + "取了1000,余额是:" + card.getMoney());
            } else {
                System.out.println("余额不足,及时存钱");
                i--;
            }
        }
    }
}


高级应用

该程序可以进行简化,具体实现如下:

public static void main(String[] args) {
        BankCard card = new BankCard();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    card.setMoney(card.getMoney() + 1000);
                    System.out.println(Thread.currentThread().getName() + "存了1000,余额:" + card.getMoney());
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    if (card.getMoney() >= 1000) {
                        card.setMoney(card.getMoney() - 1000);
                        System.out.println(Thread.currentThread().getName() + "取了1000,余额:" + card.getMoney());
                    } else {
                        System.out.println("余额不足");
                        i--;
                    }
                }
            }
        }).start();
    }



public class BankCard {
    private double money;

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}


以上代码虽然实现了案例要求,但是会存在 一方还没有存钱,而另一方在不知情的情况下多次去进行取钱的情况,如图:

在这里插入图片描述

为了解决上述问题,我们可以引进线程同步的概念。

同步代码块的使用(同步:Synchronized:有等待):

语法:
	synchronized(锁) {
		//需要访问临界资源的代码段
	}

	说明:
	a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
	b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
	c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义
public class AddMoney implements Runnable {
    private BankCard card;

    public AddMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized (card) {

                card.setMoney(card.getMoney() + 1000);
                System.out.println(Thread.currentThread().getName() + "存了1000,余额是:" + card.getMoney());

            }
        }

    }
}



public class SubMoney implements Runnable{
    private BankCard card;

    public SubMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            synchronized (card) {
                if (card.getMoney() >= 1000) {
                    card.setMoney(card.getMoney() - 1000);
                    System.out.println(Thread.currentThread().getName() + "取了1000,余额是:" + card.getMoney());
                } else {
                    System.out.println("余额不足");
                    i--;
                }
            }
        }
    }
}



public class BankCard {
    private double money;

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}



public static void main(String[] args) {
        BankCard card=new BankCard();
        AddMoney add=new AddMoney(card);
        SubMoney sub=new SubMoney(card);
        new Thread(add,"少泊").start();
        new Thread(sub,"发海").start();
    }

在这里插入图片描述

实现线程同步也可以使用接口Lock来实现,而且更加有效:

从jdk1.5之后加入新的接口 Lock,ReentrantLock(可重入锁)是Lock接口的实现类。
	通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
	注意:最好将 unlock的操作放到finally块中
	通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
public class AddMoney implements Runnable {
    private BankCard card;
    private Lock lock;

    public AddMoney(BankCard card, Lock lock) {
        this.card = card;
        this.lock=lock;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++) {
            lock.lock();
            try {
                card.setMoney(card.getMoney() + 1000);
                System.out.println(Thread.currentThread().getName() + "存了1000,余额是:" + card.getMoney());
            } finally {
                lock.unlock();
            }
        }
    }
}



public class SubMoney implements Runnable{
    private BankCard card;
    private Lock lock;

    public SubMoney(BankCard card,Lock lock) {
        this.card = card;
        this.lock=lock;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
                lock.lock();
            try {
                if (card.getMoney() >= 1000) {
                    card.setMoney(card.getMoney() - 1000);
                    System.out.println(Thread.currentThread().getName() + "取了1000,余额是:" + card.getMoney());
                } else {
                    System.out.println("余额不足");
                    i--;
                }
            } finally {
                lock.unlock();
            }

        }
    }
}



public class BankCard {
    private double money;

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}


public static void main(String[] args) {
        BankCard card=new BankCard();
        Lock lock=new ReentrantLock();
        AddMoney add=new AddMoney(card,lock);
        SubMoney sub=new SubMoney(card,lock);
        new Thread(add,"少泊").start();
        new Thread(sub,"发海").start();
    }

线程同步虽然有效的解决了在没存钱时多次取钱的问题,但可以通过运行结果发现,在加入线程锁之后,只能先等一个人做完全部存钱行为后才可以进行取钱。那如何实现有顺序的执行两个不同的操作线程呢?

这时候我们就可以开始学习线程之间的通信,此时我们更改案例需求,将其变为:

你和你朋友公用一张银行卡,你向卡中存钱,你朋友取钱,保证你存一笔,然后取一笔,再存一笔,再取一笔,顺序执行存取操作

实现功能:使用线程通信。
在jdk1.5之前有三个方法实现线程通信:

wait(): 等待,线程执行这个方法进入等待队列(和锁有关,一个锁对应一个等待队列), 需要被唤醒

notify(): 通知唤醒,从等待队列中随机唤醒一个线程

notifyAll():全部唤醒,把等待队列中所有的线程都唤醒
public class AddMoney implements Runnable{

    private BankCard card;

    public AddMoney(BankCard card) {
        this.card = card;
    }


    @Override
    public void run() {
        for(int i=0;i<10;i++){
            card.save();
        }
    }
}



public class SubMoney implements Runnable {

    private BankCard card;

    public SubMoney(BankCard card) {
        this.card = card;
    }


    @Override
    public void run() {
        for(int i=0;i<10;i++){
            card.take();
        }
    }
}



public class BankCard {
    private double money;
    private boolean flag;//true 有钱,可以取   false没钱 ,可以存


    //存钱
    public synchronized void save(){ //this
        if(flag){//有钱
            try {
                this.wait();//等待,释放cpu,释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        money=money+1000;
        System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
        flag=true;//修改标记
        this.notify();//唤醒取钱线程

    }


    //取钱
    public synchronized void take(){//this
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        
        
        money=money-1000;
        System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
        flag=false;//修改标记为false
        this.notify();//通知存钱线程
    }
}



public static void main(String[] args) {
        //1创建card
        BankCard card=new BankCard();
        //2创建存钱和取钱
        AddMoney add=new AddMoney(card);
        SubMoney sub=new SubMoney(card);
        //3创建线程对象
        Thread shaobo=new Thread(add, "少泊");
        Thread fengjie=new Thread(sub,"凤姐");

        shaobo.start();
        fengjie.start();

    }

运行结果如下:

在这里插入图片描述

显然此时我们已经完成了案例的要求,但是此时存钱和取钱的线程各只有一个,那如果我们再添加更多的存取线程后,上述代码还能很好的执行么?我们试着向代码中存取线程各自增加一个新的线程

public static void main(String[] args) {
        //1创建card
        BankCard card=new BankCard();
        //2创建存钱和取钱
        AddMoney add=new AddMoney(card);
        SubMoney sub=new SubMoney(card);
        //3创建线程对象
        Thread shaobo=new Thread(add, "少泊");
        Thread fengjie=new Thread(sub,"凤姐");

        Thread fahai=new Thread(add, "发海");
        Thread bingbing=new Thread(sub, "冰冰");

        shaobo.start();
        fengjie.start();
        fahai.start();
        bingbing.start();

    }

此时的运行结果会出现一个新的状况:

在这里插入图片描述

可以发现,再增加新的线程后,出现了余额为负数的情况,而这种情况明显是错误的,这时该怎么解决呢?

首先,我们先思考这种情况是怎么产生的?

我们用以下步骤来描述问题的发生:

1.存钱线程1 抢到CPU,执行代码,存钱成功,修改标记为true
2.存钱线程2 抢到CPU,执行代码,等待并释放CPU和锁
3.存钱线程1 抢到CPu,执行代码,等待并释放CPU和锁
4.取钱线程1 抢到CPU,执行代码,取钱成功,修改标记为false,唤醒存钱线程   1
5.存钱线程1 抢到CPU,执行代码,存钱成功,修改标记为true,唤醒存钱线程2
6.存钱线程2 抢到CPU,执行代码,存钱成功,修改标记为true,唤醒

我们可以看到上述步骤中,在步骤5和步骤6中,都发生了存钱成功,并且修改标记为true的现象。说明问题就在此处发生,而发生的原因是:在线程进行判断后,不满足条件的线程进入休眠,而当它被唤醒时,判断条件已经完成,线程将会顺序执行代码,即判断标记失效了,我们该怎么处理该问题呢?

解决方法:将条件语句改为while循环

public class BankCard {
    private double money;
    private boolean flag;//true 有钱,可以取   false没钱 ,可以存


    //存钱
    public synchronized void save(){ //this
        while (flag){//有钱
            try {
                this.wait();//等待,释放cpu,释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        money=money+1000;
        System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
        flag=true;//修改标记
        this.notify();//唤醒取钱线程

    }


    //取钱
    public synchronized void take(){//this
        while (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }



        money=money-1000;
        System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
        flag=false;//修改标记为false
        this.notify();//通知存钱线程
    }
}

在解决这个问题后,我们再次执行代码,会发现执行正常了,但是多次执行后,会产生一个新的问题:死锁

在这里插入图片描述

此时我们需要重新分析代码的执行过程:

1.存钱线程1 抢到CPU,执行代码,存钱成功,修改标记为true,唤醒
2.存钱线程2 抢到CPU,执行代码,等待并释放CPU和锁
3.存钱线程1 抢到CPU,执行代码,等待并释放CPU和锁
4.取钱线程1 抢到CPU,执行代码,取钱成功,修改标记为false,唤醒存钱线程1
5.取钱线程2 抢到CPU,执行代码,等待并释放CPU和锁
6.取钱线程1 抢到CPU,执行代码,等待并释放CPU和锁
7.存钱线程1 抢到CPU,执行代码,存钱成功,修改标记为true,唤醒存钱线程2
8.存钱线程2 抢到CPU,执行代码,等待并释放CPU和锁
9.存钱线程1 抢到CPU,执行代码,等待并释放CPU和锁

从上述执行步骤可以看到,在执行过程中,四个线程都进入了休眠状态,从而造成死锁现象。

这时,我们就需要使用到notifyAll()方法

notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态

		·是Object的方法
		·调用方式:对象.notify();
		·表示唤醒 对象 所标记外边在等待的一个线程	

notifyAll():全部唤醒

		·是Object的方法
		·调用方式:对象.notifyAll()
		·表示唤醒  对象 所标记外边等待的所有线程
		

代码修改为:

public class BankCard {
    private double money;
    private boolean flag;//true 有钱,可以取   false没钱 ,可以存


    //存钱
    public synchronized void save(){ //this
        while (flag){//有钱
            try {
                this.wait();//等待,释放cpu,释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        money=money+1000;
        System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
        flag=true;//修改标记
        this.notifyAll();//唤醒取钱线程

    }


    //取钱
    public synchronized void take(){//this
        while (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }



        money=money-1000;
        System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
        flag=false;//修改标记为false
        this.notifyAll();//通知存钱线程
    }
}
money=money+1000;
    System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
    flag=true;//修改标记
    this.notifyAll();//唤醒取钱线程

}


//取钱
public synchronized void take(){//this
    while (!flag){
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }



    money=money-1000;
    System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
    flag=false;//修改标记为false
    this.notifyAll();//通知存钱线程
}

}


这时,一个较为完善的代码就修改成功了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值