Java线程并发协作与任务定时调度

简介: Java线程并发协作与任务定时调度

  多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。

什么是生产者?

     生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。

什么是消费者?

     消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。

什么是缓冲区?

 消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。

图11-17 生产者消费者示意图

     缓冲区是实现并发的核心,缓冲区的设置有3个好处:

实现线程的并发协作

有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。

解耦了生产者和消费者

  生产者不需要和消费者直接打交道。

解决忙闲不均,提高效率

 生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。

生产者与消费者模式:

public class TestProduce {
    public static void main(String[] args) {
        SyncStack sStack = new SyncStack();// 定义缓冲区对象;
        Shengchan sc = new Shengchan(sStack);// 定义生产线程;
        Xiaofei xf = new Xiaofei(sStack);// 定义消费线程;
        sc.start();
        xf.start();
    }
}
class Mantou {// 馒头
    int id;
    Mantou(int id) {
        this.id = id;
    }
}
class SyncStack {// 缓冲区(相当于:馒头筐)
    int index = 0;
    Mantou[] ms = new Mantou[10];
    public synchronized void push(Mantou m) {
        while (index == ms.length) {//说明馒头筐满了
            try {
               //wait后,线程会将持有的锁释放,进入阻塞状态;
               //这样其它需要锁的线程就可以获得锁;
                this.wait();
                //这里的含义是执行此方法的线程暂停,进入阻塞状态,
                //等消费者消费了馒头后再生产。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 唤醒在当前对象等待池中等待的第一个线程。
        //notifyAll叫醒所有在当前对象等待池中等待的所有线程。
        this.notify();
        // 如果不唤醒的话。以后这两个线程都会进入等待线程,没有人唤醒。
        ms[index] = m;
        index++;
    }
    public synchronized Mantou pop() {
        while (index == 0) {//如果馒头筐是空的;
            try {
                //如果馒头筐是空的,就暂停此消费线程(因为没什么可消费的嘛)。
                this.wait();                //等生产线程生产完再来消费;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        index--;
        return ms[index];
    }
}
class Shengchan extends Thread {// 生产者线程
    SyncStack ss = null;
    public Shengchan(SyncStack ss) {
        this.ss = ss;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("生产馒头:" + i);
            Mantou m = new Mantou(i);
            ss.push(m);
        }
    }
}
class Xiaofei extends Thread {// 消费者线程;
    SyncStack ss = null;
    public Xiaofei(SyncStack ss) {
        this.ss = ss;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Mantou m = ss.pop();
            System.out.println("消费馒头:" + i);
        }
    }
}

执行结果如图11-18所示:

线程并发协作总结:

     线程并发协作(也叫线程通信),通常用于生产者/消费者模式,情景如下:

 1. 生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

     2. 对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。

 3. 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。

     4. 在生产者消费者问题中,仅有synchronized是不够的。

       · synchronized可阻止并发更新同一个共享资源,实现了同步;

  · synchronized不能用来实现不同线程之间的消息传递(通信)。

     5. 那线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:

   6. 以上方法均是java.lang.Object类的方法;

     都只能在同步方法或者同步代码块中使用,否则会抛出异常。

注:

  在实际开发中,尤其是“架构设计”中,会大量使用这个模式。 对于初学者了解即可,如果晋升到中高级开发人员,这就是必须掌握的内容。

任务定时调度:

   通过Timer和Timetask,我们可以实现定时启动某个线程。

java.util.Timer

  在这种实现方式中,Timer类作用是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其它线程的。

java.util.TimerTask

   TimerTask类是一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力。

在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。

java.util.Timer的使用:

import java.util.Timer;
import java.util.TimerTask;
public class TestTimer {
    public static void main(String[] args) {
        Timer t1 = new Timer();//定义计时器;
        MyTask task1 = new MyTask();//定义任务;
        t1.schedule(task1,3000);  //3秒后执行;
        //t1.schedule(task1,5000,1000);//5秒以后每隔1秒执行一次!
        //GregorianCalendar calendar1 = new GregorianCalendar(2010,0,5,14,36,57); 
        //t1.schedule(task1,calendar1.getTime()); //指定时间定时执行; 
    }
}
class MyTask extends TimerTask {//自定义线程类继承TimerTask类;
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("任务1:"+i);
        }
    }
}

执行结果如图所示:

 运行以上程序时,可以感觉到在输出之前有明显的延迟(大概就是3秒!)。还有几个方法,我注释掉了,大家自己试试吧!


     在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间需要完全独立的话,最好还是一个Timer启动一个TimerTask实现。


注:


     实际开发中,我们可以使用开源框架quanz,更加方便的实现任务定时调度。实际上,quanz底层原理就是我们这里介绍的内容。

目录
相关文章
消息中间件 缓存 监控
36 0
|
17天前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
17天前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
21天前
|
Rust 并行计算 安全
揭秘Rust并发奇技!线程与消息传递背后的秘密,让程序性能飙升的终极奥义!
【8月更文挑战第31天】Rust 以其安全性和高性能著称,其并发模型在现代软件开发中至关重要。通过 `std::thread` 模块,Rust 支持高效的线程管理和数据共享,同时确保内存和线程安全。本文探讨 Rust 的线程与消息传递机制,并通过示例代码展示其应用。例如,使用 `Mutex` 实现线程同步,通过通道(channel)实现线程间安全通信。Rust 的并发模型结合了线程和消息传递的优势,确保了高效且安全的并行执行,适用于高性能和高并发场景。
31 0
|
30天前
|
存储 监控 Java
|
30天前
|
存储 Java
Java 中 ConcurrentHashMap 的并发级别
【8月更文挑战第22天】
34 5
|
30天前
|
存储 算法 Java
Java 中的同步集合和并发集合
【8月更文挑战第22天】
21 5
|
28天前
|
缓存 Java 调度
【Java 并发秘籍】线程池大作战:揭秘 JDK 中的线程池家族!
【8月更文挑战第24天】Java的并发库提供多种线程池以应对不同的多线程编程需求。本文通过实例介绍了四种主要线程池:固定大小线程池、可缓存线程池、单一线程线程池及定时任务线程池。固定大小线程池通过预设线程数管理任务队列;可缓存线程池能根据需要动态调整线程数量;单一线程线程池确保任务顺序执行;定时任务线程池支持周期性或延时任务调度。了解并正确选用这些线程池有助于提高程序效率和资源利用率。
34 2
|
30天前
|
Java 开发者
【编程高手必备】Java多线程编程实战揭秘:解锁高效并发的秘密武器!
【8月更文挑战第22天】Java多线程编程是提升软件性能的关键技术,可通过继承`Thread`类或实现`Runnable`接口创建线程。为确保数据一致性,可采用`synchronized`关键字或`ReentrantLock`进行线程同步。此外,利用`wait()`和`notify()`方法实现线程间通信。预防死锁策略包括避免嵌套锁定、固定锁顺序及设置获取锁的超时。掌握这些技巧能有效增强程序的并发处理能力。
19 2
|
21天前
|
开发框架 Android开发 iOS开发
跨平台开发的双重奏:Xamarin在不同规模项目中的实战表现与成功故事解析
【8月更文挑战第31天】在移动应用开发领域,选择合适的开发框架至关重要。Xamarin作为一款基于.NET的跨平台解决方案,凭借其独特的代码共享和快速迭代能力,赢得了广泛青睐。本文通过两个案例对比展示Xamarin的优势:一是初创公司利用Xamarin.Forms快速开发出适用于Android和iOS的应用;二是大型企业借助Xamarin实现高性能的原生应用体验及稳定的后端支持。无论是资源有限的小型企业还是需求复杂的大公司,Xamarin均能提供高效灵活的解决方案,彰显其在跨平台开发领域的强大实力。
26 0