トップ 履歴 一覧 カテゴリ ソース 検索 ヘルプ RSS ログイン

Source/Java/Semaphore

INDEX

ファイルを利用した擬似セマフォ

システムの一時ファイルディレクトリにロックファイルを作成し、バイト単位でロックをしてパーミット数を管理します。

ファイルを利用した擬似セマフォ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * $Id: PseudoFileSemaphore.java,v 0.0 2009/04/02 00:00:00 t-imamura Exp $
 *
 * Copyright (c) 2009 t-imamura, All rights reserved.
 */
package semaphore;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.regex.Pattern;

/**
 * ファイルを利用した擬似セマフォ.
 * <br>
 * システムの一時ファイルディレクトリにロックファイルを作成し、
 * バイト単位でロックをしてパーミット数を管理します。
 *
 * <pre>
 * final PseudoFileSemaphore semaphore = new PseudoFileSemaphore(SEMAPHORE_NAME, PERMITS);
 *
 * // シャットダウンフック
 * Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
 *   public void run() {
 *     semaphore.release();
 *   }
 * }));
 *
 * // パーミットの取得を試みる
 * if (semaphore.acquire()) {
 *   // 起動OK!
 *   JOptionPane.showMessageDialog(null, "アプリケーションを起動しました。",
 *       SEMAPHORE_NAME, JOptionPane.INFORMATION_MESSAGE);
 * } else {
 *   // 起動NG!
 *   JOptionPane.showMessageDialog(null, "アプリケーションの起動上限に達しました。",
 *       SEMAPHORE_NAME, JOptionPane.ERROR_MESSAGE);
 * }
 * </pre>
 */
public class PseudoFileSemaphore {

    /** ロックファイル拡張子 */
    private static final String LOCK_FILE_EXT = ".lck";

    /** 利用可能なパーミットの数 */
    private final int permits;

    /** ロックファイル */
    private final File file;

    /** ロックファイルチャネル */
    private FileChannel channel;

    /** ファイルロック */
    private FileLock filelock;

    /**
     * PseudoFileSemaphoreを構築する。
     * <br>
     * <code>name</code> には、英数といくつかの記号(<code>-+=_(){}</code>)のみが利用できます。
     *
     * @param name アプリケーション(ロックファイル)名
     * @param permits 利用可能なパーミットの数
     */
    public PseudoFileSemaphore(String name, int permits) {
        if (!Pattern.compile("^[-+=0-9A-Za-z_(){}]+$").matcher(name).matches()) {
            // ファイル名として使える文字なら何でも良いが、無難な英数といくつかの記号のみ
            throw new IllegalArgumentException("name cannot be used. name=" + name);
        }
        if(permits < 1){
            throw new IllegalArgumentException("permits is small. permits=" + permits);
        }

        this.permits = permits;
        this.file = new File(System.getProperty("java.io.tmpdir"), name + LOCK_FILE_EXT);
    }

    /**
     * 一時ファイルディレクトリにロックファイルを作成する。
     *
     * @param file ロックファイルパス
     * @param permits 最大取得可能上限
     * @return ロックファイルチャネル
     * @throws IOException 入出力エラーが発生した場合
     */
    private FileChannel createLockFileChannel(File file, int permits) throws IOException {
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        FileChannel channel = raf.getChannel();
        raf.setLength(permits);
        if (raf.length() < permits) {
            raf.seek(raf.length());
            for (long i = 0; i < permits - raf.length(); i++) {
                raf.write(0xff);
            }
        } else if (raf.length() > permits) {
            channel.truncate(permits);
        }
        return channel;
    }

    /**
     * パーミットを解放し、セマフォーに戻します。
     */
    public synchronized void release() {
        if (this.filelock == null)
            return;
        try {
            this.filelock.release();
            this.filelock = null;
            this.channel.close();
            this.channel = null;
            this.file.delete();
        } catch (IOException e) {
            // 例外は特に何もしない
        }
    }

    /**
     * 利用可能な場合に限り、このセマフォーからパーミットを取得します。
     *
     * @return パーミットが取得された場合は <code>true</code>
     */
    public synchronized boolean acquire() {
        try {
            FileChannel channel = createLockFileChannel(this.file, this.permits);
            FileLock lock = null;
            for (int p = 0; p < this.permits; p++) {
                try {
                    lock = channel.tryLock(p, 1, false);
                    if (lock != null && lock.isValid()) {
                        break;
                    } else {
                        lock = null;
                    }
                } catch (OverlappingFileLockException e) {
                    // 既にロックされてたら次を試みる
                    continue;
                }
            }
            if (lock != null) {
                this.filelock = lock;
                this.channel = channel;
            } else if(channel != null) {
                channel.close();
            }
        } catch (IOException e) {
            // 例外が発生しても何もしない、パーミットが取れないだけ。
        }
        return (this.filelock != null);
    }

}

テスト兼サンプル

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package semaphore;

import javax.swing.JOptionPane;

/**
 * ファイルを使用した擬似セマフォの利用.
 * http://d.hatena.ne.jp/Kazzz/20071217/p1
 */
public class PseudoFileSemaphoreTest {

    private static final String SEMAPHORE_NAME = "PseudoFileSemaphore";

    private static final int SEMAPHORE_PERMITS = 1;

    /**
     * アプリケーション起動.
     * @param args コマンド引数
     * @throws Exception 例外
     */
    public static void main(String[] args) throws Exception {
        final PseudoFileSemaphore semaphore = new PseudoFileSemaphore(SEMAPHORE_NAME, SEMAPHORE_PERMITS);

        // シャットダウンフック
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                semaphore.release();
            }
        }));

        if (semaphore.acquire()) {
            // 起動OK!
            JOptionPane.showMessageDialog(null, "アプリケーションを起動しました。",
                    SEMAPHORE_NAME, JOptionPane.INFORMATION_MESSAGE);
        } else {
            // 起動NG!
            JOptionPane.showMessageDialog(null, "アプリケーションの起動上限に達しました。",
                    SEMAPHORE_NAME, JOptionPane.ERROR_MESSAGE);
        }
    }

}

参考

最終更新時間:2009年10月24日 16時18分49秒 指摘や意見などあればSandBoxのBBSへ。

PseudoFileSemaphore.java PseudoFileSemaphoreTest.java