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()
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值