Java中啥是接口回调?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中啥是接口回调?相关的知识,希望对你有一定的参考价值。
可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。
实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。
Java语言特点
Java看起来设计得很像C++,但是为了使语言小和容易熟悉,设计者们把C++语言中许多可用的特征去掉了,这些特征是一般程序员很少使用的。
例如,Java不支持go to语句,代之以提供break和continue语句以及异常处理。Java还剔除了C++的操作符过载(overload)和多继承特征,并且不使用主文件,免去了预处理程序。因为Java没有结构,数组和串都是对象,所以不需要指针。
参考技术A 什么是接口回调?1.接口回调是什么[2]?接口回调是指:可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。看下面示例。interface People void peopleList();class Student implements People public void peopleList() System.out.println("I’m a student.");class Teacher implements People public void peopleList() System.out.println("I’m a teacher.");public class Example public static void main(String args[]) People a; //声明接口变量a=new Student(); //实例化,接口变量中存放对象的引用a.peopleList(); //接口回调a=new Teacher(); //实例化,接口变量中存放对象的引用a.peopleList(); //接口回调结果:I’m a student.I’m a teacher.再来看看向上转型(upcasting)的概念。 2.什么是向上转型[1]?Shape s=new Circle();这里,创建了一个Circle对象,并把得到的引用立即赋值给Shape。通过继承,Circle就是一种Shape。假设你调用基类方法(它已在导出类中被覆盖):s.draw();由于后期绑定(多态),将会正确调用Circle.draw()方法。 3.向上转型与接口回调的区别看似向上转型和接口回调是一回事。看下面两句话,均出自Thinking in Java。使用接口的核心原因:为了能够向上转型为多个基类型[1]。即利用接口的多实现,可向上转型为多个接口基类型(具体见《抽象与接口》章节6)。从实现了某接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。(此句摘自Thinking in Java 3rd 接口与内部类一章)所以,我认为,这两个概念是从两个方面来解释一个行为。接口回调的概念,强调使用接口来实现回调对象方法使用权的功能(下一章节详细分析)。而向上转型则牵涉到多态和运行期绑定的范畴。 4.用 Java 接口实现回调函数的等价功能熟悉 MS-Windows 和 X Window System 事件驱动编程模型的开发人员,习惯于传递在某种事件发生时调用(即“回调”)的函数指针。Java 的面向对象模型目前并不支持方法指针,Java 的接口支持提供了一种获得回调的等价功能的机制。其技巧就是:定义一个简单接口,并在该接口中声明我们要调用的方法。假定我们希望在某个事件发生时得到通知。我们可以定义一个接口:InterestingEvent.javapackage org.zj.sample;public interface InterestingEvent public void interestingEvent ();这使得我们可以控制实现该接口的类的任何对象。因此,我们不必关心任何外部类型信息。发出事件信号的类必须等待实现了 InterestingEvent 接口的对象,并在适当时候调用 interestingEvent() 方法。EventNotifier.javapackage org.zj.sample;public class EventNotifier private InterestingEvent ie; private boolean somethingHappened; public EventNotifier(InterestingEvent event) ie = event; // 保存事件对象以备后用。 somethingHappened = false; // 还没有要报告的事件。 public void doWork() if (somethingHappened) // 检查设置的谓词。 ie.interestingEvent();// 通过调用接口的这个方法发出事件信号。 public void setHappened()//设置谓词。 somethingHappened=true; 在上例中,使用 somethingHappened 谓词来跟踪是否应触发事件。希望接收事件通知的代码必须实现 InterestingEvent 接口,并将自身引用传递给事件通知程序。CallMe.javapackage org.zj.sample;public class CallMe implements InterestingEvent @SuppressWarnings("unused") private EventNotifier en; public CallMe() // 注意 EventNotifier (InterestingEvent event),应该传递一个接口类型。 // 而下面将this,即实现了InterestingEvent接口的CallMe实例传递给//EventNotifier。也就是所谓的接口回调了。 en = new EventNotifier(this); // 创建事件通知程序,并将自身引用传递给它。 // 为事件定义实际的处理程序。本回答被提问者采纳 参考技术B接口回调是指:可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。看下面示例。
void peopleList();
class Student implements People
public void peopleList()
System.out.println("I’m a student.");
class Teacher implements People
public void peopleList()
System.out.println("I’m a teacher.");
public class Example
public static void main(String args[])
People a; //声明接口变量
a=new Student(); //实例化,接口变量中存放对象的引用
a.peopleList(); //接口回调
a=new Teacher(); //实例化,接口变量中存放对象的引用
a.peopleList(); //接口回调
结果:
I’m a student.
I’m a teacher. 参考技术C 所谓回调,就是客户程序C调用服务程序S中的某个函数A,然后S又在某个时候反过来调用C中的某个函数B,对于C来说,这个B便叫做回调函数。
Java 中啥是快速、等待通知或忙等待?
【中文标题】Java 中啥是快速、等待通知或忙等待?【英文标题】:What is fast, wait notify or busy wait in Java?Java 中什么是快速、等待通知或忙等待? 【发布时间】:2014-07-25 05:43:12 【问题描述】:我知道使用忙等待不是一个好的编程习惯,最好尽可能使用同步对象(等待通知)。但我想知道是否准备好牺牲 cpu 周期,那么忙等待会更快还是等待通知?
我假设等待通知将涉及对同步对象的内在锁定,并且信号可能来自内核以唤醒线程,这使得这种方法比忙碌等待慢得多,在这种方法中,人们可以连续检查一个条件直到满意为止。一旦满足此条件(例如布尔值 == true),线程可能会退出忙等待。据我了解,我觉得忙等待应该更快。
如果我的论点有错误,如果其他人能分享他们的想法并纠正我,我将不胜感激。
【问题讨论】:
我可能错了,但是如果强制CPU循环,会不会比允许CPU空闲时慢? 我无法相信在现代系统中,你不是在写“金属”,而是在操作系统上运行并通过驱动程序,谁知道有多少操作系统代码等待 IO,那忙等待会比阻塞快得多。但是,嘿,如果有疑问,请尝试两种方法并衡量结果! 写一个实验真的比写一个问题要花更长的时间吗?我怀疑你会发现一个快速的实验让你毫无疑问地确定正确的路线。 使用wait()/notify()
将是有利的,因为一旦您notify()
,(其中一个)等待线程就会收到通知并开始执行。即,调用notify()
的线程将不会继续。如果您忙于等待,即使第二个线程设置了第一个线程正在等待的布尔标志,第二个线程仍会执行,直到其时间片完成然后第一个线程启动。 @其他人,如果我错了,请纠正我..
@TheLostMind notify()
不会导致调用线程挂起,也不会导致等待线程立即唤醒。相反,被通知线程仍然必须等待通知线程首先离开同步块。关于您的时间片问题:在多核系统中,线程可能真的并行运行!所以你的说法并不完全正确。
【参考方案1】:
实验表明,如果您忙于等待,那么您会比等待并通知(无论如何在我的硬件上)更快地看到标志。 (详情如下。)差异是非常非常非常非常小,因此这仅适用于非常罕见的应用程序。例如,股票交易应用程序,如果公司追求他们可以获得的任何优势(争相将他们的服务器放置在尽可能靠近交易所的地方,以便从交易所获得其网络馈送的微秒级改进等)可能会认为这种差异是值得的。我也可以想象一些科学应用。
在绝大多数应用程序中,差异实际上根本没有差异。
但是发生在 CPU 上的当然是其中一个核心硬钉:
就影响机箱上的其他进程和数据中心的功耗而言,这很糟糕。
所以:非常不情愿地使用,仅在真正重要的情况下使用。
数据(非常小的样本,但代码如下):
忙等待:10631 12350 15278 等待并通知:87299 120964 107204 达美:76668 108614 91926时间以 nano秒为单位。十亿分之一秒。上面的平均增量为 92403ns(0.092402667 毫秒,0.000092403 秒)。
BusyWait.java
:
public class BusyWait
private static class Shared
public long setAt;
public long seenAt;
public volatile boolean flag = false;
public static void main(String[] args)
final Shared shared = new Shared();
Thread notifier = new Thread(new Runnable()
public void run()
System.out.println("Running");
try
Thread.sleep(500);
System.out.println("Setting flag");
shared.setAt = System.nanoTime();
shared.flag = true;
catch (Exception e)
);
notifier.start();
while (!shared.flag)
shared.seenAt = System.nanoTime();
System.out.println("Delay between set and seen: " + (shared.seenAt - shared.setAt));
WaitAndNotify.java
:
public class WaitAndNotify
private static class Shared
public long setAt;
public long seenAt;
public boolean flag = false;
public static void main(String[] args)
(new WaitAndNotify()).test();
private void test()
final Shared shared = new Shared();
final WaitAndNotify instance = this;
Thread notifier = new Thread(new Runnable()
public void run()
System.out.println("Running");
try
Thread.sleep(500);
System.out.println("Setting flag");
shared.setAt = System.nanoTime();
shared.flag = true;
synchronized (instance)
instance.notify();
catch (Exception e)
);
notifier.start();
while (!shared.flag)
try
synchronized (this)
wait();
catch (InterruptedException ie)
shared.seenAt = System.nanoTime();
System.out.println("Delay between set and seen: " + (shared.seenAt - shared.setAt));
【讨论】:
while 循环通常不应该在同步块中,而不是相反吗? @isnot2bad:这取决于你在做什么。通常的规则是尽可能少地同步。在这种情况下,这意味着在wait
和 notify
调用附近。当然,理论上,我们从来没有真正循环(因为这需要我们的升旗代码之外的其他东西执行notify
),我们只是进入while
的主体一次,然后从不重复,所以对于这个测试代码来说,无论哪种方式都是一样的。
@T.J.Crowder 你是对的。另一件事是您正在修改同步块之外的共享标志。 (我知道它适用于您的情况,因为以下同步块确保 happens-before 关系,但仍然......)。顺便提一句。我稍微修改了您的代码并在循环中调用它以进行 JVM 预热,从而大大加快了繁忙等待示例的速度。
如果预期的等待时间长于线程的剩余时间片,那么旋转的任何优势都会完全消失。如果线程被换出,则首选等待/通知。
@JimMischel:是的,我可以发誓 OP 会说假设有足够的内核,但我现在看不到。我刚刚在我的四核机器上做了一个 20 秒的测试,该机器有三个虚拟机和运行在它上面的各种其他东西(Linux)。数字与我上面的半秒测试一致。但是我对时间片等的了解很少,并且其他核心没有远程加载,所以我猜线程被允许保留核心。【参考方案2】:
一个准备好在繁忙的等待中牺牲 CPU 周期,因为它更快。 忙等待是实时低延迟应用程序的示例。
有一个名为lmax disruptor 的框架是为伦敦证券交易所构建的,其中一个锁定策略是忙等待,这就是他们使用它的方式。
为了超快,最好在通知您的锁时浪费 cpu 周期来节省时间。
你对所有其他的东西都是正确的,如果你用谷歌搜索一下disruptor 并阅读他们的论文,你会得到更多的澄清。关于高性能和低延迟有太多话要说。
一个不错的博客是Mechanical Sympathy。
【讨论】:
【参考方案3】:视情况而定。有几种情况:
场景 A
如果在'busy-wait'中等待的是硬件操作(例如,从硬盘读取一个扇区到内存):
1) 硬件将执行操作。
2) 驱动程序将启动中断。
3) 操作将暂停实际进程(您的忙等待进程),保存任何 CPU 寄存器的实际值,中断将在其处理中覆盖。
4) 中断将被处理,修改任何指示数据可用的标志(在磁盘读取的情况下)。
5) 任何被覆盖的寄存器都将被恢复。
6) 您的流程将继续其流程。就在下一次迭代中,它将调用循环的 conthe 条件。例如,如果忙等待是:
while( !fileReady() )
...
fileReady() 方法将是一种在内部检查是否设置了具体标志(在 4 中修改的标志)的方法。 7)所以就在下一次迭代中,循环将进入并执行操作。
请记住,如果有另一个进程正在运行(操作系统进程、其他程序),它们会将您的进程置于进程尾部。此外,操作系统可以决定,鉴于您的进程已经使用了他可以使用的所有 CPU 周期(它花费了它的时间片),它的优先级将低于其他进入睡眠状态(而不是使用忙等待方法)的进程。需要等待某个条件。
结论。如果没有其他外部进程在同一个内核/CPU 中运行(非常不可能),则速度会更快。
场景 B
另一方面,如果busy-method正在等待另一个进程结束(或将任何变量设置为某个值,busy-wait会变慢。
1)busy-method 将在 CPU 中运行。由于其他进程没有运行,条件不能改变,所以busy-method会一直运行,直到CPU决定把CPU时间给其他进程。
2) 另一个进程将运行。如果这个过程花费的时间没有达到busy-wait过程需要的条件,则执行1),否则继续执行3)
3) 另一个(不是忙等待)进程仍会运行一段时间,直到 cpu 决定新的进程更改。
4)busy-method 会再次运行,但是现在条件已经满足,所以操作已经完成了。
结论:它比较慢,我们也拖慢了整个过程。
场景 C
如果我们有与 B 相同的场景,但有多个内核(每个内核中有一个进程)怎么办?
首先,请记住,即使您的 CPU 具有多个内核,也可能不允许您的程序使用多个内核。也许有一些操作系统或其他程序在使用它们。
其次,它不值这个价。请记住,进程必须进行通信以允许忙等待发现条件满足。这通常可以通过“最终”变量来完成,因此每次评估条件时都需要在同步块中输入(在进入循环之前不能锁定并且不能解锁它,因为在这种情况下另一个进程将无法更改变量。所以你需要这样的东西:
boolean exit = false;
while( exit==false )
synchronize(var)
if(var = CONDITIONS_MEET)
exit=true;
//operations...
¡¡ 但是等待通知会做一些类似的事情,并且更有效(在语言级别),不会浪费 CPU 周期,并使用良好的规则!!
结论:您正在使您的生活复杂化,而这不太可能会更快(非常非常不可能)。
最终结论:只有在非常非常简单的场景中,知道操作系统的具体细节和程序运行的环境,才可以考虑busy-wait方法.
我希望这将回答您的问题。如果有不清楚的地方,请不要犹豫。
【讨论】:
【参考方案4】:忙碌等待比正常等待通知更快。
但是为什么要等呢?因为一个生产者或其他线程会做一些工作,然后设置一个条件(或通知),这样你就可以真正摆脱忙/等待循环。 现在假设如果您的 Producer 正在执行一些繁重的任务,那么您实际上会通过忙等待来消耗它的 CPU 周期(主要是在单处理器系统中),这反过来可能会使您的系统整体变慢。
所以现在应该使用忙等待。 正如克劳迪奥所说,它主要用于低延迟系统。 但仍不能盲目使用。制作人时使用忙等待 正在以稳定的速度生产。 如果您的生产者以可变速率生产项目(通常由泊松分布证明),那么您可能应该使用等待通知。
通常情况下,高吞吐量和低延迟系统的最佳权衡是使用 忙等待一段时间,然后转到 wait()。 如果您的系统需要超低延迟,那么您可以进行许多优化 其中之一可能是忙碌等待。 但不应该是每个线程都在忙等待。确保只有一些消费者 可能在 N/2 左右 消费者正在忙着等待 N 是你的内核数 系统。浪费 CPU 周期可能会影响系统的整体性能和响应能力。 供您参考:即使是 Normal ReentrantLock 及其变体也应用这些策略。 IE 即 当一个线程调用 lock.lock() 时,它会尝试两次获取锁,然后再进入队列并等待锁被释放。对于低延迟系统,您甚至可以为特定场景定义自己的锁,在这些场景中它们会在进入队列之前尝试超过 10 次(它们将是所谓的自旋锁的变体)
【讨论】:
以上是关于Java中啥是接口回调?的主要内容,如果未能解决你的问题,请参考以下文章