java網絡編程讀書筆記-Ch03

ch03 Threads

By the time a server is attempting to handle a thousand or more simultaneous connections, performance slows to a crawl.

There are at least two solutions to this problem.
* reuse processes rather than spawning new ones
* use lightweight threads instead of heavyweight processes to handle connections

a thread-based design is usually where you should start until you can prove you’re hitting a wall.

Running Threads

A thread with a little t is a separate, independent path of execution in the virtual machine. A Thread with a capital T is an instance of the java.lang.Thread class.

There is a one- to-one relationship between threads executing in the virtual machine and Thread ob‐ jects constructed by the virtual machine.

Thread t = new Thread();
t.start();

To give a thread something to do, you either subclass the Thread class and override its run() method, or implement the Runnable interface and pass the Runnable object to the Thread constructor Separates the task that the thread performs from the thread itself more cleanly.

When the run() method completes, the thread dies. In essence, the run() method is to a thread what the main() method is to a traditional nonthreaded program.

A multithreaded program exits when both the main() method and the run() methods of all nondaemon threads return. (Daemon threads perform background tasks such as garbage collection and don’t prevent the virtual machine from exiting.)

Subclassing Thread

/**
* subclass of Thread whose run() method calculates a 256-bit SHA-2 message digest
* for a specified file. It does this by reading the file with a DigestInput Stream.
* This filter stream calculates a cryptographic hash function as it reads the file.
* When it’s finished reading, the hash function is available from the digest() method.
*/

import java.io.*;
import java.security.*;
import javax.xml.bind.*;

public class DigestThread extends Thread{
    private String filename;

    public DigestThread(String filename){
        this.filename = filename;
    }

    // -----run
    @Override
    public void run(){
        try{
            FileInputStream in = new FileInputStream(filename);
            /*
            * MessageDigest 類爲應用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法
            * 信息摘要是安全的單向哈希函數,它接收任意大小的數據,並輸出固定長度的哈希值。
            * ```
                public static MessageDigest getInstance(String algorithm)
                                 throws NoSuchAlgorithmException
                返回實現指定摘要算法的 MessageDigest 對象。
                algorithm - 所請求算法的名稱
            ```
            */
            MessageDigest sha = MessageDigest.getInstance("SHA-256");

            /*
            * **DigestInputStream**
            * 使用輸入流的方式完成摘要更新,調用on(boolean on)方法開啓和關閉摘要功能。
            * 如果on(false),則DigestInputStream就變成了一般的輸入流。
            * 默認摘要功能是開啓的,如果開啓了摘要功能,調用read方法時,
            * 將調用MessageDigest 類的update方法更新摘要。輸入流的內容是read的字節而不是摘要。
            */
            DigestInputStream din = new DigestInputStream(in, sha);
            while(din.read() != -1);
            din.close();

            // 在調用 digest 之後,MessageDigest 對象被重新設置成其初始狀態。
            byte[] digest = sha.digest();

            /*
            * String 字符串常量
            * StringBuffer 字符串變量(線程安全)
            * StringBuilder 字符串變量(非線程安全)
            */
            StringBuilder result = new StringBuilder(filename);
            result.append(": ");

            /*
            * printXXX 的函數就是encode,parseXXX 的函數就是decode。
            * 比如,String printBase64Binary(byte[])就是將字節數組做base64編碼,
            * byte[] parseBase64Binary(String) 就是將Base64編碼後的String還原成字節數組。
            */
            result.append(DatatypeConverter.printHexBinary(digest));

            System.out.println(result);
        }catch(IOException ex){
            System.err.println(ex);
        }catch(NoSuchAlgorithmException ex){
            System.err.println(ex);
        }
    }
    // -----run

    public static void main(String[] args){
        for(String filename : args){
            Thread t = new DigestThread(filename);
            t.start();
        }
    }

}

編譯運行

JunrdeMacBook-Pro:src junr$ java DigestThread ch02.md
ch02.md: 0318537999FF14474A9963B5DA244810913E75ECB8AA0C3162A6021FB3A8AC6B

Getting information out of a thread back into the original calling thread is trickier because of the asynchronous nature of threads.

If you subclass Thread, you should override run() and nothing else! The various other methods of the Thread class—for example, start(), interrupt(), join(), sleep(), and so on—all have very specific se‐ mantics and interactions with the virtual machine that are difficult to reproduce in your own code.

Implementing the Runnable Interface

One way to avoid overriding the standard Thread methods is not to subclass Thread. Instead, write the task you want the thread to perform as an instance of the Runnable interface. This interface declares the run() method, exactly the same as the Thread class:

public void run()

import java.io.*;
import java.security.*;
import javax.xml.bind.*;

public class DigestRunnable implements Runnable{
    private String filename;

    public DigestRunnable(String filename){
        this.filename = filename;
    }

    // -----run
    @Override
    public void run(){
        try{
            FileInputStream in = new FileInputStream(filename);
            /*
            * MessageDigest 類爲應用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法
            * 信息摘要是安全的單向哈希函數,它接收任意大小的數據,並輸出固定長度的哈希值。
            * ```
                public static MessageDigest getInstance(String algorithm)
                                 throws NoSuchAlgorithmException
                返回實現指定摘要算法的 MessageDigest 對象。
                algorithm - 所請求算法的名稱
            ```
            */
            MessageDigest sha = MessageDigest.getInstance("SHA-256");

            /*
            * **DigestInputStream**
            * 使用輸入流的方式完成摘要更新,調用on(boolean on)方法開啓和關閉摘要功能。
            * 如果on(false),則DigestInputStream就變成了一般的輸入流。
            * 默認摘要功能是開啓的,如果開啓了摘要功能,調用read方法時,
            * 將調用MessageDigest 類的update方法更新摘要。輸入流的內容是read的字節而不是摘要。
            */
            DigestInputStream din = new DigestInputStream(in, sha);
            while(din.read() != -1);
            din.close();

            // 在調用 digest 之後,MessageDigest 對象被重新設置成其初始狀態。
            byte[] digest = sha.digest();

            /*
            * String 字符串常量
            * StringBuffer 字符串變量(線程安全)
            * StringBuilder 字符串變量(非線程安全)
            */
            StringBuilder result = new StringBuilder(filename);
            result.append(": ");

            /*
            * printXXX 的函數就是encode,parseXXX 的函數就是decode。
            * 比如,String printBase64Binary(byte[])就是將字節數組做base64編碼,
            * byte[] parseBase64Binary(String) 就是將Base64編碼後的String還原成字節數組。
            */
            result.append(DatatypeConverter.printHexBinary(digest));

            System.out.println(result);
        }catch(IOException ex){
            System.err.println(ex);
        }catch(NoSuchAlgorithmException ex){
            System.err.println(ex);
        }
    }
    // -----run

    public static void main(String[] args){
        for(String filename:args){
            DigestRunnable dr = new DigestRunnable(filename);
            Thread t = new Thred(dr);
            t.start()
        }
    }
}

Returning Information from a Thread

Most people’s first reaction is to store the result in a field and provide a getter method

Race Conditions

假設我們寫了下面的類,用於返回一個結果。

import java.io.*;
import java.security.*;

public class ReturnDigest extends Thread{

    private String filename;
    private byte[] digest;

    public ReturnDigest(String filename){
        this.filename = filename;
    }

    @Override
    public void run(){
        try{
            FileInputstream in = new FileInputStream(filename);
            MessageDigest sha = MessageDigest.getInstance("SHA-256");
            DigestInputStream din = new DigestInputStream(in, sha);
            while(din.read() != -1);
            din.close();
            digest = sha.digest();
        }catch(...){
            ...
        }
    }

    public byte[] getDIgest(){
        return digest;
    }
}

上面的類通過getDigest 返回了一個結果

我們下面的類,調用了上面的類

import javax.xml.bind.*;
public classs ReturnDigestUserInterface{
    public static void main(String[] args) {
        ReturnDigest[] digests = new ReturnDigest[args.length];
        for(int i=0;i < args.length; i++){
            digests[i] = new ReturnDigest(args[i]);
            digests[i].start();
        }
        for(int i = 0; i < args.length; i++){
            System.out.println(digests[i].getDigest());
        }
    }
}

那麼就有可能出現競爭,第二個for循環可能在第一個循環結束,但是線程都沒有結束的情況下輸出,就會出現錯誤。

Polling

The solution most novices adopt is to make the getter method return a flag value (or perhaps throw an exception) until the result field is set.

可以在main中的循環裏,不斷判斷是否完成,是不是 == null,不等於再輸出。

Callbacks

let the thread tell the main program when it’s finished. It does this by invoking a method in the main class that started it. This is called a callback because the thread calls its creator back when it’s done. This way, the main program can go to sleep while waiting for the threads to finish and not steal time from the running threads.

在 run 的最後加上

CallbackDigestUserInterface.receiveDigest(digest, filename);
import javax.xml.bind.*;

public class CallbackDigestUserInterface{
    public static void receiveDigest(byte[] digest, String name){
        System.out.print(digest)
    }

    public static void main(String[] args){
        for(String filename : args){
            CallbackDigest cb = new CallbackDigest(filename);
            Thread t = new Thread(cb);
            t.start();
        }
    }
}

就只是相當於加了個函數??
具體的代碼請參考原書P65

use static methods for the callback so that CallbackDigest only needs to know the name of the method in CallbackDigestUserInterface to call. However, it’s not much harder (and it’s considerably more common) to call back to an instance method. In this case, the class making the callback must have a reference to the object it’s calling back. Generally, this reference is provided as an argument to the thread’s constructor. When the run() method is nearly done, the last thing it does is invoke the instance method on the callback object to pass along the result.

Therefore, it’s good form to avoid launching threads from con‐ structors.

Futures, Callables and Executors

Instead of directly creating a thread, you create an ExecutorService that will create threads for you as needed.

You submit Callable jobs to the ExecutorService and for each one you get back a Future.

At a later point, you can ask the Future for the result of the job. If the result is ready, you get it immediately. If it’s not ready, the polling thread blocks until it is ready. The advantage is that you can spawn off many different threads, then get the answers you need in the order you need them.

import java.util.concurrent.Callable;

class FindMaxTask implements Callable<Integer>{

    private int[] data;
    private int start;
    private int end;

    FindMaxTask(int[] data, int start, int end){
        this.data = data;
        this.start = start;
        this.end = end;
    }

    public Integer call(){
        int max = Integer.MIN_VALUE;
        for(int i = start; i < end; i++){
            if(data[i] > max) max = data[i];
        }
        return max;
    }
}

Although you could invoke the call() method directly, that is not its purpose. Instead, you submit Callable objects to an Executor that spins up a thread for each one.

import java.util.concurrent.*; 
public class MultithreadedMaxFinder {
    public static int max(int[] data) throws InterruptedException, ExecutionException {
        if (data.length == 1) { 
            return data[0];
        } else if (data.length == 0) {
            throw new IllegalArgumentException();
        }
        // split the job into 2 pieces
        FindMaxTask task1 = new FindMaxTask(data, 0, data.length/2);
        FindMaxTask task2 = new FindMaxTask(data, data.length/2, data.length);
        // spawn 2 threads
        ExecutorService service = Executors.newFixedThreadPool(2);
        Future<Integer> future1 = service.submit(task1);
        Future<Integer> future2 = service.submit(task2); 

        return Math.max(future1.get(), future2.get());

    } 
}

Java 範型 Generic Types

class Cell<E>{
    private Cell<E> next;
    private E element;
    public Cell(E element){
        this.element = element;
    }

    public Cell(E element, Cell<E> next){
        this.element = element;
        this.next = next;
    }

    public Cell<E> getNext(){
        return next;
    }

    public void setNext(Cell<E> next){
        this.next = next;
    }
}

By convention, type variables have single character names: E for an element type, K for a key type, V for a value type, T for a general type, and so forth.

When you define a generic class, all invocations of that generic class are simply expressions of that one class. Declaring a variable strCell as Cell tells the compiler that strCell will refer to an object of type Cell where E will be String. It does not tell the compiler to create a new class Cell.

The following code shows quite clearly that there is just one class because the value of same is TRue:

Cell<String> strCell = new Cell<String>("Hello");
Cell<Integer> intCell = new Cell<Integer>(25);
boolean same = (strCell.getClass() == intCell.getClass());

泛型的好處是在編譯的時候檢查類型安全,並且所有的強制轉換都是自動和隱式的,提高代碼的重用率。

泛型類中的類型參數幾乎可以用於任何可以使用接口名、類名的地方,下面的代碼示例展示了 JDK 5.0 中集合框架中的 Map 接口的定義的一部分:
public interface Map

範型接口

在泛型接口中,生成器是一個很好的理解,看如下的生成器接口定義

public interface Generator<T> {
    public T next();
}

然後定義一個生成器類來實現這個接口:

public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}
public class Main {

    public static void main(String[] args) {
        FruitGenerator generator = new FruitGenerator();
        System.out.println(generator.next());
        System.out.println(generator.next());
        System.out.println(generator.next());
        System.out.println(generator.next());
    }
}

泛型方法

一個基本的原則是:無論何時,只要你能做到,你就應該儘量使用泛型方法。也就是說,如果使用泛型方法可以取代將整個類泛化,那麼應該有限採用泛型方法。下面來看一個簡單的泛型方法的定義:

public class Main {

    public static <T> void out(T t) {
        System.out.println(t);
    }

    public static void main(String[] args) {
        out("findingsea");
        out(123);
        out(11.11);
        out(true);
    }
}

```abstract 

可以看到方法的參數徹底泛化了,這個過程涉及到編譯器的類型推導和自動打包,也就說原來需要我們自己對類型進行的判斷和處理,現在編譯器幫我們做了。這樣在定義方法的時候不必考慮以後到底需要處理哪些類型的參數,大大增加了編程的靈活性。

<T>是爲了規範參數;T表示的是返回值類型。





<div class="se-preview-section-delimiter"></div>

# Synchronization

The exact order in which one thread preempts the other threads is indeterminate.





<div class="se-preview-section-delimiter"></div>

## Synchronized Blocks

To indicate that these five lines of code should be executed together, wrap them in a synchronized block that synchronizes on the System.out object, like this:





<div class="se-preview-section-delimiter"></div>

```java
synchronized (System.out) {
    System.out.print(input + ": "); 
    System.out.print(DatatypeConverter.printHexBinary(digest)); 
    System.out.println();
}

Synchronization must be considered any time multiple threads share resources. These threads may be instances of the same Thread subclass or use the same Runnable class, or they may be instances of completely different classes.

Synchronized Methods

You can synchronize an entire method on the current object (the this reference) by adding the synchronized modifier to the method declaration.

public synchronized void write(String m) throws IOException{
    //
}

Simply adding the synchronized modifier to all methods is not a catchall solution for synchronization problems.

  • 減慢運行速度
  • 增加deadlock的機率

Deadlock

Thread Scheduling

It is possible for such a thread to starve all other threads by taking all the available CPU resources. With a little thought, you can avoid this problem. In fact, starvation is a considerably easier problem to avoid than either mis-synchronization or deadlock.

Priorities

Not all threads are created equal. Each thread has a priority, specified as an integer from 0 to 10. When multiple threads are ready to run, the VM will generally run only the highest-priority thread, although that’s not a hard-and-fast rule. In Java, 10 is the highest priority and 0 is the lowest. The default priority is 5, and this is the priority that your threads will have unless you deliberately set them otherwise.

public final void setPriority(int newPriority)

Preemption

There are two main kinds of thread scheduling: preemptive and coop‐ erative.

There are 10 ways a thread can pause in favor of other threads or indicate that it is ready to pause. These are:
• It can block on I/O.
• It can block on a synchronized object.
• It can yield.
• It can go to sleep.
• It can join another thread.
• It can wait on an object.
• It can finish.
• It can be preempted by a higher-priority thread.
• It can be suspended.
• It can stop.

Blocking

Blocking occurs any time a thread has to stop and wait for a resource it doesn’t have.

Yielding

A thread does this by invoking the static Thread.yield() method.

Joining threads

Java provides three join() methods to allow one thread to wait for another thread to finish before continuing. These are:

public final void join() throws InterruptedException
public final void join(long milliseconds) throws InterruptedException 
public final void join(long milliseconds, int nanoseconds)
    throws InterruptedException

The joining thread (i.e., the one that invokes the join() method) waits for the joined thread (i.e, the one whose join() method is invoked) to finish.

double[] array = new double[10000];
for (int i = 0; i < array.length; i++){
    array[i] = Math.random();
}
SortThread t = new SortThread(array);
t.start();
try{
    t.join();
    System.out.print("Minimum: " + array[0]);
    ...
}catch(InterruptedException ex){
    ...
}

比如在線程B中調用了線程A的Join()方法,直到線程A執行完畢後,纔會繼續執行線程B。

line 8 joins the current thread to the sorting thread. At this point, the thread executing these lines of code stops in its tracks. It waits for the sorting thread to finish running.

Notice that at no point is there a reference to the thread that pauses. It’s not the Thread object on which the join() method is invoked; it’s not passed as an argument to that method. It exists implicitly only as the current thread. If this is within the normal flow of control of the main() method of the program, there may not be any Thread variable anywhere that points to this thread.

A thread that’s joined to another thread can be interrupted just like a sleeping thread if some other thread invokes its interrupt() method.

Waiting on an object

等待時釋放lock,其他線程改變object時,會通知等待線程。
A thread can wait on an object it has locked. While waiting, it releases the lock on the object and pauses until it is notified by some other thread.

Another thread changes the object in some way, notifies the thread waiting on that object, and then continues.

Waiting pauses execution until an object or re‐ source reaches a certain state. Joining pauses execution until a thread finishes.

爲了wait on an object,需要暫停的thread首先得到 the lock on the object using synchronized 然後調用wait

public final void wait() throws InterruptedException
public final void wait(long milliseconds) throws InterruptedException 
public final void wait(long milliseconds, int nanoseconds)
    throws InterruptedException

These methods are not in the Thread class; rather, they are in the java.lang.Object class.

When one of these methods is invoked, the thread that invoked it releases the lock on the object it’s waiting on (though not any locks it possesses on other objects) and goes to sleep.

It remains asleep until one of three things happens:
• The timeout expires.
• The thread is interrupted.
• The object is notified.

Thread Pools and Executors

Starting a thread and cleaning up after a thread that has died takes a noticeable amount of work from the virtual machine, especially if a program spawns hundreds of threads

The Executors class in java.util.concurrent makes it quite easy to set up thread pools. You simply submit each task as a Runnable object to the pool. You get back a Future object you can use to check on the progress of the task.

import java.io.*;
import java.util.zip.*;

pubic class GZipRunnable implements Runnable{
    private final File input;

    public GZipRunnable(FIle input){
        this.input = input;
    }

    @Override
    public void run(){
        if(!input.getName().endsWith(".gz")){
            File output = new File(input.getParent(), input.getName() + ".gz");
            if(!output.exists()){
                try{
                    InputStream in = new BufferedInputStream(new FileInputStream(input));
                    OutputStream out = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(output)));
                    int b;
                    while((b = in.read()) != -1)out.write(b);
                    out.flush()
                }catch(IOException ex){
                    System.err.println(ex);
                }
            }
        }
    }
}
import java.io.*;
import java.util.concurrent.*;

public class GZipAllFiles{
    public final static int THREAD_COUNT = 4;

    public static void main(String[] args){
        ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);

        for(String filename : args){
            File f = new File(filename);
            if(f.exists()){
                if(f.isDirectory()){
                    FIle[] files = f.listFiles();
                    for(int i = 0; i < files.length; i++){
                        if(!files[i].isDirectory()){
                            Runnable task = new GZipRunnable(files[i]);
                            pool.submit(task);
                        }
                    }
                }else{

                }
            }
        }
        pool.shutdown()
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章