多线程学习笔记(3)----------------------synchronized、lock锁,生产者消费者模型、线程池

目录

一、synchronizzed同步方法和同步块

二、死锁

三、lock锁

四、synchronized与lock对比

五、生产者消费者模型

1、wait()

2、notify()

3、notifyAll()

4、消费者生产者模型

5、线程池

 


一、synchronizzed同步方法和同步块

  • 首先看下多线程造成的线程不安全案例一
package com.daiy.syn;

//不安全的多线程买票
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket, "张三").start();
        new Thread(buyTicket, "李四").start();
        new Thread(buyTicket, "王五").start();
    }

}

class BuyTicket implements Runnable {

    //一共10张票
    private int num = 20;
    boolean flag = true;

    @Override
    public void run() {

        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void buy() throws InterruptedException {
        //判断是否还有票
        if (num <= 0) {
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + "买了第" + num-- + "张票");
    }

}

上面案例为一个买票案例,从结果看出,当3个人同时买票,会出现-1或者票多次卖的问题,此时在买票方法上加上synchronized,则会解决这个问题

  • 多线程造成的线程安全案例二(多个对象) 
package com.daiy.syn;

//线程不安全的并发取钱
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(200, "招商账户");

        Bank zs = new Bank(account, 50, "张三");
        Bank ls = new Bank(account, 100, "李四");

        zs.start();
        ls.start();

    }
}

class Account {
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}


//synchronized默认锁的时this类本身
class Bank extends Thread {
    Account account;//账户
    int drawMoney;//取的钱
    int comeToHandMoney;//到手的钱

    public Bank(Account account, int drawMoney, String name) {
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }

    @Override
    public void run() {
            //取钱时先判断钱是否够
            if (account.money - drawMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "钱不够,不可以取!");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //钱够了就扣除钱,得到当前卡内余额
            account.money = account.money - drawMoney;
            //到手的钱
            comeToHandMoney = comeToHandMoney + drawMoney;

            System.out.println(account.name + "卡里的余额为:" + account.money);
            System.out.println(Thread.currentThread().getName() + "到手里得到钱:" + comeToHandMoney);


    }
}

此案例为两人同时对同一账户进行取钱操作,可以看出100的钱取出来150的问题,造成了线程不安全

由于取钱方法是在Bank类中,但是取钱方法中实际是对Account对象进行操作,由于synchronizzed默认锁的时this类本身,我们在取钱方法上直接使用synchronizzed是不行的,需要用到synchronizzed块进行操作       【synchronizzed锁的对象是变化的量,需要增删改的对象

//synchronized默认锁的时this类本身
class Bank extends Thread {
    Account account;//账户
    int drawMoney;//取的钱
    int comeToHandMoney;//到手的钱

    public Bank(Account account, int drawMoney, String name) {
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }

    @Override
    public void run() {

/****************************将代码放synchronized块中************************************/
        synchronized (account) {
            //取钱时先判断钱是否够
            if (account.money - drawMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "钱不够,不可以取!");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //钱够了就扣除钱,得到当前卡内余额
            account.money = account.money - drawMoney;
            //到手的钱
            comeToHandMoney = comeToHandMoney + drawMoney;

            System.out.println(account.name + "卡里的余额为:" + account.money);
            System.out.println(Thread.currentThread().getName() + "到手里得到钱:" + comeToHandMoney);
        }


    }
}
 

此时,就没有100块钱取出150的问题

二、死锁

package com.daiy.syn;

//死锁测试 多个线程互相拥抱着对方的资源,形成僵局,造成死锁
public class DeadLock {
    public static void main(String[] args) {
        Learn zs = new Learn(1, "张三");
        Learn ls = new Learn(2, "李四");
        zs.start();
        ls.start();
    }
}

//书本
class Book {

}

//笔
class Pen {

}


class Learn extends Thread {
    static Book book = new Book();
    static Pen pen = new Pen();
    int choose;//选择
    String name;//姓名

    public Learn(int choose, String name) {
        this.choose = choose;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            GoodGoodStudy();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //互相持有对方的锁,需要拿到对方的资源
    public void GoodGoodStudy() throws InterruptedException {
        if (this.choose == 1) {
            synchronized (book) {//获得书本的锁
                System.out.println(this.name + "获得书本的锁");
                Thread.sleep(1000);
                synchronized (pen) {//获得笔的锁
                    System.out.println(this.name + "获得笔的锁");
                }
            }
        } else {
            synchronized (pen) {//获得笔的锁
                System.out.println(this.name + "获得笔的锁");
                Thread.sleep(1000);
                synchronized (book) {//获得书本的锁
                    System.out.println(this.name + "获得书本的锁");
                }
            }
        }
    }
}

运行上代码,可以看到服务没有停止,代码已经僵持住了不会继续往下走了,这就造成了死锁

产生死锁的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

修改上方代码如下,让资源得到释放而不是互相持有对方需要的锁 

package com.daiy.syn;

//死锁测试 多个线程互相拥抱着对方的资源,形成僵局,造成死锁
public class DeadLock {
    public static void main(String[] args) {
        Learn zs = new Learn(1, "张三");
        Learn ls = new Learn(2, "李四");
        zs.start();
        ls.start();
    }
}

//书本
class Book {

}

//笔
class Pen {

}


class Learn extends Thread {
    static Book book = new Book();
    static Pen pen = new Pen();
    int choose;//选择
    String name;//姓名

    public Learn(int choose, String name) {
        this.choose = choose;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            GoodGoodStudy();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //互相持有对方的锁,需要拿到对方的资源
    public void GoodGoodStudy() throws InterruptedException {
        if (this.choose == 1) {
            synchronized (book) {//获得书本的锁
                System.out.println(this.name + "获得书本的锁");
                Thread.sleep(1000);
            }
            synchronized (pen) {//获得笔的锁
                System.out.println(this.name + "获得笔的锁");
            }
        } else {
            synchronized (pen) {//获得笔的锁
                System.out.println(this.name + "获得笔的锁");
                Thread.sleep(1000);
            }
            synchronized (book) {//获得书本的锁
                System.out.println(this.name + "获得书本的锁");
            }
        }
    }
}

 

三、lock锁

案例一的买票,通过lock锁 也可以解决线程安全问题

package com.daiy.lock;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        BuyTicket2 buyTicket = new BuyTicket2();
        new Thread(buyTicket, "张三").start();
        new Thread(buyTicket, "李四").start();
        new Thread(buyTicket, "王五").start();
    }
}

class BuyTicket2 implements Runnable {

    //一共10张票
    private int num = 10;
    boolean flag = true;
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        while (flag) {

            try {
                lock.lock(); //加锁
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//释放锁
            }

        }
    }

    public void buy() throws InterruptedException {
        //判断是否还有票
        if (num <= 0) {
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + "买了第" + num-- + "张票");

    }
}

 

四、synchronized与lock对比

  •  lock是显式锁,需要手动开启和关闭锁,synchronized是隐式锁,出了作用域自动释放

  • lock只有代码块锁,synchronized锁有方法锁和代码块锁

  • 使用lock锁,jvm将花费较少的时间来调度线程,性能更好

优先使用顺序,lock>同步代码块>同步方法

五、生产者消费者模型

1、wait()

1、wait()是Object里面的方法,而不是Thread里面的。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。
2、wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常,wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁。
package com.daiy.TestPc;



/**       wait方法测试
 * 1.wait()是Object里面的方法,而不是Thread里面的。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。
 * 2.wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常。
 * wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁。
 */
public class TestWait implements Runnable {
    private final Object object = new Object();

    @Override
    public void run() {
        synchronized (object) {
            System.out.println("线程执行开始。。。");
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行结束。。。");
        }
    }

    public static void main(String[] args) {
        TestWait testWait = new TestWait();
        Thread thread = new Thread(testWait);
        thread.start();
    }
}

 

从上图我们可以看出线程调用了wait()方法后一直在等待,不会继续往下执行。wait()一旦执行,除非接收到唤醒操作或者是异常中断,否则不会继续往下执行。

2、notify()

1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行,
  调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁,
  如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。
2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。
package com.daiy.TestPc;

public class TestWait2 {

    public static void main(String[] args) throws InterruptedException {
        Testnotify testnotify = new Testnotify();
        Thread thread1 = new Thread(testnotify);
        thread1.start();
        Thread.sleep(1000);
        testnotify.setFlag(false);
        Thread thread2 = new Thread(testnotify);
        thread2.start();
    }
}

class Testnotify implements Runnable {


    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            testwait();
        } else {
            testnotify();
        }
    }

    public void testwait() {
        synchronized (this) {
            try {
                System.out.println("线程开始执行。。。");
                Thread.sleep(1000);
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行结束。。。");
        }
    }

    /**
     * 1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行,
     * 调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁,
     * 如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。
     * 2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。
     */
    public void testnotify() {
        synchronized (this) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.notify();
        }
    }
}
 

从上图可以看出,在调用notify()方法之后,线程又继续了

3、notifyAll()

notifyAll是唤醒所有等待的线程 

package com.daiy.TestPc;

public class TestWait3 {
    public static void main(String[] args) {
        TestNotifyAll testNotifyAll = new TestNotifyAll();
        Thread thread1 = new Thread(testNotifyAll, "线程1");
        thread1.start();

        Thread thread2 = new Thread(testNotifyAll, "线程2");
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testNotifyAll.setFlag(false);
        Thread thread3 = new Thread(testNotifyAll, "线程3");
        thread3.start();
    }
}

class TestNotifyAll implements Runnable {

    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            testwait();
        } else {
            testnotify();
        }
    }

    public void testwait() {
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + "-->开始执行。。。");
                Thread.sleep(1000);
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->执行结束。。。");
        }
    }

    public void testnotify() {
        synchronized (this) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.notifyAll();
        }
    }
}

4、消费者生产者模型

 

package com.daiy.TestPc.test;


/**
 * 生产者消费者模型   货物类 goods  商场商品库存类shop  生产者类producer  消费者类consumer
 */
//测试类
public class TestPc {
    public static void main(String[] args) {
        Shop shop = new Shop();
        new Thread(new Producer(shop), "生产者").start();
        new Thread(new Consumer(shop), "消费者").start();
    }

}

//货物类 goods
class Goods {
    //货物id
    private int id;

    public Goods() {

    }

    public Goods(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

//商场货物库存类shop
class Shop {
    Goods goods;
    boolean flag;

    //生产者生产货物
    public synchronized void producerSave(Goods goods) throws InterruptedException {
        //flag为true时,商场货物库存充足,不需要生产者生产,应该令生产者等待,通知消费者可以消费
        if (flag) {
            System.out.println("货物库存充足,欢迎购买...");
            this.wait();
        }
        //库存不充足,生产者应当生产货物存到商场中
        System.out.println(Thread.currentThread().getName() + ">>在生产货物,货物id为" + goods.getId());
        //生产货物了,设置flag为true

        this.goods = goods;
        flag = true;
        //取消线程等待,通知消费者可以消费了
        this.notifyAll();

    }


    //商场出售商品,消费者消费
    public synchronized void consumerBuy() throws InterruptedException {
        //flag为false时,商场货物库存不足,消费者不可以直接消费,等待
        if (flag == false) {
            System.out.println("货物库存暂时不足,请稍后购买...");
            this.wait();

        }
        System.out.println(Thread.currentThread().getName() + ">>在购买货物,货物id为" + goods.getId());
        //货物卖出去了,设置商品类为空,flag为false
        this.goods = null;
        flag = false; // 已经没有商品了,可以通知生产者了(生产者在等待)
        this.notifyAll(); // 通知所有生产者无需等待,没有商品了,需要生产商品了
        
    }
}

//生产者
class Producer implements Runnable {
    private Shop shop;


    public Producer(Shop shop) {
        this.shop = shop;
    }

    @Override
    public void run() {
        // 循环放货物到shop商场里
        int i = 0;
        while (i++ < 10) {
            try {
                // 生产货物,存到商场
                Thread.sleep(1000);
                this.shop.producerSave(new Goods(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer implements Runnable {

    private Shop shop;


    public Consumer(Shop shop) {
        this.shop = shop;
    }

    @Override
    public void run() {
        // 循环取从shop商场里取货物
        int i = 0;
        while (i++ <= 10) {
            try {
                // 购买货物
                Thread.sleep(2000);
                this.shop.consumerBuy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行上代码,结果显示

5、线程池

package com.daiy.demo9;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//线程池测试
public class TestPool {
    public static void main(String[] args) {
        //创建服务,创建线程池 参数为线程池大小
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //执行
        executorService.execute(new ThreadTest());
        executorService.execute(new ThreadTest());
        executorService.execute(new ThreadTest());
        executorService.execute(new ThreadTest());

        //关闭连接
        executorService.shutdown();
    }
}


class ThreadTest extends Thread {


    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->在玩线程池");
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值