ฉันจะเรียกวิธีการบล็อกบางอย่างด้วยการหมดเวลาใน Java ได้อย่างไร


97

มีวิธีที่ดีมาตรฐานในการเรียกใช้วิธีการบล็อกด้วยการหมดเวลาใน Java หรือไม่? ฉันต้องการที่จะทำได้:

// call something.blockingMethod();
// if it hasn't come back within 2 seconds, forget it

ถ้ามันสมเหตุสมผล

ขอบคุณ.


1
ในการอ้างอิงโปรดดู Java Concurrency in Practice โดย Brian Goetz หน้า 126 - 134 โดยเฉพาะส่วน 6.3.7 "การ จำกัด เวลาในงาน"
สีน้ำตาล 22179

คำตอบ:


156

คุณสามารถใช้ Executor:

ExecutorService executor = Executors.newCachedThreadPool();
Callable<Object> task = new Callable<Object>() {
   public Object call() {
      return something.blockingMethod();
   }
};
Future<Object> future = executor.submit(task);
try {
   Object result = future.get(5, TimeUnit.SECONDS); 
} catch (TimeoutException ex) {
   // handle the timeout
} catch (InterruptedException e) {
   // handle the interrupts
} catch (ExecutionException e) {
   // handle other exceptions
} finally {
   future.cancel(true); // may or may not desire this
}

หากfuture.getไม่กลับมาใน 5 วินาทีมันจะพ่นไฟล์TimeoutException. การหมดเวลาสามารถกำหนดเป็นวินาทีนาทีมิลลิวินาทีหรือหน่วยใด ๆ ที่มีให้เป็นค่าคงที่ในTimeUnit.

ดูJavaDocสำหรับรายละเอียดเพิ่มเติม


13
วิธีการบล็อกจะยังคงทำงานต่อไปแม้จะหมดเวลาแล้วใช่ไหม
Ivan Dubrov

1
ขึ้นอยู่กับ future.cancel ขึ้นอยู่กับว่าวิธีการบล็อกกำลังทำอะไรอยู่ในขณะนั้นวิธีนี้อาจยุติหรือไม่ก็ได้
skaffman

4
ฉันจะส่งพารามิเตอร์ไปยังบล็อกเมธอด () ได้อย่างไร ขอบคุณ!
Robert A Henru

@RobertAHenru: สร้างคลาสใหม่ที่เรียกว่าBlockingMethodCallableซึ่งมี contructor ยอมรับพารามิเตอร์ที่คุณต้องการส่งผ่านblockingMethod()และเก็บไว้เป็นตัวแปรสมาชิก (อาจเป็นขั้นสุดท้าย) จากนั้นภายในcall()ส่งพารามิเตอร์เหล่านั้นไปยังไฟล์blockMethod().
Vite Falcon

1
สุดท้ายควรทำfuture.cancel(true)- วิธีการยกเลิก (บูลีน) ในประเภทอนาคต <Object> ใช้ไม่ได้กับอาร์กิวเมนต์ ()
Noam Manos

9

คุณสามารถตัดสายใน a FutureTaskและใช้ get () เวอร์ชันหมดเวลา

ดูhttp://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/FutureTask.html


1
FutureTask ไม่ได้เป็นแบบอะซิงโครนัสใช่หรือไม่ ด้วยตัวมันเองมันทำสิ่งต่างๆพร้อมกันคุณต้องรวมเข้ากับ Executor เพื่อหาพฤติกรรม asynch
skaffman


4

เป็นเรื่องดีมากที่ผู้คนพยายามนำสิ่งนี้ไปใช้ในหลาย ๆ ด้าน แต่ความจริงก็คือไม่มีทาง

นักพัฒนาส่วนใหญ่จะพยายามวางสายการบล็อกไว้ในชุดข้อความอื่นและมีอนาคตหรือตัวจับเวลา แต่ไม่มีวิธีใดใน Java ที่จะหยุดเธรดภายนอกได้นับประสากรณีเฉพาะบางกรณีเช่นเมธอด Thread.sleep () และ Lock.lockInterruptably () ที่จัดการเธรดการขัดจังหวะอย่างชัดเจน

ดังนั้นคุณมีเพียง 3 ตัวเลือกทั่วไป:

  1. วางสายการบล็อคของคุณไว้ในเธรดใหม่และหากหมดเวลาคุณเพียงแค่ดำเนินการต่อปล่อยให้เธรดนั้นแขวนไว้ ในกรณีนี้คุณควรตรวจสอบให้แน่ใจว่าเธรดถูกตั้งค่าเป็นเธรด Daemon วิธีนี้เธรดจะไม่หยุดแอปพลิเคชันของคุณจากการยุติ

  2. ใช้ Java API ที่ไม่ปิดกั้น ดังนั้นสำหรับเครือข่ายเช่นใช้ NIO2 และใช้วิธีการไม่บล็อก สำหรับการอ่านจากคอนโซลให้ใช้ Scanner.hasNext () ก่อนบล็อกเป็นต้น

  3. หากการโทรที่บล็อกของคุณไม่ใช่ IO แต่เป็นตรรกะของคุณคุณสามารถตรวจสอบซ้ำ ๆThread.isInterrupted()เพื่อตรวจสอบว่าถูกขัดจังหวะภายนอกหรือไม่และมีการโทรอีกครั้งในเธรดthread.interrupt()การบล็อก

หลักสูตรเกี่ยวกับภาวะพร้อมกันนี้https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

เรียนรู้พื้นฐานเหล่านั้นอย่างแท้จริงหากคุณต้องการเข้าใจวิธีการทำงานใน Java จริงๆแล้วมันพูดถึงข้อ จำกัด และสถานการณ์เฉพาะเหล่านั้นและวิธีดำเนินการเกี่ยวกับข้อ จำกัด เหล่านั้นในการบรรยาย

ฉันเองพยายามตั้งโปรแกรมโดยไม่ใช้การปิดกั้นการโทรให้มากที่สุด มีชุดเครื่องมือเช่น Vert.x ที่ทำให้การทำ IO เป็นเรื่องง่ายและมีประสิทธิภาพและไม่มีการดำเนินการ IO แบบอะซิงโครนัสและไม่ปิดกั้น

ฉันหวังว่ามันจะช่วยได้


3

นอกจากนี้ยังมีโซลูชัน AspectJ สำหรับสิ่งนั้นด้วยห้องสมุดด้าน jcabi

@Timeable(limit = 30, unit = TimeUnit.MINUTES)
public Soup cookSoup() {
  // Cook soup, but for no more than 30 minutes (throw and exception if it takes any longer
}

มันไม่สามารถรวบรัดได้มากกว่านี้ แต่คุณต้องขึ้นอยู่กับ AspectJ และแนะนำมันในวงจรการสร้างของคุณแน่นอน

มีบทความอธิบายเพิ่มเติม: จำกัด เวลาดำเนินการเมธอด Java


1
Thread thread = new Thread(new Runnable() {
    public void run() {
        something.blockingMethod();
    }
});
thread.start();
thread.join(2000);
if (thread.isAlive()) {
    thread.stop();
}

โปรดทราบว่าการหยุดนั้นเลิกใช้แล้วทางเลือกที่ดีกว่าคือการตั้งค่าสถานะบูลีนที่ระเหยได้ภายในการปิดกั้นวิธีการ () ตรวจสอบและออกดังนี้:

import org.junit.*;
import java.util.*;
import junit.framework.TestCase;

public class ThreadTest extends TestCase {
    static class Something implements Runnable {
        private volatile boolean stopRequested;
        private final int steps;
        private final long waitPerStep;

        public Something(int steps, long waitPerStep) {
            this.steps = steps;
            this.waitPerStep = waitPerStep;
        }

        @Override
        public void run() {
            blockingMethod();
        }

        public void blockingMethod() {
            try {
                for (int i = 0; i < steps && !stopRequested; i++) {
                    doALittleBit();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        public void doALittleBit() throws InterruptedException {
            Thread.sleep(waitPerStep);
        }

        public void setStopRequested(boolean stopRequested) {
            this.stopRequested = stopRequested;
        }
    }

    @Test
    public void test() throws InterruptedException {
        final Something somethingRunnable = new Something(5, 1000);
        Thread thread = new Thread(somethingRunnable);
        thread.start();
        thread.join(2000);
        if (thread.isAlive()) {
            somethingRunnable.setStopRequested(true);
            thread.join(2000);
            assertFalse(thread.isAlive());
        } else {
            fail("Exptected to be alive (5 * 1000 > 2000)");
        }
    }
}

1

ลองทำตามนี้ วิธีง่ายๆมากขึ้น รับประกันว่าหากบล็อกไม่ดำเนินการภายในเวลาที่กำหนด กระบวนการนี้จะยุติและแสดงข้อยกเว้น

public class TimeoutBlock {

 private final long timeoutMilliSeconds;
    private long timeoutInteval=100;

    public TimeoutBlock(long timeoutMilliSeconds){
        this.timeoutMilliSeconds=timeoutMilliSeconds;
    }

    public void addBlock(Runnable runnable) throws Throwable{
        long collectIntervals=0;
        Thread timeoutWorker=new Thread(runnable);
        timeoutWorker.start();
        do{ 
            if(collectIntervals>=this.timeoutMilliSeconds){
                timeoutWorker.stop();
                throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated.");
            }
            collectIntervals+=timeoutInteval;           
            Thread.sleep(timeoutInteval);

        }while(timeoutWorker.isAlive());
        System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds.");
    }

    /**
     * @return the timeoutInteval
     */
    public long getTimeoutInteval() {
        return timeoutInteval;
    }

    /**
     * @param timeoutInteval the timeoutInteval to set
     */
    public void setTimeoutInteval(long timeoutInteval) {
        this.timeoutInteval = timeoutInteval;
    }
}

ตัวอย่าง:

try {
        TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds
        Runnable block=new Runnable() {

            @Override
            public void run() {
                //TO DO write block of code 
            }
        };

        timeoutBlock.addBlock(block);// execute the runnable block 

    } catch (Throwable e) {
        //catch the exception here . Which is block didn't execute within the time limit
    }

1

ฉันให้รหัสที่สมบูรณ์แก่คุณที่นี่ แทนวิธีที่ฉันเรียกคุณสามารถใช้วิธีของคุณ:

public class NewTimeout {
    public String simpleMethod() {
        return "simple method";
    }

    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        Callable<Object> task = new Callable<Object>() {
            public Object call() throws InterruptedException {
                Thread.sleep(1100);
                return new NewTimeout().simpleMethod();
            }
        };
        Future<Object> future = executor.submit(task);
        try {
            Object result = future.get(1, TimeUnit.SECONDS); 
            System.out.println(result);
        } catch (TimeoutException ex) {
            System.out.println("Timeout............Timeout...........");
        } catch (InterruptedException e) {
            // handle the interrupts
        } catch (ExecutionException e) {
            // handle other exceptions
        } finally {
            executor.shutdown(); // may or may not desire this
        }
    }
}

0

สมมติว่าblockingMethodแค่นอนเป็นเวลามิลลิวินาที:

public void blockingMethod(Object input) {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

วิธีแก้ปัญหาของฉันคือการใช้wait()และsynchronizedเช่นนี้:

public void blockingMethod(final Object input, long millis) {
    final Object lock = new Object();
    new Thread(new Runnable() {

        @Override
        public void run() {
            blockingMethod(input);
            synchronized (lock) {
                lock.notify();
            }
        }
    }).start();
    synchronized (lock) {
        try {
            // Wait for specific millis and release the lock.
            // If blockingMethod is done during waiting time, it will wake
            // me up and give me the lock, and I will finish directly.
            // Otherwise, when the waiting time is over and the
            // blockingMethod is still
            // running, I will reacquire the lock and finish.
            lock.wait(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ดังนั้นคุณสามารถแทนที่

something.blockingMethod(input)

ถึง

something.blockingMethod(input, 2000)

หวังว่าจะช่วยได้


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.