进程
进程是处于运行过程中的程序,具有独立的功能,是系统进行资源分配和调度的独立单位。
- 独立性
进程是系统重独立存在的实体,它拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个进程不能访问其他进程的地址空间;
- 动态性
进程是一个正在系统中活动的指令集合,包含子时间的概念,具有自己的生命周期和状态;
- 并发性
在单个处理器上,多个进程可以并发地执行,并且在执行时他们彼此之间不会互相的影响。
并发与并行的区别
- 并行
在同一时刻,有多条指令在多个处理器上同时执行;
- 并发
- 在同一个时刻,某一个处理器只能执行一条指令;
- 多个进程的指令可以被快速的轮换执行,使得宏观上具有多个进程同时执行的效果;
对于多核计算机,当进程数多于核心数,也会存在并发的行为!
线程
- 线程扩展了进程的概念,使得同一个进程可以同时并发处理多个任务;
- 线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程;
- 线程不拥有系统资源,它与父进程里的其他线程共享父进程拥有的全部系统资源
- 系统可以同时执行多个任务,每个任务就是线程;进程可以同时执行多个任务,每个任务就是线程。
线程优点
- 容易共享内存
进程之间不能共享内存,但线程之间共享内存非常容易,因为与分隔的进程相比,线程之间的隔离程度要小,他们共享内存、文件句柄,其他每个进程应有的状态;
- 运行效率更高
系统创建进程时需要为其分配系统资源,但创建线程时不需要,所以线程的运行效率更高;
- 编程方式简单
Java内置了多线程功能的支持,并不是简单地对操作系统的底层进行调度,编程更方便。
线程的创建方式
Java的线程模型
- Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例;
- 线程用于完成一定的任务(执行一段程序流),Java使用线程执行体来代表这段程序流
- 线程使用过程:定义线程执行体->创建线程对象->调用线程对象的方法来启动线程。
继承Thread类
- 定义Thread类的子类,并重写该类的run()方法(线程体);
- 创建Thread类的子类的实例,即线程对象;
- 调用线程对象的start()方法,即启动这个线程。
实现Runnable接口
- 定义Runnable接口的实现类,并实现该接口的run()方法(线程体);
- 创建Runnable实现类的实例,并以此作为target来创建Thread对象;
- 调用Thread对象的start()方法来启动线程。
实现Callable接口
- 定义Callable接口的实现类,并实现该接口的call()方法(线程体);
- 创建Callable接口的实例,并使用Future接口来包装Callable对象,最后使用Future对象(线程的返回值)作为target来创建Thread对象;
- 调用Thread对象的start()方法来启动线程;
- 调用Future对象的get()方法获取线程的返回值。
三种方式的比较
继承父类的方式
优点:编程比较简单
缺点:线程类已经继承Thread类,所以不能再继承其他的父类。
实现接口的方式
优点:线程类只实现了接口,还可以继承于其他父类
缺点:编程比较麻烦。
建议采用实现Runnable接口的方式,若需要获取返回值则采用实现Callable接口的方式
线程的生命周期
当线程被创建之后,它不会立刻进入运行状态,也不会一直处于运行状态。
在线程的生命周期中,需要经过如下5中状态:
1.新建(New)2.就绪(Ready)3.运行(Running)4.阻塞(Blocked) 5.死亡(Dead)
控制线程
线程休眠
Thread类提供了休眠方法,可以让当前线程暂停一段时间:
static void sleep (long millis)
static void sleep(long millis,int nanos)
sleep() vs yield()
sleep()会让线程进入阻塞状态,而yield()会让线程进入就绪状态;
sleep()给其他线程运行的机会,不理会其他线程的优先级,yield()考虑线程的优先级,只会给优先级相同或者更高的线程执行机会。
等待线程
Thread类提供了等待方法,可以让调用方等待该线程直至它死亡。
后台线程
后台线程,也叫守护线程,或精灵线程;它是在后台运行的,它的任务就是为其他线程提供服务;如果所有的前台线程都死亡,则后台线程会自动死亡。线程默认是前台线程,Thread类提供如下方法来设置后台线程:
void setDaemon (boolean on); boolean isDaemon()
前台线程都死亡后,JVM会通知后台线程死亡,但他从接收指令到做出响应,需要一定的时间。
线程优先级
线程安全问题
死锁
当两个线程互相等待对方释放同步监视器时会发生死锁,应避免这种情况的出现;
线程通信
线程通信的前提是要求支持并发读写数据,所以线程通信前必须要对共享资源加锁。
synchronized
synchronized(obj) {...}
synchronized void fun() {...}
Lock
Condition condition=lock.newCondition();
阻塞队列
生产者消费者模式
- 某个模块负责产生数据,另一个模块负责处理数据;
- 产生数据的模块被称为生产者;处理数据的模块被称为消费者;
- 需要有一个缓冲区位于消费者与生产者之间,作为沟通的桥梁;
- 生产者只负责吧数据放入缓冲区,而消费者从缓冲区取出数据;
BlockingQueue
BlockingQueue是Queue的子接口,他的主要作用是作为线程通信的工具;
BlockingQueue增加了2个支持阻塞的方法:
void put(E e):尝试把元素e放入队列中,如果该队列的元素已满,则阻塞该线程;
void take():尝试从队列的头部取出元素,如果元素已空,则阻塞该线程;
线程组
ThreadGroup类代表线程组,它可以包含一批线程,并对这些线程进行统一的管理;
- 每个线程都有对应的线程组,若程序未显示指定线程的线程组,则该线程属于默认线程组;
- 默认情况下,子线程与它的父线程组属于同一个线程组,而main线程归属于Main线程组;
- 一旦线程加入某个线程组,则该线程将一直属于这个线程组,中途不允许修改其线程组。
创建线程组
使用线程组
线程池
启动线程的成本是比较高的,通过线程池可以实现线程的复用,从而提高性能;线程池可以控制程序中的线程的胡亮,避免超出系统的负荷,导致系统的崩溃。
- 创建线程池之后,他会自动创建一批空间的线程;
- 程序键线程体传给线程池,他就会启动一个空闲的线程来执行线程体;
- 当线程体执行结束后,该县城不会死亡,而是变成空闲的状态继续使用。
创建线程池
使用线程池
ForkJoinPool
- Fork/Join是一种思想,旨在充分利用多核资源,用于执行并行任务;
- ForkJoinPool是ExecutorService的实现类,是上述思想的实现;
- 它的做法是,将一个大的任务分割成若干小任务,最终汇总成每个小任务结果,从而得到大任务的结果;
创建ForkJoinPool
使用ForkJoinPool
ThreadLocal
线程安全的集合
包装不安全的集合
包装安全的集合
网络编程
InetAddress
public class InetAddressDemo {public static void main(String[] args) throws IOException {InetAddress baidu= InetAddress.getByName("www.baidu.com");System.out.println(baidu.getHostAddress());System.out.println(baidu.isReachable(2000));InetAddress local=InetAddress.getByAddress(new byte[]{127,0,0,1});System.out.println(local.getHostName());System.out.println(local.isReachable(2000));}
}
线程同步
同步:对在一个系统中所发生的事件之间进行协调,在事件上出现一致性与统一化的现象;
加锁
1.加锁>修改>解锁
2.加锁机制可以保障在任一时刻只有一个线程可以进入共享资源的代码区(临界区)。
synchronized