线程是进程的实体,是cpu调度和分派的最小单位;进程则是系统资源分配的最小单位;进程间的资源是独立的,而同一进程里的线程共享该进程的资源。多线程是为了同时完成多项任务,提高资源的使用效率来提高系统的效率。所以优势在于充分利用cpu的空闲时间片。
主线程和子线程的执行顺序
线程调度的机制和Jdk版本也有关系,新的jdk的版本时间片切割的更好,看上去像是同时进行的而老版本不会频繁对时间切片,因此线程1可能执行完了才执行线程2(以上所说都是在一个处理器的情况下)。
1 | public class ThreadB extends Thread { |
1 | public class ThreadA { |
上面打印的结果一开始我以为会先执行ThreadB的run方法的打印语句,但是这个想法是错误的,因为还有主线程的存在,由于线程启动需要一段时间,因此会先打印main线程得到cpu并执行方法里面的语句,直到main线程调用wait()把cpu让出去了,ThreadB才开始执行。
wait()和notify()
当某一个线程正在某一个对象的同步方法中调用了该对象的wait方法,则会释放该对象的独占锁并放入该对象的等待对列;所以在调用wait之前,该线程就必须获得该对象的锁,因此wait必须在同步代码块里面才能用;否则报.IllegalMonitorStateException,没有监视器。直到某个线程调用该对象的notify()或者notifyAll()时才有机会继续进行。(notify是唤醒该对象等待队列上的一个线程,如果有很多个线程在等待就随机唤醒一个;notifyAll是唤醒等待队列上所有的线程,然后这些线程就去竞争资源,谁获得了这个对象的锁才可以继续执行)
线程池
new Thread(),每次new一个线程会性能差,缺乏统一的管理,缺乏更多的功能入定时执行,定期执行等;而线程池则可以:
- 重用存在的线程,减少线程创建、消亡的开销;
- 可以控制最大并发线程数
- 提供定时、定期执行等功能
Java的四种线程池(Executors(执行器)创建)
- newCachedThreadPool() 可缓存线程池,可以灵活的创建和回收线程
- newFixedThreadPool(int size) 定长线程池,可以控制最大并发数
- newSchedualThreadPool(int size) 定长线程池,支持定时、定期的执行任务
- newSingleThreadExecutor()单线程化的线程池,只会用唯一个线程工作,保证任务按指定顺序执行(先进先出,后进先出);可以序列化处理任务。比如有大量线程都需要使用使用文件系统上传文件,为了不会过度使用文件系统,那么可以选择单线程,保证任意时刻只有一个线程在运行。
Callable接口
如果你希望在线程结束后返回值那么可以用Callable而不是Runnable.
Callable是JAVA SE5引进的一种具有类型参数的泛型,类型参数是call里面返回的类型。如果要调用call方法,就需要使用executorService.submit()方法调用,返回的是future对象,用callable返回的类型进行的参数化,可以用isDone查看future是否已经完成。当任务完成时可以用get得到。
newCachedThreadPool()举例
ExecutorService是具有生命周期的executor;
shutdown(),执行后不再接收新任务,如果里面有任务,就执行完1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class ThreadCallTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService exec= Executors.newCachedThreadPool();
Executors.newFixedThreadPool(5);
Executors.newScheduledThreadPool(5);
Executors.newSingleThreadScheduledExecutor();
Future<Integer> sum=exec.submit(new ThreadCall());
exec.shutdown();
System.out.println(sum.get());
}
}
public class ThreadCall implements Callable<Integer> {
int sum;
public Integer call() throws Exception {
for(int i=0;i<8;i++)
sum+=i;
return sum;
}
}
线程的异常逃逸
由于线程的本质特性,使得我们不能捕获从线程中逃逸的异常,一旦异常逃出任务的run()方法,就会向外传播到控制台。当然这种异常通常是我们不知道的什么适合会发生的运行时异常。
解决的方法是,java SE5,允许在每个thread对象上附着一个异常处理器Thread.UncaughtExceptionHandler.当线程因为未捕捉异常而临近死亡的时候会调用该处理器的方法。给每个线程都自己加一个异常处理器是很麻烦的,因此可以建一个线程工厂(需要继承并发包下的ThreadFactory),给每一个线程都自动加上异常处理器。线程工厂就是让工厂帮我们创建线程,当我们需要new一个线程的时候就直接调用工厂的生产方法就可以了。
1 | /** |
1 | /** |
1 | /** |
synchronized与lock
sychronized的缺陷
- 某个线程在synchronized代码中执行,由于获得了锁对象,其他也需要这个锁对象的线程只能等待这个线程释放锁才可以做自己的事情。如果此时该线程sleep了(sleep会让出cpu却不会释放锁)或者等待IO,别的线程只能等啊等,这样线程执行效率会很低;
- 多线程的读文件按道理是不应该加锁,因为多个线程一起读文件并不会对文件造成影响;如果用synchronized对文件加锁(防止多个线程同时写文件或者同时读写文件造成文件内容有问题)就会使一个线程在读文件而别的线程无法读的问题,也会大大影响效率。ReentrantReadWriteLock(可重入读写锁)可以解决这个问题。
Lock和synchronized的区别
- synchronized不需要手动释放锁,执行完同步代码块就自动释放;而lock需要手动释放,调用unlock()方法。如果忘了就可能导致死锁;
- synchronized是java语言的关键字,是内置特性;而Lock是一个类。