疯狂JAVA 16章 读书笔记 2015.3.12
疯狂JAVA 16章 读书笔记 2015.3.26
- 线程和进程:
- 线程和进程:
- 进程:系统进行资源分配和调度的一个独立单位
- 线程:线城是进程的执行单元
- 进程的特点:
-
- 独立性:拥有自己独立的实体、资源、私有的地址空间等
- 动态性:有生命周期和不同的状态
- 并发性:多个进程可以在单个处理器上并发执行,不互相影响
- 线程的特点:
-
- 进行创建后,主线程就被创建了; 一个线程必须有一个父进程
- 线城是独立运行的,它不知道时候还有其他线程存在;
- 抢占式执行
- 一句话概括:操作系统可以同时执行多个任务,每个任务就是进程,进程可以同时执行多个任务,每个任务就是线程
-多线程的优势
- 进程之间不能共享内存,但是线程之间共享内存非常容易
- 创建进程时需要为该进程重新分配系统资源,但创建线程代价小得多,因此多线程来实现多任务并发比多进程的效率高
-线程的创建和启动
- 继承Thread类创建线程类
-
- 继承Thread类,重写run方法,创建Thread子类的实例,start()启动该线程
- 无法共享线程类的实例变量
- 用this获得当前线程对象
- 实现Runnable接口创建线程类
-
- 定义Runnable接口的实现类,重写run方法实现Runnable实例作为Thread的target创建Thread对象,start()启动该线程
- 可以共享线程类的实例变量(因为多个线程可以共享同一个线程类(实际上是现成的target类)的实例变量)
- 用Thread.currentThread()方法获得当前线程对象
- 使用Callable和Future创建线程(类似上一种方法)
- Thread 构造方法:
-
-不同线程创建方法对比:(总体来说Runnable方法优于Thread方法)
实现Runnable( Callable或者Future) | 继承Thread类 |
还可以继承其他类 | 不能再继承其他父类 |
可共享同一个target对象 | 不能共享同一个线程的实例变量 |
编程稍复杂 | 编程简单 |
-线程的生命周期
- 新建(new)状态 :与其他对象同,JAVA虚拟机分配内存,初始化成员变量的值,无动态特征
- 就绪(Runnable)状态:调用start()方法之后,JAVA虚拟机为其创建方法调用栈和程序计数器(未开始运行,何时运行取决于JVM里线程调度器的调度)
- 运行和阻塞状态:
-
- 线程调用sleep()方法主动放弃所占用的处理器资源 运行->阻塞
- 调用了阻塞式IO方法,该方法返回之前阻塞
- 线程试图获得同步监视器,但该同步监视器被其他线程持有,阻塞
- 等待某个通知(notify)
- 线程死亡
-控制线程
-join()
让一个线程等待另一个线程完成的方法
Join方法会抛出interruptExceptinon
-sleep(long millis)
让当前正在执行的线程暂停一段时间
sleep()方法会抛出interruptExceptinon
- yield()
让当前正在执行的线程暂停,但不阻塞,强制将其转入就绪状态
当某个线程调用yield()暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会
- 改变线程优先级
默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级
setPriority(int new Priority) / getPriority() 来设置和获取指定线程的优先级
MAX_PRIORITY /MIN_PRIORITY / NORM_PRIORITY (10/1/5)
- 守护线程 Daemon Thread(后台线程)
通过
setDaemon(true)执行,
isDaemon()判断是否是后台线程
setDaemon(true)必须在start()方法之前调用,否则会引发IllegalThreadStateException异常
如果所有的前台线程都死亡,后台线程会自动死亡
- sleep 和 yield 方法的区别:
sleep()方法暂停后,给其他线程执行机会,不理会其他线程的优先级;yield()方法只会给优先级相同,或优先级更高的线程执行机会
sleep()方法会将线程转入阻塞状态;yield()方法强制当前线程进入就绪状态
sleep()方法声明抛出了InterruptedException异常,要捕获或者显示抛出异常;yield()方法没有声明抛出任何异常
sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行
-线程同步
加锁->修改->释放锁
- 同步代码块
上面代码中的synchronized后括号里的obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定
- 同步方法
用syncronized修饰实例方法(非static),无需显示指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
- 何时释放同步监视器
执行同步方法快或同步方法时,执行了同步监视器对象的wait()方法,当前线程暂停,释放同步监视器
执行Thread.sleep()或者Thread.yield()方法时不会释放同步监视器
- 同步锁(Lock)
比较常用ReentrantLock(可重入锁),使用该Lock对象可以显示的加锁、释放锁,代码格式如下:
- 死锁
当两个线程互相等待对方释放同步监视器时就会发生死锁,Java虚拟机没有检测,也没有采取措施来处理死锁情况
-线程通信
对于使用synchronized修饰的同步方法,this就是同步监视器,因此可以直接调用这三个方法,对于使用synchronized修饰的同步代码块,synchronized括号里的对象是同步监视器,必须使用该对象调用这三个方法。
- 线程相关类:
- wait()释放对该同步监视器的锁定- notify()选择是任意的;只有当前线程放弃对该同步监视器的锁定后(使用wait方法),才可以执行被唤醒的线程- notifyAll()唤醒在此同步监视器上等待的所有线程;只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程
- 使用Condition控制线程通信:
Condition实例被绑定在一个Lock对象上,要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可- await()-signal()-signalAll()![]()
![]()
- 使用阻塞队列(BlockingQueue接口)控制线程通信:(Java5)
- 特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;
当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
-put(E e)
-take()
-几个实现:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue...
-线程池
概念:系统一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情况下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
- Executors工厂类: java5开始,java内建支持线程池
- 使用Executors类的静态工厂方法创建一个ExecutorsService对象,该对象代表一个线程池
- 使用Runnable或Callable实现类的实例,作为线程执行任务
- 调用ExecutorsService对象的
submit()方法来提交R或C实例
- 当不想提交任何任务时,调用
ExecutorsService.shutdown()方法关闭线程池
结果:两个线程交替进行:
- 线程相关类:
- ThreadLocal类
- 为了隔离多个线程的数据共享,从根本上避免多个线程之间对共享资源(变量)的竞争
- 如果多个线程之间需要共享资源,以达到线程之间的通信功能,就是用同步机制;
如果仅仅需要隔离多个线程之间的共享资源冲突,则可以使用ThreadLocal
- 另外:
Collections类里有一组synchronized方法
可以将Collection封装成同步的
- 线程安全的集合类:
java.util.concurrent包
- concurrent 开头的 : 支持高并发访问的集合
- CopyOnWrite 开头的 : 复制底层数组的方式实现写操作;适合用于读取操作远远大于写入操作的场景中,例如缓存等