并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
进程与线程的区别:
注意:
因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 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类的构造方法:
Thread类的成员方法:
Thread.sleep()使用会出现异常,因为父类run方法没有抛异常,因此只能使用try.catch方法来解决。
try {
Thread.sleep(50); //单位是毫秒, 该方法的特点是: 在哪里睡, 到点后就在哪里醒来. } catch (InterruptedException e) {
e.printStackTrace(); }
概述:如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
原因:线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。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
线程调度方式:
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) | 将此线程标记为守护线程,当运行的线程都是守护线程时, |
守护线程,会随着主线程的消失而消失,但由于线程抢占具有随机性和延迟性,所以当主线程消失时,守护线程不会立即消失。
设置主线程的方法:Thread.currentThread().setName("刘备 ");
Lock锁:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法:
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法:
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
概述:就是一个池子(容器), 里边有一些线程对象, 用的时候从里边拿, 用完之后再放回去。
目的: 节约资源, 提高效率。
线程池工厂类:Executors
public static ExecutorService newFixedThreadPool(int nThreads) 线程池工厂类创建固定线程数的方法
public static ExecutorService newCachedThreadPool(); 创建线程池, 可根据需求来创建新的线程对象, 针对于生命周期短的线程对象
线程池接口:ExecutorService
public Future>submit(Runnable task) 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第三种方式
public void shutdown() 关闭线程池
实现多线程的方式三:Callable接口
好处:
弊端:该方式只能结合线程池一起使用
接口:
Callable和Runable的对比:
两者的Future返回值对比:
概述:所谓生产者消费者问题,实际上主要是包含了两类线程:一类是生产者线程用于生产数据,另一类是消费者线程用于消费数据。
object类的等待和唤醒方法:
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程(随机唤醒) |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
**概述:**实际开发中, 我们发现好多模块中的部分功能都是相似的, 每次写很麻烦, 于是我们可以把它们抽取出来, 定义成模型,这样按照模型做出来的东西就是符合某种规范的, 或者具有某些特性的, 这些模型就叫: 设计模式。 设计模式并不是一种语法, 而是前辈们总结的一系列的解决问题的思路和代码的解决方案, 设计模式一共分为 23 种.
分类:
例如: 单例设计模式、工厂方法设计模型.
例如:适配器设计模式、装饰设计模式
例如:消费设计模式、 模板方法设计模式.
----------根据黑马程序员课程的学习总结