1 多线程
并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程与线程的区别:
- 进程有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
- 线程堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
注意:
-
因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
-
Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
-
由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。
-
计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令。所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务。那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度。JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性
多线程代码实现:
方式一: 继承Thread类.
-
定义一个类(MyThread), 继承Thread类.
public class MyThread extends Thread
-
重写Thread#run()方法.
-
把要执行的代码放到run()方法中.
@Override
public void run() { 要执行的代码}
-
开启线程.
MyThread mt = new MyThread();
mt.start();
注意:
mt.run(); //这样写不会报错, 但是只是普通的方法调用而已, 并没有开启线程;
对于同一个线程对象,不能调用两次start()方法。会报 IllegalThreadStateException(非法的线程状态异常)
匿名内部类写法:
new Thread() {
@Override
public void run() {
要是执行的代码 }
}.start();
方式二: 实现Runnable接口。
-
定义一个类(MyRunnable), 实现Runnable接口
public class MyRunnable implements Runnable
-
重写Runnable#run()方法
-
把要执行的代码放到run()方法中
@Override
public void run() {
要执行的代码 }
-
创建Runnable接口的子类对象, 并将其作为参数传递给Thread类, 创建线程对象
MyRunnable mr = new MyRunnable();
Thread th = new Thread(mr);
-
开启线程
th.start();
匿名内部类写法:
new Thread(new Runnable() {
@Override
public void run() {
要执行的代码}
}).start();
lambda写法:
new Thread(() -> {
要执行的代码
}).start();
注意:因为Lambda表达式这种方式没有显示的重写run()方法, 所以导致通过start()方法开启线程的时候,找不到对
应的run()方法, 然后当做普通的方法调用了, 偶尔会看见抢资源的情况, 但是几率较小。
thread和runable的区别:如果要开辟多个线程时,需要new多个Thread的自定义子类对象。但runable实现,只需要new一次实现runable接口的子类。通过thread方法来创建多个对象。这就要求第一种方式里,定义的变量应该是静态(static)的,但方式二就不需要,并且因为只new了一次该类,所以synchronized的锁对象可以是this。
方式1开启多个线程:
MyThread mt1 = new MyThread(“窗口1”);
MyThread mt2 = new MyThread(“窗口2”);
方式2开启多个线程:
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, “窗口1”);
Thread t2 = new Thread(mr, “窗口2”);
方式三: 实现Callable接口,需要结合线程池使用
Thread类的构造方法:
- public Thread():分配一个新的线程对象。
- public Thread(String name):分配一个指定名字的新的线程对象。
- public Thread(Runnable target):分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
Thread类的成员方法:
- public String getName():获取当前线程名称。
- public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。
- public void run():此线程要执行的任务在此处定义代码。
- public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static Thread currentThread():返回对当前正在执行的线程对象的引用。
Thread.sleep()使用会出现异常,因为父类run方法没有抛异常,因此只能使用try.catch方法来解决。
try {
Thread.sleep(50); //单位是毫秒, 该方法的特点是: 在哪里睡, 到点后就在哪里醒来. } catch (InterruptedException e) {
e.printStackTrace(); }
2 线程安全:
概述:如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
原因:线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。Java中提供了同步机制(synchronized)来解决
同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){
需要同步操作的代码}
注意:
-
锁对象可以是任意类型,多个线程对象要使用同一把锁。
-
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
-
线程同步(安全), 效率相对较低,线程不同步(不安全), 效率相对较高。
死锁:在实现多线程程序时, 不要出现同步代码块嵌套的行为, 否则可能会出现死锁的情况, 死锁指的是多个线程同时抢多把锁, 因为CPU执行线程的随机性, 从而导致线程"卡死"的情况. 即: 死锁至少需要两个线程, 两把锁, 一个线程先抢锁A, 后抢锁B, 而另一个线程先抢锁B, 后抢锁A,则它们就可能发生死锁现象。
线程的生命周期指的是某一个线程从开始创建直至销毁时, 所经历的全部阶段, 主要分为:新建、就绪、运行(运行的时候可能会发生阻塞)、死亡。
线程安全的类:
-
StringBuffer
线程安全,可变的字符序列
从版本JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步
-
Vector
从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同,Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector
-
Hashtable
该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值
从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同,Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable
3 线程进阶
线程调度方式:
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型,它具有随机性。假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的。
优先级相关方法:
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级, 线程默认优先级是5;线程优先级的范围是:1-10 |
线程控制方法:
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() | 表示加入线程,类似于现实生活中的插队, 当此线程执行完毕后, 其他线程才会接着运行 |
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,<br/ Java虚拟机将退出 |
守护线程,会随着主线程的消失而消失,但由于线程抢占具有随机性和延迟性,所以当主线程消失时,守护线程不会立即消失。
设置主线程的方法:Thread.currentThread().setName("刘备 ");
Lock锁:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法:
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法:
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
4 线程池
概述:就是一个池子(容器), 里边有一些线程对象, 用的时候从里边拿, 用完之后再放回去。
目的: 节约资源, 提高效率。
-
线程池工厂类:Executors
public static ExecutorService newFixedThreadPool(int nThreads) 线程池工厂类创建固定线程数的方法
public static ExecutorService newCachedThreadPool(); 创建线程池, 可根据需求来创建新的线程对象, 针对于生命周期短的线程对象
-
线程池接口:ExecutorService
public Future<?>submit(Runnable task) 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第三种方式
public void shutdown() 关闭线程池
实现多线程的方式三:Callable接口
好处:
- 线程执行后, 可以具有返回值.
- 可以抛出异常
弊端:该方式只能结合线程池一起使用
接口:
- public Future submit(Callable task) 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第三种方式
Callable和Runable的对比:
- Callable是另外一种形式的线程执行目标类,相当于Runnable接口.
- 其中的call方法相当于Runnable中的run方法。
- 不一样的是,call方法具有返回值,run方法没有返回值。
两者的Future返回值对比:
- 将方法返回值封装成了对象,结合线程提供了返回结果的更多信息和功能。
- Future获取具体返回值的方法:Vget()
- 比如run方法的返回值为void,获取到的值即为null。而返回值的类型是随着submit方法的调用而传入的。
5 生产者消费者模式
概述:所谓生产者消费者问题,实际上主要是包含了两类线程:一类是生产者线程用于生产数据,另一类是消费者线程用于消费数据。
- 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
- 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
- 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
object类的等待和唤醒方法:
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程(随机唤醒) |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
6 设计模式:
**概述:**实际开发中, 我们发现好多模块中的部分功能都是相似的, 每次写很麻烦, 于是我们可以把它们抽取出来, 定义成模型,这样按照模型做出来的东西就是符合某种规范的, 或者具有某些特性的, 这些模型就叫: 设计模式。 设计模式并不是一种语法, 而是前辈们总结的一系列的解决问题的思路和代码的解决方案, 设计模式一共分为 23 种.
分类:
- 创建型: 5种, 主要是用来创建对象的.
例如: 单例设计模式、工厂方法设计模型.
- 结构型: 7种, 主要是用来描述类与类之间的关系
例如:适配器设计模式、装饰设计模式
- 行为型: 11种, 主要指的是事物能够做什么.
例如:消费设计模式、 模板方法设计模式.