线程是一个程序内部的一条执行路径 ; 线程做为调度和执行的单位, 每个线程拥有独立的运行栈和程序计数器 , 线程切换的开销小比进程小
线程
程序 : 为完成特定任务 , 用某种语言编写的一组指令 . 即指的是一段静态的代码
进程 : 程序的一次执行过程或是正在运行的一个程序 . 这说明了资源分配的单位 , 程序在运行时会为每个进程分配不同的内存区域 ,进程可以细分为多个线程 , 每个线程,有自己独立的 :栈 , 程序计数器 ; 多个线程 , 共享一个进程中的结构 : 方法区和堆
线程 : 是一个程序内部的一条执行路径 ; 线程做为调度和执行的单位, 每个线程拥有独立的运行栈和程序计数器 , 线程切换的开销小
并行 : 多个CPU同时执行多个任务 ,
并发 : 一个CPU(采用调度)同时执行多个任务
创建线程的四种方式
1 ). 继承Thread类
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把
run()方法称为线程执行体。 - 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
1 | 构造方法: |
2 ).实现Runnable接口
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread对象参数来创建Thread对象,该Thread对象才是真正
的线程对象。 - 调用线程对象的start()方法来启动线程。
★Thread类与Runnable接口两种方式的区别
一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
★结论 : 开发中优先选择Runnable接口实现
3 ). 实现Callable接口
- 创建一个实现Callable的实现类
- 重写Call方法 , 将此线程需要执行的操作声明在Call( )中
- 创建Callable接口的实现类对象
- 将实现类对象传递到FutureTask的构造器中 ,创建FutureTask对象
- 将FutureTask对象做为参数传递Thread类的构造器中 , 创建Thread对象 ,调用start 方法
- 这里 可以回去Callable中的Call方法的返回值 , FutrueTask对象调用get()可以获取
★多线程实现Callable接口比Runnable接口强大
- call()可以有返回值
- call()可以抛出异常 , 被外面的操作捕获 , 获取异常的信息
- Callable支持泛型
4 ). 线程池创建
- 创建线程池对象。 public static ExecutorService newFixedThreadPool(int nThreads)
- 创建Runnable接口子类对象。
- 提交Runnable接口子类对象。public Future<?> submit(Runnable task)
- 关闭线程池
线程的六个生命周期
1 | public enum State { |
线程安全的两种方式
Synchronized锁
1). 同步代码块
1 | synchronized(锁对象){} |
2). 同步方法
1 | 格式:在返回值类型前面加上 synchronized关键字进行修饰 |
Lock锁
1 | //使用lock的实现类ReentrantLock |
Synchronized与Lock两者之间的不同
相同 : 都是为了解决线程安全
不同点 : Synchronized机制在执行完同步代码之后 就会自动的释放同步监视器
Lock需要手动的启动同步(lock()) , 同时结束时也需要手动实现(unlock())
线程间的通讯
wait() : 一旦执行此方法 ,当前线程进入阻塞状态 ,并释放同步监视器
notify() : 一旦执行此方法 , 就会唤醒被wait的一个线程 . 如果是多线程 , 唤醒优先级高的线程
notifyAll() : 一旦执行此方法 , 就会唤醒所有被wait()的线程
注意 :
- wait() , notify() , notifyAll () 三个方法必须使用在同步代码块或同步方法中
- wait() , notify() , notifyAll () 的调用者必须是同步代码块或同步方法中的同步监视器 , 否则就会出现IllegelMonitorStateException异常
- 为确保所有对象都能充当同步监视器 , 那么wait() , notify() , notifyAll ()三个方法都是定义在java.lang.Object上
★sleep() 和wait () 的异同?
相同点 : 一旦执行方法 , 都可以使得当前的线程进入阻塞状态
不同点 : 1) 两个方法声明的位置不同 : thread类中声明了sleep() , Object类中声明wait()
2) 调用的要求不同 : sleep()可以在任何需要的场景下调用 , wait()必须使用同步代码锁
3) 关于是否释放同步监视器 : 如果两个方法都使用在同步代码块或同步方法中 , sleep() 不会释放锁 , 而wait () 会释放锁
AQS分析(转述+分析)
java有了synchronized关键字为什么还会有 Reentrantlock?
线程在执行的时候会调用start的本地方法
1 | private native void start0();//说明Jvm的线程==操作系统的线程 , 那么synchronized就会去操作操作系统的函数来使线程进行阻塞 |
1.6之前 , 线程synchronized会操作系统的函数 ,这样每次进行同步的话 ,CPU就会从用户态–>内核态–>用户态 , 那么程序的效率就会降低 ,于是就有了Reentrantlock
1 | public class LockSupport { |
1 | public class ReentrantLock implements Lock, java.io.Serializable { |
其中AQS结构是一个类似于双向的链表的结构
1 | private transient volatile Node head; //队首 |
也就是单线程 - 交替执行 , 和队列无关 – jdk级别就能解决同步问题