首页
友链
关于
留言板
推荐
电脑壁纸
在线音乐
解压游戏
Search
1
【菜谱】咖喱鸡肉盖饭
3,704 阅读
2
pycharm生成requirements.txt
1,103 阅读
3
ubuntu安装docker和docker-compose
1,018 阅读
4
celery worker启动报错:AttributeError: 'str' object has no attribute 'items'
739 阅读
5
django后台管理界面美化
638 阅读
学习
python
linux
java
菜谱
随笔
登录
Search
标签搜索
笔记
linux
python
java
实用教程
原创
编程
多线程
docker
tomcat
thread
锁
线程同步
ssm
maven
Markdown
Windows
菜谱
生活笔记
django
Dawn
累计撰写
39
篇文章
累计收到
8
条评论
首页
栏目
学习
python
linux
java
菜谱
随笔
页面
友链
关于
留言板
推荐
电脑壁纸
在线音乐
解压游戏
搜索到
3
篇与
多线程
的结果
2021-07-27
java-生产者消费者问题以及解决办法
1.生产者消费者问题概述生产者消费者问题是多线程同步的一个经典问题。生产者消费者同时使用一块缓冲区,生产者生产商品放入缓冲区,消费者从缓冲区取出商品。我们需要保证的是,当缓冲区满时,生产者不可生产商品;当缓冲区为空时,消费者不可取出商品。这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。在生产者消费者问题中,仅有synchronized是不够的synchronized可阻止并发更新同一个共享资源,实现了同步synchronized不能用来实现不同线程之间的消息传递(通信)Java提供了几个方法解决线程之间的通信问题方法名作用wait()表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁wait(long timeout)指定等待的毫秒数notify()唤醒一个处于等待状态的线程notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException2.生产者消费者问题的解决办法2.1 解决思路采用某种机制保护生产者消费者之间的同步,有较高的效率,并且易于实现,代码的可控制性较好,属于常用的方式在生产者和消费者之间建立一个管道,管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。解决核心在于保证同一资源被多个线程并发访问时的完整性,常用的同步方法时采用信号或者加锁机制,保证资源在任意时刻至多被一个线程访问2.2 实现方法wait()和nofity()方法await()和signal()方法BlockingQueue阻塞队列方法管道法信号量2.3 代码实现2.3.1 wait()和nofity()方法当缓冲区已满时,生产者线程停止执行,放弃锁,使自己处于等状态,让其他线程执行;当缓冲区已空时,消费者线程停止执行,放弃锁,使自己处于等状态,让其他线程执行。当生产者向缓冲区放入一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态;当消费者从缓冲区取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。示例://测试生产者消费者模型--利用缓冲区解决 public class TestProductAndConsume1 { public static void main(String[] args) { SynContainer synContainer = new SynContainer(); new Product(synContainer).start(); new Consumer(synContainer).start(); } } //生产者 class Product extends Thread { SynContainer container; public Product(SynContainer container){ this.container = container; } @Override public void run() { for (int i = 0; i < 100; i++) { container.push(new Chicken(i)); System.out.println("生产了第" + i + "只鸡"); } } } //消费者 class Consumer extends Thread { SynContainer container; public Consumer(SynContainer container){ this.container = container; } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了" + container.pop().id + "只鸡"); } } } //产品 class Chicken { int id; public Chicken(int id) { this.id = id; } } //缓冲区 class SynContainer { //需要一个容器大小 Chicken[] chickens = new Chicken[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken){ //容器满了,等待消费者消费 if (count == chickens.length){ //通知消费者消费,生产者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果容器没有满,放入产品 chickens[count] = chicken; count++; //通知消费者消费 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop(){ //判断能否消费 if (count == 0){ //等待生产者生产,消费者消费 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Chicken chicken = chickens[count]; //吃完了,通知生产者生产 this.notifyAll(); return chicken; } }notify()方法可使所有正在等待队列中等待同一共享资源的"全部"线程从等待状态退出,进入可运行状态。此时,优先级最高的哪个线程最先执行,但也有可能是随机执行的,这要取决于JVM虚拟机的实现。即最终也只有一个线程能被运行,上述线程优先级都相同,每次运行的线程都不确定是哪个,后来给线程设置优先级后也跟预期不一样,主要看JVM具体实现。2.3.2 await()/signal()方法在JDK5中,用ReenteantLock和Condition可以实现等待/通知模型,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。示例:(在这里只需要改动缓冲区即仓库类)//缓冲区 class SynContainer { //需要一个容器大小 Chicken[] chickens = new Chicken[10]; //容器计数器 int count = 0; // 锁 private final Lock lock = new ReentrantLock(); // 仓库满的条件变量 private final Condition full = lock.newCondition(); // 仓库空的条件变量 private final Condition empty = lock.newCondition(); //生产者放入产品 public void push(Chicken chicken){ //获得锁 lock.lock(); try { //容器满了,等待消费者消费 if (count == chickens.length) { //通知消费者消费,生产者等待 try { full.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果容器没有满,放入产品 chickens[count] = chicken; count++; //通知消费者消费 empty.signalAll(); } finally { lock.unlock();//释放锁 } } //消费者消费产品 public Chicken pop(){ //获得锁 lock.lock(); Chicken chicken = null; try { //判断能否消费 if (count == 0) { //等待生产者生产,消费者消费 try { empty.await(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; chicken = chickens[count]; //吃完了,通知生产者生产 full.signalAll(); } finally { lock.unlock(); } return chicken; } }2.3.3 BlockingQueue阻塞队列方法BlockingQueue是JDk5新增内容,它是一个已经在内部实现了同步的队列,实现方式采用的是我们第2种await()/signal()方法。它可以在生成对象时指定容量大小,用于阻塞操作的是put()和take()方法。put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞。示例:(在这里只需要改动缓冲区即仓库类)//缓冲区 class SynContainer { // 仓库存储的载体 private LinkedBlockingQueue<Object> list = new LinkedBlockingQueue<>(10); //生产者放入产品 public void push(Chicken chicken) { try { list.put(chicken); } catch (InterruptedException e) { e.printStackTrace(); } } //消费者消费产品 public Chicken pop() { Chicken chicken = null; try { chicken = (Chicken) list.take(); } catch (InterruptedException e) { e.printStackTrace(); } return chicken; } }可能会出现put()或take()和System.out.println()输出不匹配的情况,是由于它们之间没有同步造成的。BlockingQueue可以放心使用,这可不是它的问题,只是在它和别的对象之间的同步有问题。2.3.4 Semaphore信号量Semaphonre是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可对象,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,比如数据库 连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。计数为0的Semphore是可以release的,然后就可以acquire(即一开始使线程阻塞从而完成其他执行)。示例:(在这里只需要改动缓冲区即仓库类)//缓冲区 class SynContainer { // 仓库存储的载体 private LinkedList<Object> list = new LinkedList<Object>(); // 仓库的最大容量 final Semaphore notFull = new Semaphore(10); // 将线程挂起,等待其他来触发 final Semaphore notEmpty = new Semaphore(0); // 互斥锁 final Semaphore mutex = new Semaphore(1); //生产者放入产品 public void push(Chicken chicken) { try { notFull.acquire(); mutex.acquire(); list.add(chicken); } catch (InterruptedException e) { e.printStackTrace(); }finally { mutex.release(); notFull.release(); } } //消费者消费产品 public Chicken pop() { Chicken chicken = null; try { notFull.acquire(); mutex.acquire(); chicken = (Chicken) list.remove(); } catch (InterruptedException e) { e.printStackTrace(); }finally { mutex.release(); notFull.release(); } return chicken; } }2.3.5 管道管道是一种特殊的流,用于不同线程间直接传送数据,一个线程发送数据到输出管道,另一个线程从输入管道读数据。inputStream.connect(outputStream)或outputStream.connect(inputStream)作用是使两个Stream之间产生通信链接,这样才可以将数据进行输入和输出。这种方式只适用于两个线程之间通信,不适合多个线程之间通信。2.4.5.1 PipedInoutStream/PipedOutputStream(操作字节流)示例://测试生产者消费者问题--管道-PipedInoutStream/PipedOutputStream(操作字节流) public class TestProductAndConsume5 { public static void main(String[] args) { Producer producer = new Producer(); Consumer consumer = new Consumer(); Thread thread1 = new Thread(producer); Thread thread2 = new Thread(consumer); try { producer.getPipedOutputStream().connect(consumer.getPipedInputStream()); thread2.start(); thread1.start(); } catch (IOException e) { e.printStackTrace(); } } } class Producer implements Runnable { private PipedOutputStream pipedOutputStream; public Producer() { pipedOutputStream = new PipedOutputStream(); } public PipedOutputStream getPipedOutputStream() { return pipedOutputStream; } @SneakyThrows @Override public void run() { try { for (int i = 0; i < 5; i++) { pipedOutputStream.write(("This is a test , ID = " + i + "!\n").getBytes()); } pipedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } class Consumer implements Runnable { private PipedInputStream pipedInputStream; public Consumer() { pipedInputStream = new PipedInputStream(); } public PipedInputStream getPipedInputStream() { return pipedInputStream; } @SneakyThrows @Override public void run() { int length = -1; byte[] buffer = new byte[1024]; try { while ((length = pipedInputStream.read(buffer)) != -1) { System.out.println(new String(buffer, 0, length)); } pipedInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }2.4.5.2 PipedReader/PipedWriter(操作字符流)示例与操作字节流基本一致,只是替换相应流,这里不再贴代码
2021年07月27日
139 阅读
0 评论
0 点赞
2021-07-27
java-Lock锁概述
1.Lock锁概述从JDK5.0开始,Java提供了更强大的线程同步机制--通过显式定义同步锁对象来实现同步。java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的时ReentrantLock,可以显式加锁、释放锁。Lock是一个接口,两个直接实现类:ReentrantLock(可重入锁),ReentrantReadWriteLock(读写锁)语法://定义锁 private final ReentrantLock lock = new ReentrantLock(); lock.lock();//加锁 try { //保证线程安全的代码 }finally { lock.unlock;//解锁 }示例:public class TestLock{ public static void main(String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); } } class TestLock2 implements Runnable{ int ticketNums = 10; //定义lock锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { lock.lock();//加锁 try{ if (ticketNums > 0){ try { Thread.sleep(1000); System.out.println(ticketNums--); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } }finally { lock.unlock();//解锁 } } } }2. Lock锁的API修饰符和类型方法描述voidlock()获得锁voidlockInterruptibly()获得锁,可中断。举个例子,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。booleantryLock()锁在空闲的时候才能获取锁(未获得锁不会等待)。举个例子:当两个线程同时通过lock.terLock()想获取某个锁时,假若此时线程A获取到了锁,而线程B不会等待,直接放弃获取锁。booleantryLock(Long time, TimeUnit unit)如果锁定可用,则此方法立即返回true如果锁不可用,则当前线程将被禁用以进行线程调度,并且在发生以下三种情况之一之前处于休眠状态: 1.当前线程获取锁 2.其它一些线程中断当前线程 3.等待时间过去了,返回falsevoidunlock()释放锁3.synchronized与Lock的对比Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放Lock只有代码块锁,synchronized有代码块锁和方法锁使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了资源) > 同步方法(在方法体之外)
2021年07月27日
123 阅读
0 评论
0 点赞
2021-07-27
java死锁简介
1.死锁的定义死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。即某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生"死锁"的问题。示例:public class TestDeadLock { public static void main(String[] args) { MakeUp makeUp1 = new MakeUp(0, "小灰"); MakeUp makeUp2 = new MakeUp(1, "小白"); makeUp1.start(); makeUp2.start(); } } class Lipstick{ } class Mirror{ } class MakeUp extends Thread { //需要的资源只有一份 static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice; //选择 String girlName; //使用化妆品的人 MakeUp(int choice, String girlName){ this.choice = choice; this.girlName = girlName; } @Override public void run() { //化妆 makeUp(); } private void makeUp(){ if (choice == 0){ synchronized (lipstick){//获得口红的锁 try { System.out.println(this.girlName + "获得口红的锁"); Thread.sleep(1000); synchronized (mirror){//1秒钟后获得镜子的锁 System.out.println(this.girlName + "获得镜子的锁"); } } catch (InterruptedException e) { e.printStackTrace(); } } // synchronized (mirror){//1秒钟后获得镜子的锁 // System.out.println(this.girlName + "获得镜子的锁"); // } }else{ synchronized (mirror){//获得镜子的锁 try { System.out.println(this.girlName + "获得镜子的锁"); Thread.sleep(2000); synchronized (lipstick){//1秒钟后获得口红的锁 System.out.println(this.girlName + "获得口红的锁"); } } catch (InterruptedException e) { e.printStackTrace(); } } // synchronized (lipstick){//1秒钟后获得口红的锁 // System.out.println(this.girlName + "获得口红的锁"); // } } } }2.产生死锁的四个必要条件互斥条件:一个资源每次只能被一个进程使用。请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。上面列出了死锁的四个必要条件,我们只要想办法破其中任意一个或多个条件就可以避免死锁发生。
2021年07月27日
67 阅读
0 评论
0 点赞