程序(program):是为完成特定的任务,用某种语言编写的一组指令的集合。
即指一段静态的代码,静态队形。
进程(process):是程序的一次执行过程,或正在运行的一个程序。是一个动态的过程:有它自身
的产生、存在和消亡的过程。——生命周期
(程序是静态的,进程是动态的)
(进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域)
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
->若一个进程同一时间并行执行多个线程,就是支持多线程的
->线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
->一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,
可以访问相同的变量和对象。这就使得线程之间通信更简便,高效。但多个线程操作共享的系统资源可能会带来安全隐患。
(JVM中方法区和堆每个线程一份,栈和程序计数器是每个线程一份)
单核CPU和多核CPU的理解:
->单核CPU:其实是一种假的多线程,因为在一个时间单元内,只能执行一个线程的任务。
但因为CPU时间单元特别短因此感觉不出来
多核CPU:可以更好的发挥多线程的效率。
(一个Java应用程序至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程,如果发生异常,会影响主线程)
并行与并发:
->并行:多个CPU同时执行多个任务。
->并发:一个CPU(采用时间片)同时执行多个任务。(如:秒杀,多个人做同一件事)
使用多线程的优点:
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法)肯定会比
多个线程来完成用的时间更短,为何仍需要多线程的?(线程之间的切换也需要开销)
多线程的优点:
1、提高应用程序的响应。对图像化界面更有意义,可增强用户体验
2、提高计算机系统的CPU利用率
3、改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
为什么需要多线程:
1、程序需要同时执行多个任务
2、程序需要实现一些需要等待的任务时,如用输入、文件读写等
3、需要一些后台运行的程序时
线程的创建和使用:
1、继承Thread类:
->继承Thread类
->重写run方法 :1、启动当前线程 2、调用当前线程的run()
->创建Thread子类的对象
->通过此对象调用start()方法
(如果不调用start()直接调用run()方法则不会创建新的线程)
(不能通过调用多次start()方法创建多个线程,一个对象只能调用一次,需要就重新创建对象)
2、实现Runnable接口
->创建一个实现了Runnable接口的类
->实现类去实现Runnable接口中的抽象方法run()
->创建实现类的对象
->将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
->通过Thread类的对象调用start()
创建线程方式的比较:
->优先选择实现Runnable接口的方式(jdk5以前,5以后一般使用线程池)
->实现的方式没有类的单继承的局限性
->实现的方式更适合处理多个线程有共享数据的情况
(Thread类也实现的Runnable接口)
3、实现Callable接口(jdk5以后新增)
->创建一个实现了Callable接口的类
->重写call方法
->创建实现类的对象
->将实现类的对象作为参数创建FutureTask对象
->将FutureTask对象作为参数创建Thread对象并调用start()方法启动线程
--》可以通过FutureTask对象实例的get()方法获取call方法的返回值
(为什么使用Callable而不是Runnable:
1、call()方法可以有返回值)
2、call方法可以抛出异常
3、Callable支持泛型)
4、线程池(jdk5以后新增)
->使用Executors创建合适的线程池
(可以通过ExecutorService的实现类ThreadPoolExecutor设置线程池的属性)
->执行指定的线程操作。需要提供实现Runnable接口或Callable接口的实现类对象
executors.execute(Runnable接口的实现类对象);
executors.submit(Callable接口的实现类对象);
->关闭线程池 executors.shutdown()
为什么使用线程池?
->经常创建和销毁线程使用量特别大的资源,比如并发情况下的线程对
性能影响很大
->便于线程的管理
线程的常用方法:
currentThread():静态方法返回当前线程对象
getName()/setName():获取/设置当前线程名
start():启动当前线程并调用run()方法
yield():释放当前cpu执行权(释放后还有可能分配给当前线程)
join(): 在线程a中调用线程b的join()方法,线程a进入阻塞状态直到线程b执行完以后线程a结束阻塞状态
sleep(Long millis):让当前线程休眠指定的时间,在指定的时间内当前 线程是阻塞状态
isAlive():判断当前线程是存活
wait():使当前执行线程阻塞,会释放锁
notify()/notifyAll():唤醒被wait()的线程
(wait()、notify()/notifyAll()是Object中的方法,
必须使用在同步代码块或同步方法中,使用lock时不能使用。
三个方法的调用者必须和同步监视器是同一个对象)
(sleep()和wait()的异同:都会是当前线程进入阻塞状态
sleep()是Thread类中的静态方法,wait()是Object类中的方法。
sleep()可以在任何需要的地方调用,wait()必须使用在同步代码块或同步方法中。
sleep()不会释放锁,wait()可以释放锁)
线程的优先级:
1、MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
2、获取和设置当前线程的优先级
getPriority()/setPriority(int p)
(高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程该概率的情况下被执行。并不意味着当高优先级的线程执行完以后,低优先级的线程才执行)
线程的分类:守护线、用户线程
线程的生命周期:
Thread类中的枚举类State描述了线程的几种状态分别是
NEW 新建
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
->新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
->就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是还没有分配到CPU资源
->运行:当就绪的线程被调度并获得CPU资源时进入运行状态,run()方法定义了线程的操作和功能
->阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行进入阻塞状态
->死亡:线程完成了它的全部工作或线程被提前强制地中止或出现异常导致结束
(运行状态可以和就绪状态相互切换[获得和失去CPU资源调用yield()方法可以从运行状态回到就绪状态])
(运行状态调用sleep()、join()、wait()或等待同步锁时进入阻塞状态)
线程的同步:(多个线程处理共享数据会出现线程安全问题)
->当一个线程在操作共享数据时其它线程不能参与进来,知道当前线程操作完成后
其它线程才可以操作共享数据(在java中通过同步机制来解决线程的安全问题)
解决方式:
->同步代码块:
synchronized(同步监视器){ //同步监视器:也就是锁(任何一个类的对象都可以充当锁,但是多个线程必须公用一把锁)
//需要被同步的代码 (操作共享数据的代码)
}
->同步方法:
如果操作共享数据的代码完整的在一个方法中可以把方法直接生命成同步方法
(非静态的同步方法,同步监视器是this,静态的同步方法,同步监视器是当前类本身(.class))
(同步的方式解决了线程的安全问题。但是操作同步代码时只能有一个线程参与,其它线程等待。相当于是一个线程,效率低)
单例模式(线程安全的懒汉)
class Bank{
private Bank(){};
private static Bank instance = null;
public static Bank getInstance(){
if(instance == null){
synchronized (Bank.class){
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
死锁的问题:
死锁:不同的线程分别占用对方需要的同步资源不放弃,
都在等待对方放弃自己需要的同步资源,就形成了死锁。
出现死锁所有的线程都会处于阻塞状态无法继续。
解决办法:
->专门的算法、法则
->尽量减少同步资源的定义
->尽量避免嵌套同步
Lock锁解决线程安全问题(jdk5.0以后新增方式):
Lock是一个接口,使用时创建实现类对象调用lock()方法开启同步,结束同步调用unlock()方法
与synchronized的区别:synchronized在执行完相应的同步代码后会自动释放同步监视器,
Lock需要手动开启同步,同时结束同步也需要手动实现(lock()和unlock())