วิธีการใช้งานแอปพลิเคชัน Java แบบอินสแตนซ์เดียว


89

บางครั้งฉันเห็นแอปพลิเคชั่นมากมายเช่น msn, windows media player และอื่น ๆ ที่เป็นแอปพลิเคชันอินสแตนซ์เดียว (เมื่อผู้ใช้ดำเนินการในขณะที่แอปพลิเคชันกำลังเรียกใช้แอปพลิเคชันใหม่จะไม่สร้างขึ้น)

ใน C # ฉันใช้Mutexคลาสสำหรับสิ่งนี้ แต่ฉันไม่รู้วิธีทำใน Java


วิธีง่ายๆกับ java NIO ดูตัวอย่างที่สมบูรณ์stackoverflow.com/a/20015771/185022
AZ_

คำตอบ:


62

ถ้าฉันเชื่อบทความนี้โดย:

มีอินสแตนซ์แรกที่พยายามเปิดซ็อกเก็ตการฟังบนอินเทอร์เฟซ localhost หากสามารถเปิดซ็อกเก็ตได้ให้ถือว่านี่เป็นอินสแตนซ์แรกของแอปพลิเคชันที่เปิดตัว หากไม่เป็นเช่นนั้นข้อสันนิษฐานก็คืออินสแตนซ์ของแอปพลิเคชันนี้กำลังทำงานอยู่ อินสแตนซ์ใหม่ต้องแจ้งให้อินสแตนซ์ที่มีอยู่ทราบว่ามีการพยายามเปิดใช้งานแล้วจึงออก อินสแตนซ์ที่มีอยู่จะเข้ามาแทนที่หลังจากได้รับการแจ้งเตือนและเริ่มเหตุการณ์ไปยังผู้ฟังที่จัดการการกระทำ

หมายเหตุ: Aheกล่าวถึงในความคิดเห็นว่าการใช้InetAddress.getLocalHost()อาจเป็นเรื่องยุ่งยาก:

  • ไม่ทำงานตามที่คาดไว้ในสภาพแวดล้อม DHCP เนื่องจากที่อยู่ที่ส่งคืนขึ้นอยู่กับว่าคอมพิวเตอร์มีการเข้าถึงเครือข่ายหรือไม่
    วิธีแก้ปัญหาคือการเปิดการเชื่อมต่อกับInetAddress.getByAddress(new byte[] {127, 0, 0, 1});
    ที่เกี่ยวข้องอาจจะข้อผิดพลาด 4435662
  • ฉันยังพบข้อผิดพลาด 4665037ซึ่งรายงานกว่าผลที่คาดว่าgetLocalHostผลตอบแทนที่อยู่ IP ของเครื่องเทียบกับผลที่เกิดขึ้น: 127.0.0.1การกลับมา

เป็นเรื่องที่น่าแปลกใจที่ได้getLocalHostกลับมา127.0.0.1บน Linux แต่ไม่ใช่บน windows


หรือคุณอาจใช้ManagementFactoryวัตถุ ตามที่อธิบายไว้ที่นี่ :

getMonitoredVMs(int processPid)วิธีการรับเป็นพารามิเตอร์โปรแกรมปัจจุบัน PID และจับชื่อโปรแกรมที่ถูกเรียกจากบรรทัดคำสั่งตัวอย่างเช่นโปรแกรมถูกเริ่มต้นจากc:\java\app\test.jarเส้นทางแล้วตัวแปรค่าเป็น " c:\\java\\app\\test.jar" วิธีนี้เราจะจับเพียงชื่อแอปพลิเคชันในบรรทัดที่ 17 ของรหัสด้านล่าง
หลังจากนั้นเราค้นหา JVM สำหรับกระบวนการอื่นที่มีชื่อเดียวกันหากเราพบและ PID ของแอปพลิเคชันแตกต่างกันแสดงว่าเป็นอินสแตนซ์ของแอปพลิเคชันที่สอง

JNLP ยังมีไฟล์ SingleInstanceListener


3
ระวังว่าน้ำยากำปั้นมีจุดบกพร่อง เราเพิ่งค้นพบว่าInetAddress.getLocalHost()ไม่ทำงานตามที่คาดไว้ในสภาพแวดล้อม DHCP เนื่องจากที่อยู่ที่ส่งคืนขึ้นอยู่กับว่าคอมพิวเตอร์มีการเข้าถึงเครือข่ายหรือไม่ วิธีแก้ปัญหาคือการเปิดการเชื่อมต่อกับInetAddress.getByAddress(new byte[] {127, 0, 0, 1});.
Ahe

2
@Ahe: จุดที่ยอดเยี่ยม ฉันได้รวมความคิดเห็นของคุณตลอดจนการอ้างอิงรายงานข้อบกพร่องของ Oracle-Sun ในคำตอบที่แก้ไขของฉัน
VonC

3
ตาม JavaDoc InetAddress.getByName(null)ส่งคืนที่อยู่ของอินเทอร์เฟซแบบวนกลับ ฉันเดาว่านี่ดีกว่าแล้วระบุ 127.0.0.1 ด้วยตนเองเพราะตามทฤษฎีแล้วสิ่งนี้ควรใช้งานได้ในสภาพแวดล้อม IPv6 เท่านั้น
kayahr

3
ดูยังใช้บริการ SingleInstanceService
trashgod

1
@Puce แน่นอนไม่มีปัญหา: ฉันกู้คืนลิงก์เหล่านั้นแล้ว
VonC

65

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

private static boolean lockInstance(final String lockFile) {
    try {
        final File file = new File(lockFile);
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
        log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}

พารามิเตอร์ "lockFile" ควรเป็นอย่างไรสำหรับแอปพลิเคชันเดสก์ท็อป ชื่อไฟล์ jar ของแอปพลิเคชัน? แล้วไม่มีไฟล์ jar แค่ไฟล์คลาสบางไฟล์?
5YrsLaterDBA

3
จำเป็นจริงๆหรือไม่ที่จะปลดล็อกไฟล์ด้วยตนเองและปิดไฟล์เมื่อปิดเครื่อง? สิ่งนี้ไม่เกิดขึ้นโดยอัตโนมัติเมื่อกระบวนการตาย?
Natix

5
แต่จะเกิดอะไรขึ้นถ้าไฟฟ้าดับและคอมพิวเตอร์ปิดโดยไม่ต้องใช้ตะขอปิดเครื่อง? ไฟล์จะยังคงอยู่และแอปพลิเคชันจะไม่สามารถเปิดใช้งานได้
Petr Hudeček

7
@ PetrHudečekไม่เป็นไร ไม่ว่าแอปพลิเคชันจะสิ้นสุดลงอย่างไรการล็อกไฟล์จะถูกปลดล็อก หากไม่ใช่การปิดระบบอย่างถูกต้องสิ่งนี้ก็มีประโยชน์ในการอนุญาตให้แอปพลิเคชันตระหนักถึงสิ่งนี้ในการทำงานครั้งต่อไป ไม่ว่าในกรณีใด ๆ : การล็อกคือสิ่งที่นับไม่ใช่การมีอยู่ของไฟล์เอง หากไฟล์ยังอยู่แอปพลิเคชันจะเปิดขึ้นมา
Dreamspace President

@ โรเบิร์ต: ขอบคุณสำหรับวิธีแก้ปัญหาของคุณฉันใช้มันมาตลอด และตอนนี้ฉันขยายมันเพื่อสื่อสารกับอินสแตนซ์ที่มีอยู่แล้วซึ่งอินสแตนซ์อื่นพยายามเริ่มต้น - โดยใช้โฟลเดอร์ WatchService! stackoverflow.com/a/36772436/3500521
Dreamspace President

10

หากแอพ. มี GUI เปิดด้วย JWS และใช้ไฟล์SingleInstanceService.

อัปเดต

Java Plug-In (จำเป็นสำหรับทั้งแอพเพล็ตและแอป JWS) ถูกเลิกใช้โดย Oracle และลบออกจาก JDK ผู้ผลิตเบราว์เซอร์ได้ลบมันออกจากเบราว์เซอร์ของตนแล้ว

ดังนั้นคำตอบนี้จึงสิ้นอายุขัย ทิ้งไว้ที่นี่เพื่อเตือนคนที่ดูเอกสารเก่าเท่านั้น


2
นอกจากนี้โปรดทราบว่าอินสแตนซ์ที่กำลังทำงานอยู่สามารถแจ้งให้ทราบถึงอินสแตนซ์ใหม่และข้อโต้แย้งทำให้ง่ายต่อการสื่อสารกับโปรแกรมดังกล่าว
Thorbjørn Ravn Andersen

6

ใช่นี่เป็นคำตอบที่ดีจริงๆสำหรับแอปพลิเคชั่นอินสแตนซ์ eclipse RCP eclipse ด้านล่างคือรหัสของฉัน

ใน application.java

if(!isFileshipAlreadyRunning()){
        MessageDialog.openError(display.getActiveShell(), "Fileship already running", "Another instance of this application is already running.  Exiting.");
        return IApplication.EXIT_OK;
    } 


private static boolean isFileshipAlreadyRunning() {
    // socket concept is shown at http://www.rbgrn.net/content/43-java-single-application-instance
    // but this one is really great
    try {
        final File file = new File("FileshipReserved.txt");
        final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        final FileLock fileLock = randomAccessFile.getChannel().tryLock();
        if (fileLock != null) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try {
                        fileLock.release();
                        randomAccessFile.close();
                        file.delete();
                    } catch (Exception e) {
                        //log.error("Unable to remove lock file: " + lockFile, e);
                    }
                }
            });
            return true;
        }
    } catch (Exception e) {
       // log.error("Unable to create and/or lock file: " + lockFile, e);
    }
    return false;
}

5

เราใช้การล็อกไฟล์สำหรับสิ่งนี้ (จับล็อกไฟล์วิเศษในไดเร็กทอรีข้อมูลแอปของผู้ใช้) แต่เราสนใจเป็นหลักในการป้องกันไม่ให้มีการทำงานหลายอินสแตนซ์

หากคุณกำลังพยายามมีอาร์กิวเมนต์บรรทัดคำสั่งผ่านอินสแตนซ์ที่สอง ฯลฯ ... ไปยังอินสแตนซ์แรกการใช้การเชื่อมต่อซ็อกเก็ตบน localhost จะฆ่านกสองตัวด้วยหินก้อนเดียว อัลกอริทึมทั่วไป:

  • เมื่อเปิดตัวให้ลองเปิดฟังบนพอร์ต XXXX บน localhost
  • ถ้าล้มเหลวให้เปิดตัวเขียนไปยังพอร์ตนั้นบน localhost และส่ง args บรรทัดคำสั่งจากนั้นปิดเครื่อง
  • มิฉะนั้นให้ฟังบนพอร์ต XXXXX บน localhost เมื่อรับอาร์กิวเมนต์บรรทัดคำสั่งให้ประมวลผลเหมือนกับว่าแอปเปิดตัวด้วยบรรทัดคำสั่งนั้น

5

ฉันได้พบวิธีแก้ปัญหาคำอธิบายที่เป็นการ์ตูนเล็กน้อย แต่ก็ยังใช้ได้ในกรณีส่วนใหญ่ มันใช้ไฟล์ล็อคแบบเก่าที่สร้างสิ่งต่าง ๆ แต่ในมุมมองที่แตกต่างกันมาก:

http://javalandscape.blogspot.com/2008/07/single-instance-from-your-application.html

ฉันคิดว่ามันจะช่วยสำหรับผู้ที่มีการตั้งค่าไฟร์วอลล์ที่เข้มงวด


ใช่มันเป็นวิธีที่ดีเนื่องจากการล็อกจะถูกปล่อยออกมาหากแอปพลิเคชันขัดข้องหรือมากกว่านั้น :)
LE GALL Benoît

5

คุณสามารถใช้ห้องสมุด JUnique ให้การสนับสนุนสำหรับการเรียกใช้แอปพลิเคชัน java อินสแตนซ์เดียวและเป็นโอเพ่นซอร์ส

http://www.sauronsoftware.it/projects/junique/

สามารถใช้ไลบรารี JUnique เพื่อป้องกันไม่ให้ผู้ใช้เรียกใช้อินสแตนซ์ของแอ็พพลิเคชัน Java เดียวกันในเวลาเดียวกัน

JUnique ใช้การล็อกและช่องทางการสื่อสารที่ใช้ร่วมกันระหว่างอินสแตนซ์ JVM ทั้งหมดที่เปิดใช้โดยผู้ใช้รายเดียวกัน

public static void main(String[] args) {
    String appId = "myapplicationid";
    boolean alreadyRunning;
    try {
        JUnique.acquireLock(appId, new MessageHandler() {
            public String handle(String message) {
                // A brand new argument received! Handle it!
                return null;
            }
        });
        alreadyRunning = false;
    } catch (AlreadyLockedException e) {
        alreadyRunning = true;
    }
    if (!alreadyRunning) {
        // Start sequence here
    } else {
        for (int i = 0; i < args.length; i++) {
            JUnique.sendMessage(appId, args[0]));
        }
    }
}

ภายใต้ประทุนจะสร้างไฟล์ล็อกในโฟลเดอร์% USER_DATA% /. junique และสร้างซ็อกเก็ตเซิร์ฟเวอร์ที่พอร์ตสุ่มสำหรับ appId ที่ไม่ซ้ำกันแต่ละตัวที่อนุญาตให้ส่ง / รับข้อความระหว่างแอปพลิเคชัน java


ฉันสามารถใช้สิ่งนี้เพื่อป้องกันแอปพลิเคชัน java หลายอินสแตนซ์ในเครือข่ายได้หรือไม่ aka อนุญาตให้ใช้แอปพลิเคชันเพียงอินสแตนซ์เดียวในเครือข่ายทั้งหมดของฉัน
Wuaner


2

คลาส ManagementFactory รองรับในรายละเอียด J2SE 5.0 หรือใหม่กว่า

แต่ตอนนี้ฉันใช้ J2SE 1.4 และฉันพบอันนี้http://audiprimadhanty.wordpress.com/2008/06/30/ensuring-one-instance-of-application-running-at-one-time/แต่ฉันไม่เคยทดสอบ คุณคิดอย่างไรกับมัน?


ฉันเชื่อว่าเป็นสิ่งที่อธิบายไว้ในลิงก์แรกของคำตอบของฉันด้านบน ... rbgrn.net/blog/2008/05/java-single-application-instance.html
VonC

2

คุณสามารถลองใช้ Preferences API เป็นแพลตฟอร์มที่เป็นอิสระ


ฉันชอบแนวคิดนี้เนื่องจาก API นั้นเรียบง่าย แต่อาจมีโปรแกรมสแกนไวรัสบางตัวไม่ต้องการให้คุณเปลี่ยนรีจิสทรีดังนั้นคุณจึงได้รับปัญหาที่คล้ายกันเช่นเดียวกับการใช้ RMI บนระบบที่มีซอฟต์แวร์ไฟร์วอลล์ .... ไม่แน่ใจ
Cal

@Cal แต่ปัญหาเดียวกันคือการเปลี่ยนไฟล์ / ล็อค / ฯลฯ ... คุณไม่คิดเหรอ?
Alex

2

วิธีการทั่วไปในการ จำกัด จำนวนอินสแตนซ์บนเครื่องเดียวหรือแม้แต่เครือข่ายทั้งหมดคือการใช้ซ็อกเก็ตมัลติคาสต์

การใช้ซ็อกเก็ตแบบหลายผู้รับช่วยให้คุณสามารถแพร่กระจายข้อความไปยังอินสแตนซ์ของแอปพลิเคชันของคุณจำนวนเท่าใดก็ได้ซึ่งบางส่วนสามารถอยู่บนเครื่องระยะไกลผ่านเครือข่ายขององค์กรได้

ด้วยวิธีนี้คุณสามารถเปิดใช้งานการกำหนดค่าหลายประเภทเพื่อควบคุมสิ่งต่างๆเช่น

  • หนึ่งหรือหลายอินสแตนซ์ต่อเครื่อง
  • หนึ่งหรือหลายอินสแตนซ์ต่อเครือข่าย (เช่นการควบคุมการติดตั้งบนไซต์ไคลเอนต์)

รองรับมัลติคาสต์ของ Java ผ่านแพ็คเกจ java.netโดยมีMulticastSocket & DatagramSocketเป็นเครื่องมือหลัก

หมายเหตุ : ของ MulticastSocket ไม่ได้ส่งมอบการรับประกันของแพ็กเก็ตข้อมูลดังนั้นคุณควรใช้เครื่องมือที่สร้างขึ้นบนซ็อกเก็ตแบบหลายผู้รับเช่นJGroups JGroups ไม่ส่งมอบการรับประกันของข้อมูลทั้งหมด เป็นไฟล์ jar เดียวพร้อม API ที่ง่ายมาก

JGroups มีมาระยะหนึ่งแล้วและมีการใช้งานที่น่าประทับใจในอุตสาหกรรมตัวอย่างเช่นมันหนุนกลไกการทำคลัสเตอร์ของ JBoss ที่กระจายข้อมูลไปยังอินสแตนซ์ทั้งหมดของคลัสเตอร์

ในการใช้ JGroups เพื่อ จำกัด จำนวนอินสแตนซ์ของแอพ (บนเครื่องหรือเครือข่ายสมมติว่า: สำหรับจำนวนใบอนุญาตที่ลูกค้าซื้อ) นั้นง่ายมาก:

  • เมื่อเริ่มต้นแอปพลิเคชันของคุณแต่ละอินสแตนซ์จะพยายามเข้าร่วมกลุ่มที่มีชื่อเช่น "My Great App Group" คุณจะกำหนดค่ากลุ่มนี้ให้อนุญาตสมาชิก 0, 1 หรือ N
  • เมื่อจำนวนสมาชิกกลุ่มมากกว่าที่คุณกำหนดไว้แอปของคุณควรปฏิเสธที่จะเริ่มต้น

1

คุณสามารถเปิดไฟล์ Memory Mapped แล้วดูว่าไฟล์นั้นเปิดอยู่หรือไม่ หากเปิดอยู่แล้วคุณสามารถกลับจาก main ได้

วิธีอื่น ๆ คือการใช้ไฟล์ล็อค (การฝึกยูนิกซ์มาตรฐาน) อีกวิธีหนึ่งคือใส่บางอย่างลงในคลิปบอร์ดเมื่อหลักเริ่มต้นหลังจากตรวจสอบว่ามีบางสิ่งอยู่ในคลิปบอร์ดแล้วหรือไม่

มิฉะนั้นคุณสามารถเปิดซ็อกเก็ตในโหมดฟัง (ServerSocket) ก่อนอื่นให้ลองเชื่อมต่อกับซ็อกเก็ต hte; หากคุณไม่สามารถเชื่อมต่อได้ให้เปิดเซิร์ฟเวอร์ซ็อกเก็ต หากคุณเชื่อมต่อคุณจะรู้ว่าอินสแตนซ์อื่นกำลังทำงานอยู่แล้ว

ดังนั้นทรัพยากรระบบใด ๆ จึงสามารถใช้เพื่อรู้ว่าแอปกำลังทำงานอยู่

BR, ~ ก


คุณมีรหัสสำหรับแนวคิดเหล่านั้นหรือไม่? ถ้าผู้ใช้เริ่มอินสแตนซ์ใหม่มันจะปิดอินสแตนซ์ก่อนหน้าทั้งหมดหรือไม่
นักพัฒนา Android

1

ฉันใช้ซ็อกเก็ตสำหรับสิ่งนั้นและขึ้นอยู่กับว่าแอปพลิเคชันอยู่บนฝั่งไคลเอ็นต์หรือฝั่งเซิร์ฟเวอร์พฤติกรรมแตกต่างกันเล็กน้อย:

  • ฝั่งไคลเอ็นต์: หากมีอินสแตนซ์อยู่แล้ว (ฉันไม่สามารถฟังพอร์ตเฉพาะได้) ฉันจะส่งผ่านพารามิเตอร์ของแอปพลิเคชันและออก (คุณอาจต้องการดำเนินการบางอย่างในอินสแตนซ์ก่อนหน้านี้) หากไม่มีฉันจะเริ่มแอปพลิเคชัน
  • ฝั่งเซิร์ฟเวอร์: หากมีอินสแตนซ์อยู่แล้วฉันจะพิมพ์ข้อความและออกหากไม่มีฉันจะเริ่มแอปพลิเคชัน

1
ชั้นเรียนสาธารณะ SingleInstance {
    สาธารณะคงสุดท้าย String LOCK = System.getProperty ("user.home") + File.separator + "test.lock";
    สาธารณะคงสตริงสุดท้าย PIPE = System.getProperty ("user.home") + File.separator + "test.pipe";
    เฟรม JFrame แบบคงที่ส่วนตัว = null;

    โมฆะคงที่สาธารณะ main (String [] args) {
        ลอง {
            FileChannel lockChannel = RandomAccessFile ใหม่ (LOCK, "rw") getChannel ();
            FileLock flk = null; 
            ลอง {
                flk = lockChannel.tryLock ();
            } จับ (โยนได้ t) {
                t.printStackTrace ();
            }
            ถ้า (flk == null ||! flk.isValid ()) {
                System.out.println ("ทำงานอยู่ทิ้งข้อความไปยังไพพ์และออกจากระบบ ... ");
                FileChannel pipeChannel = null;
                ลอง {
                    pipeChannel = RandomAccessFile ใหม่ (PIPE, "rw") getChannel ();
                    MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                    bb.put (0, (ไบต์) 1);
                    bb.force ();
                } จับ (โยนได้ t) {
                    t.printStackTrace ();
                } ในที่สุด {
                    ถ้า (pipeChannel! = null) {
                        ลอง {
                            pipeChannel.close ();
                        } จับ (โยนได้ t) {
                            t.printStackTrace ();
                        }
                    } 
                }
                System.exit (0);
            }
            // เราไม่ปลดล็อคและปิดช่องที่นี่ 
            // ซึ่งจะทำหลังจากแอปพลิเคชันล่มหรือปิดตามปกติ 
            SwingUtilities.invokeLater (
                Runnable ใหม่ () {
                    โมฆะสาธารณะ run () {
                        createAndShowGUI ();
                    }
                }
            );

            FileChannel pipeChannel = null;
            ลอง {
                pipeChannel = RandomAccessFile ใหม่ (PIPE, "rw") getChannel ();
                MappedByteBuffer bb = pipeChannel.map (FileChannel.MapMode.READ_WRITE, 0, 1);
                while (จริง) {
                    ไบต์ b = bb.get (0);
                    ถ้า (b> 0) {
                        bb.put (0, (ไบต์) 0);
                        bb.force ();
                        SwingUtilities.invokeLater (
                            Runnable ใหม่ () {
                                โมฆะสาธารณะ run () {
                                    frame.setExtendedState (JFrame.NORMAL);
                                    frame.setAlwaysOnTop (จริง);
                                    frame.toFront ();
                                    frame.setAlwaysOnTop (เท็จ);
                                }
                            }
                        );
                    }
                    Thread.sleep (1000);
                }
            } จับ (โยนได้ t) {
                t.printStackTrace ();
            } ในที่สุด {
                ถ้า (pipeChannel! = null) {
                    ลอง {
                        pipeChannel.close ();
                    } จับ (โยนได้ t) {
                        t.printStackTrace ();
                    } 
                } 
            }
        } จับ (โยนได้ t) {
            t.printStackTrace ();
        } 
    }

    โมฆะคงที่สาธารณะ createAndShowGUI () {

        เฟรม = JFrame ใหม่ ();
        frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        frame.setSize (800, 650);
        frame.getContentPane () เพิ่ม (JLabel ใหม่ ("MAIN WINDOW", 
                    SwingConstants.CENTER), BorderLayout.CENTER);
        frame.setLocationRelativeTo (null);
        frame.setVisible (จริง);
    }
}


1

แก้ไข : แทนที่จะใช้แนวทาง WatchService นี้สามารถใช้เธรดตัวจับเวลา 1 วินาทีแบบธรรมดาเพื่อตรวจสอบว่า indicatorFile.exists () หรือไม่ ลบออกจากนั้นนำแอปพลิเคชันไปที่ Front ()

แก้ไข : ฉันอยากทราบว่าเหตุใดจึงถูกลดคะแนน เป็นทางออกที่ดีที่สุดที่ฉันเคยเห็นมา เช่นวิธีการซ็อกเก็ตเซิร์ฟเวอร์ล้มเหลวหากมีแอพพลิเคชั่นอื่นกำลังฟังพอร์ตอยู่แล้ว

เพียงดาวน์โหลด Microsoft Windows Sysinternals TCPView (หรือใช้ netstat) เริ่มต้นโดยจัดเรียงตาม "สถานะ" มองหาบล็อกบรรทัดที่ระบุว่า "LISTENING" เลือกที่อยู่ระยะไกลที่ระบุชื่อคอมพิวเตอร์ของคุณใส่พอร์ตนั้นลงในซ็อกเก็ตใหม่ของคุณ ()-วิธีการแก้. ในการนำไปใช้งานของฉันฉันสามารถสร้างความล้มเหลวได้ทุกครั้ง และมันก็มีเหตุผลเพราะมันเป็นรากฐานของแนวทาง หรือฉันไม่ได้รับอะไรเกี่ยวกับวิธีการนำไปใช้?

โปรดแจ้งให้ฉันทราบว่าฉันผิดเกี่ยวกับเรื่องนี้อย่างไร!

มุมมองของฉัน - ซึ่งฉันขอให้คุณพิสูจน์ว่าเป็นไปได้ - คือการที่นักพัฒนาได้รับคำแนะนำให้ใช้แนวทางในรหัสการผลิตที่จะล้มเหลวอย่างน้อย 1 ในประมาณ 60000 กรณี และหากมุมมองนี้เกิดขึ้นถูกต้องก็เป็นไปไม่ได้เลยที่วิธีการแก้ปัญหาที่นำเสนอซึ่งไม่มีปัญหานี้จะถูกลดลงและถูกวิพากษ์วิจารณ์ถึงจำนวนรหัส

ข้อเสียของการเปรียบเทียบวิธีซ็อกเก็ต:

  • ล้มเหลวหากเลือกตั๋วลอตเตอรีผิด (หมายเลขพอร์ต)
  • ล้มเหลวในสภาพแวดล้อมที่มีผู้ใช้หลายคน: ผู้ใช้เพียงคนเดียวสามารถเรียกใช้แอปพลิเคชันพร้อมกัน (แนวทางของฉันจะต้องเปลี่ยนแปลงเล็กน้อยเพื่อสร้างไฟล์ในโครงสร้างผู้ใช้ แต่นั่นเป็นเรื่องเล็กน้อย)
  • ล้มเหลวหากกฎของไฟร์วอลล์เข้มงวดเกินไป
  • ทำให้ผู้ใช้ที่น่าสงสัย (ซึ่งฉันได้พบในป่า) สงสัยว่าคุณกำลังทำอะไรอยู่เมื่อโปรแกรมแก้ไขข้อความของคุณอ้างสิทธิ์ในซ็อกเก็ตเซิร์ฟเวอร์

ฉันมีความคิดที่ดีสำหรับวิธีแก้ปัญหาการสื่อสาร Java แบบอินสแตนซ์ใหม่ไปยังอินสแตนซ์ที่มีอยู่ในแบบที่ควรใช้กับทุกระบบ ดังนั้นฉันจึงจบคลาสนี้ในเวลาประมาณสองชั่วโมง ใช้งานได้เหมือนมีเสน่ห์: D

มันขึ้นอยู่กับวิธีการล็อกไฟล์ของRobert (เช่นในหน้านี้) ซึ่งฉันก็ใช้มาตลอด เพื่อบอกอินสแตนซ์ที่ทำงานอยู่แล้วว่าอินสแตนซ์อื่นพยายามเริ่มต้น (แต่ไม่ได้) ... ไฟล์จะถูกสร้างและลบทันทีและอินสแตนซ์แรกใช้ WatchService เพื่อตรวจจับการเปลี่ยนแปลงเนื้อหาของโฟลเดอร์นี้ ฉันไม่อยากจะเชื่อเลยว่านี่เป็นแนวคิดใหม่เนื่องจากพื้นฐานของปัญหาเป็นอย่างไร

สิ่งนี้สามารถเปลี่ยนได้อย่างง่ายดายเพียงแค่สร้างและไม่ลบไฟล์จากนั้นสามารถใส่ข้อมูลที่อินสแตนซ์ที่เหมาะสมสามารถประเมินได้เช่นอาร์กิวเมนต์บรรทัดคำสั่งและจากนั้นอินสแตนซ์ที่เหมาะสมก็สามารถทำการลบได้ โดยส่วนตัวแล้วฉันจำเป็นต้องรู้ว่าเมื่อใดที่จะกู้คืนหน้าต่างแอปพลิเคชันของฉันและส่งไปที่ด้านหน้า

ตัวอย่างการใช้งาน:

public static void main(final String[] args) {

    // ENSURE SINGLE INSTANCE
    if (!SingleInstanceChecker.INSTANCE.isOnlyInstance(Main::otherInstanceTriedToLaunch, false)) {
        System.exit(0);
    }

    // launch rest of application here
    System.out.println("Application starts properly because it's the only instance.");
}

private static void otherInstanceTriedToLaunch() {
    // Restore your application window and bring it to front.
    // But make sure your situation is apt: This method could be called at *any* time.
    System.err.println("Deiconified because other instance tried to start.");
}

นี่คือชั้นเรียน:

package yourpackagehere;

import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.file.*;




/**
 * SingleInstanceChecker v[(2), 2016-04-22 08:00 UTC] by dreamspace-president.com
 * <p>
 * (file lock single instance solution by Robert https://stackoverflow.com/a/2002948/3500521)
 */
public enum SingleInstanceChecker {

    INSTANCE; // HAHA! The CONFUSION!


    final public static int POLLINTERVAL = 1000;
    final public static File LOCKFILE = new File("SINGLE_INSTANCE_LOCKFILE");
    final public static File DETECTFILE = new File("EXTRA_INSTANCE_DETECTFILE");


    private boolean hasBeenUsedAlready = false;


    private WatchService watchService = null;
    private RandomAccessFile randomAccessFileForLock = null;
    private FileLock fileLock = null;


    /**
     * CAN ONLY BE CALLED ONCE.
     * <p>
     * Assumes that the program will close if FALSE is returned: The other-instance-tries-to-launch listener is not
     * installed in that case.
     * <p>
     * Checks if another instance is already running (temp file lock / shutdownhook). Depending on the accessibility of
     * the temp file the return value will be true or false. This approach even works even if the virtual machine
     * process gets killed. On the next run, the program can even detect if it has shut down irregularly, because then
     * the file will still exist. (Thanks to Robert https://stackoverflow.com/a/2002948/3500521 for that solution!)
     * <p>
     * Additionally, the method checks if another instance tries to start. In a crappy way, because as awesome as Java
     * is, it lacks some fundamental features. Don't worry, it has only been 25 years, it'll sure come eventually.
     *
     * @param codeToRunIfOtherInstanceTriesToStart Can be null. If not null and another instance tries to start (which
     *                                             changes the detect-file), the code will be executed. Could be used to
     *                                             bring the current (=old=only) instance to front. If null, then the
     *                                             watcher will not be installed at all, nor will the trigger file be
     *                                             created. (Null means that you just don't want to make use of this
     *                                             half of the class' purpose, but then you would be better advised to
     *                                             just use the 24 line method by Robert.)
     *                                             <p>
     *                                             BE CAREFUL with the code: It will potentially be called until the
     *                                             very last moment of the program's existence, so if you e.g. have a
     *                                             shutdown procedure or a window that would be brought to front, check
     *                                             if the procedure has not been triggered yet or if the window still
     *                                             exists / hasn't been disposed of yet. Or edit this class to be more
     *                                             comfortable. This would e.g. allow you to remove some crappy
     *                                             comments. Attribution would be nice, though.
     * @param executeOnAWTEventDispatchThread      Convenience function. If false, the code will just be executed. If
     *                                             true, it will be detected if we're currently on that thread. If so,
     *                                             the code will just be executed. If not so, the code will be run via
     *                                             SwingUtilities.invokeLater().
     * @return if this is the only instance
     */
    public boolean isOnlyInstance(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        if (hasBeenUsedAlready) {
            throw new IllegalStateException("This class/method can only be used once, which kinda makes sense if you think about it.");
        }
        hasBeenUsedAlready = true;

        final boolean ret = canLockFileBeCreatedAndLocked();

        if (codeToRunIfOtherInstanceTriesToStart != null) {
            if (ret) {
                // Only if this is the only instance, it makes sense to install a watcher for additional instances.
                installOtherInstanceLaunchAttemptWatcher(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread);
            } else {
                // Only if this is NOT the only instance, it makes sense to create&delete the trigger file that will effect notification of the other instance.
                //
                // Regarding "codeToRunIfOtherInstanceTriesToStart != null":
                // While creation/deletion of the file concerns THE OTHER instance of the program,
                // making it dependent on the call made in THIS instance makes sense
                // because the code executed is probably the same.
                createAndDeleteOtherInstanceWatcherTriggerFile();
            }
        }

        optionallyInstallShutdownHookThatCleansEverythingUp();

        return ret;
    }


    private void createAndDeleteOtherInstanceWatcherTriggerFile() {

        try {
            final RandomAccessFile randomAccessFileForDetection = new RandomAccessFile(DETECTFILE, "rw");
            randomAccessFileForDetection.close();
            Files.deleteIfExists(DETECTFILE.toPath()); // File is created and then instantly deleted. Not a problem for the WatchService :)
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private boolean canLockFileBeCreatedAndLocked() {

        try {
            randomAccessFileForLock = new RandomAccessFile(LOCKFILE, "rw");
            fileLock = randomAccessFileForLock.getChannel().tryLock();
            return fileLock != null;
        } catch (Exception e) {
            return false;
        }
    }


    private void installOtherInstanceLaunchAttemptWatcher(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        // PREPARE WATCHSERVICE AND STUFF
        try {
            watchService = FileSystems.getDefault().newWatchService();
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        final File appFolder = new File("").getAbsoluteFile(); // points to current folder
        final Path appFolderWatchable = appFolder.toPath();


        // REGISTER CURRENT FOLDER FOR WATCHING FOR FILE DELETIONS
        try {
            appFolderWatchable.register(watchService, StandardWatchEventKinds.ENTRY_DELETE);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }


        // INSTALL WATCHER THAT LOOKS IF OUR detectFile SHOWS UP IN THE DIRECTORY CHANGES. IF THERE'S A CHANGE, ANOTHER INSTANCE TRIED TO START, SO NOTIFY THE CURRENT ONE OF THAT.
        final Thread t = new Thread(() -> watchForDirectoryChangesOnExtraThread(codeToRunIfOtherInstanceTriesToStart, executeOnAWTEventDispatchThread));
        t.setDaemon(true);
        t.setName("directory content change watcher");
        t.start();
    }


    private void optionallyInstallShutdownHookThatCleansEverythingUp() {

        if (fileLock == null && randomAccessFileForLock == null && watchService == null) {
            return;
        }

        final Thread shutdownHookThread = new Thread(() -> {
            try {
                if (fileLock != null) {
                    fileLock.release();
                }
                if (randomAccessFileForLock != null) {
                    randomAccessFileForLock.close();
                }
                Files.deleteIfExists(LOCKFILE.toPath());
            } catch (Exception ignore) {
            }
            if (watchService != null) {
                try {
                    watchService.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        Runtime.getRuntime().addShutdownHook(shutdownHookThread);
    }


    private void watchForDirectoryChangesOnExtraThread(final Runnable codeToRunIfOtherInstanceTriesToStart, final boolean executeOnAWTEventDispatchThread) {

        while (true) { // To eternity and beyond! Until the universe shuts down. (Should be a volatile boolean, but this class only has absolutely required features.)

            try {
                Thread.sleep(POLLINTERVAL);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            final WatchKey wk;
            try {
                wk = watchService.poll();
            } catch (ClosedWatchServiceException e) {
                // This situation would be normal if the watcher has been closed, but our application never does that.
                e.printStackTrace();
                return;
            }

            if (wk == null || !wk.isValid()) {
                continue;
            }


            for (WatchEvent<?> we : wk.pollEvents()) {

                final WatchEvent.Kind<?> kind = we.kind();
                if (kind == StandardWatchEventKinds.OVERFLOW) {
                    System.err.println("OVERFLOW of directory change events!");
                    continue;
                }


                final WatchEvent<Path> watchEvent = (WatchEvent<Path>) we;
                final File file = watchEvent.context().toFile();


                if (file.equals(DETECTFILE)) {

                    if (!executeOnAWTEventDispatchThread || SwingUtilities.isEventDispatchThread()) {
                        codeToRunIfOtherInstanceTriesToStart.run();
                    } else {
                        SwingUtilities.invokeLater(codeToRunIfOtherInstanceTriesToStart);
                    }

                    break;

                } else {
                    System.err.println("THIS IS THE FILE THAT WAS DELETED: " + file);
                }

            }

            wk.reset();
        }
    }

}

คุณไม่จำเป็นต้องมีโค้ดหลายร้อยบรรทัดเพื่อแก้ปัญหานี้ new ServerSocket()ด้วยการดักจับค่อนข้างเพียงพอ
user207421

@EJP คุณหมายถึงคำตอบที่ได้รับการยอมรับหรือคุณกำลังพูดถึงอะไร? ฉันได้ค้นหาไม่น้อยสำหรับ x-แพลตฟอร์มไม่มีวิธีแก้-พิเศษห้องสมุดที่ไม่ล้มเหลวเช่นซ็อกเก็ตเพราะบางอย่างที่เกิดขึ้นกับแล้วจะครอบครองโดยที่แตกต่างกันการประยุกต์ใช้ หากมีวิธีแก้ปัญหานี้ - โดยเฉพาะอย่างยิ่งง่ายมากอย่างที่คุณอ้างถึง - ฉันอยากรู้เกี่ยวกับเรื่องนี้
ประธาน Dreamspace

@EJP: ฉันอยากจะถามอีกครั้ง 1)วิธีแก้ปัญหาเล็กน้อยที่คุณอ้างถึงว่าคุณห้อยหัวเหมือนแครอทอยู่ตรงหน้าฉัน2)ในกรณีที่เป็นโซลูชันซ็อกเก็ตคำตอบที่ยอมรับจะเริ่มต้นด้วยถ้าอย่างน้อยหนึ่งข้อ สัญลักษณ์แสดงหัวข้อย่อย "ข้อเสียของวิธีซ็อกเก็ต" ของฉันใช้และ3)ถ้าเป็นเช่นนั้นทำไมแม้ว่าคุณจะมีข้อบกพร่องเหล่านั้นคุณก็ยังคงแนะนำแนวทางนั้นมากกว่าหนึ่งวิธีเช่นของฉัน
Dreamspace President

@EJP: ปัญหาคือเสียงของคุณค่อนข้างมีน้ำหนักอย่างที่คุณแน่ใจ แต่หลักฐานทั้งหมดที่ฉันมีบังคับให้ฉันต้องมั่นใจว่าคำแนะนำของคุณที่นี่ไม่ถูกต้อง ดูสิฉันไม่ได้ยืนยันว่าวิธีการแก้ปัญหาของฉันถูกต้องและทั้งหมดนั้น แต่ฉันเป็นเครื่องจักรตามหลักฐาน คุณไม่เห็นหรือว่าตำแหน่งของคุณทำให้คุณมีความรับผิดชอบต่อชุมชนในการเติมเต็มส่วนปริศนาที่ขาดหายไปของการสื่อสารนี้ที่คุณเริ่มต้น?
Dreamspace President

@EJP: เนื่องจากไม่มีปฏิกิริยาใด ๆ จากคุณอย่างน่าเศร้านี่คือสิ่งที่ฉันจะถือว่าเป็นความจริง: ความจริงเกี่ยวกับโซลูชันซ็อกเก็ตเซิร์ฟเวอร์นั้นมีข้อบกพร่องอย่างมากและสาเหตุส่วนใหญ่ที่เลือกอาจเป็น "คนอื่น ๆ ใช้สิ่งนี้ด้วย” หรืออาจถูกหลอกให้ใช้โดยผู้ที่ขาดความรับผิดชอบ ฉันเดาว่าส่วนหนึ่งของเหตุผลที่คุณไม่ให้เกียรติเราด้วยคำอธิบายที่จำเป็นคือคุณไม่สามารถเข้าใจได้ว่า / ทำไมคุณไม่เคยตั้งคำถามกับแนวทางนี้และคุณไม่ต้องการแถลงต่อสาธารณะโดยเปิดเผยสิ่งนี้
ประธาน Dreamspace

1

ไลบรารี Unique4j สามารถใช้เพื่อรันอินสแตนซ์เดียวของแอ็พพลิเคชัน Java และส่งผ่านข้อความ คุณสามารถดูได้ที่https://github.com/prat-man/unique4j รองรับ Java 1.6+

ใช้การล็อกไฟล์ร่วมกันและการล็อกพอร์ตแบบไดนามิกเพื่อตรวจจับและสื่อสารระหว่างอินสแตนซ์โดยมีเป้าหมายหลักในการอนุญาตให้รันอินสแตนซ์เดียวเท่านั้น

ต่อไปนี้เป็นตัวอย่างง่ายๆของสิ่งเดียวกัน:

import tk.pratanumandal.unique4j.Unique4j;
import tk.pratanumandal.unique4j.exception.Unique4jException;

public class Unique4jDemo {

    // unique application ID
    public static String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6";

    public static void main(String[] args) throws Unique4jException, InterruptedException {

        // create unique instance
        Unique4j unique = new Unique4j(APP_ID) {
            @Override
            public void receiveMessage(String message) {
                // display received message from subsequent instance
                System.out.println(message);
            }

            @Override
            public String sendMessage() {
                // send message to first instance
                return "Hello World!";
            }
        };

        // try to obtain lock
        boolean lockFlag = unique.acquireLock();

        // sleep the main thread for 30 seconds to simulate long running tasks
        Thread.sleep(30000);

        // try to free the lock before exiting program
        boolean lockFreeFlag = unique.freeLock();

    }

}

คำเตือน: ฉันสร้างและดูแลไลบรารี Unique4j


1

ฉันเขียนห้องสมุดเฉพาะสำหรับhttps://sanyarnd.github.io/applocker

มันขึ้นอยู่กับการล็อกช่องไฟล์ดังนั้นจะไม่บล็อกหมายเลขพอร์ตหรือแอปพลิเคชันการหยุดชะงักในกรณีที่ไฟฟ้าดับ (ช่องสัญญาณจะถูกปล่อยออกมาเมื่อกระบวนการสิ้นสุดลง)

ไลบรารีมีน้ำหนักเบาและมี API ที่คล่องแคล่ว

ได้รับแรงบันดาลใจจากhttp://www.sauronsoftware.it/projects/junique/แต่ใช้ช่องไฟล์แทน และยังมีคุณสมบัติใหม่พิเศษอื่น ๆ

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