问题:当服务器需要处理成千上万个同时的连接时,性能就会变得像爬行一样慢。
解决方法:
1. 重用进程,对于每个连接不再是创建新的进程。服务器启动时,就创建固定数量的进程来处理请求。
2. 使用轻量级的线程处理连接,每个单独的进程都有自己的一块内存,但线程在资源使用上更宽松,因为它们会共享内存。使用线程代替进程,可以让服务器的性能提升三倍。
如果一个应用同时需要数千个持续很长时间的连接(这种应用相当少见),就要考虑一步I/O而不是线程。需要利用Java的NIO来实现编程,现在可以不用看这块。
使用线程,也会带来了一些问题:不同线程共享相同的内存,一个线程完全有可能破坏另一个线程使用的变量和数据结构。所以,每个线程只有在确保资源不会被改变或者它有独占访问权的时候才可以使用某个资源。但是,如果两个线程太过小心,每个线程都在等待对资源的独占访问权,却永远都得不到,这会导致死锁。总之,最终导致编程变的困难。
一、线程的实现:Thread Vs Runnable
当一个类需要extends其它类时,此时只能implements Runnable.
二、从线程返回信息
利用回调,也就是观察者(Observer)设计模式。
注意:不要在构造函数中启动线程,特别是线程将回调原来的对象时。
这里有一个竞争条件,可能会在构造函数结束而对象完全初始化之前允许新线程进行回调。
三、Future、Callable和Executor
Java 5 引入了编程的一个新方法,通过隐藏细节可以更容易地处理回调。不再是直接创建一个线程,你要创建一个ExecutorService,它会根据需要为你创建线程。
package zhen.concurrent;
import java.util.concurrent.Callable;
public class FindMaxTask implements Callable<Integer> {
private int[] data;
private int start;
private int end;
public FindMaxTask(int[] data, int start, int end) {
super();
this.data = data;
this.start = start;
this.end = end;
}
@Override
public Integer call() throws Exception {
System.out.println("begin: start="+start+", end="+end);
int max = Integer.MIN_VALUE;
for(int i=start;i<end;i++){
if(data[i]>max)
max = data[i];
}
System.out.println("max: start="+start+", end="+end+", max="+max);
return max;
}
}
package zhen.concurrent;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
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();
}
FindMaxTask task1 = new FindMaxTask(data, 0, data.length/2);
FindMaxTask task2 = new FindMaxTask(data, data.length/2, data.length);
ExecutorService service = Executors.newFixedThreadPool(2);
Future<Integer> future1 = service.submit(task1);
Future<Integer> future2 = service.submit(task2);
service.shutdown();
System.out.println("shutdow ThreadPool and wait for get...");
return Math.max(future1.get(), future2.get());
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
int[] data = {2,4,5,74,63,7,2,47,6,467,5,458};
int result = max(data);
System.out.println(result);
}
}
四、同步—-多线程访问相同的资源
问题:
package zhen.thread;
public class BadThread extends Thread {
private String name;
public BadThread(String name) {
this.name = name;
}
@Override
public void run() {
// synchronized (System.out) {
System.out.println("1:" + name);
System.out.println("2:" + name);
System.out.println("3:" + name);
try {
// 使用休眠来模拟耗时操作
// 此时线程暂停,但不放弃所占有的锁
// 因此容易造成需要相同锁的线程阻塞
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4:" + name);
System.out.println("5:" + name);
// }
}
}
假如启动三个上面的线程,希望结果是下面的样子:
1:one
2:one
3:one
4:one
5:one
1:two
2:two
3:two
4:two
5:two
1:three
2:three
3:three
4:three
5:three
就是one和one的连续,two和two的连续,three和three连续,任意两者不能混合。
但是上面的代码输入结果如下:
1:two
1:three
1:one
2:three
2:two
3:three
2:one
3:two
3:one
4:one
4:three
5:three
4:two
5:two
5:one
这不是我们想要的结果
解决方法:
同步代码块、同步方法(使用synchronized修饰符)
把上述代码中的注释取消即可
同步的替代:
1.使用局部变量,避免使用字段
2.使对象不可变(像String类),将所有字段声明为private 和 final
3.使用java.util.Collections方法把映射和列表等集合包装在一个线程安全的版本中。
Set foo;
Collections.synchronizedSet(foo);
五、下面看看线程放弃控制权的几种方法
1.Thread.yield()
不放弃已经拥有的锁
其他拥有相同优先级的线程有机会运行
2.Thread.sleep()
不放弃已经拥有的锁
不仅其他拥有相同优先级的线程有机会运行,较低优先级的线程也有机会
可以同该线程的interrupt()方法打断休眠
3.Thread.join()——(从Java 5之后可能那么重要了,因为有了Executor和Future)
比如A线程通过B线程计算数据,当A线程执行到某一点,需要等待B线程结束才能继续。
此时,A可以调用B.join()来等待B的结束。
通过Executor和Future可以更简单的实现此功能。
4.Object.wait() 和 Object.notify()
线程可以等待一个它锁定的对象,在等待时,它会释放这个对象的锁并暂停,直到它得到其他线程的通知。另外一个线程以某种方式修改这个对象,通知等待对象的线程,然后继续执行。这与Thread.join()不同,并不要求通知线程必须结束。
一般要将wait()调用放在检查当前对象状态的循环中,不要假定因为线程得到了通知,所以对象现在就处于正确的状态。
六、如何避免死锁
如果多个对象需要操作相同的共享资源集,要确保以相同的顺序请求这些资源。例如,如果类A和类B需要独占访问对象X和对象Y,要确保两个类都先请求X后请求Y。除非已经拥有X,否则这两个类都不能请求Y,如果做到这一点,死锁就不是问题了。