关于线程

138 阅读16分钟

并发与并行

并发:多个任务在同一时间段执行,示意可看作是,cpu在执行时,交替执行多个任务

并行:多个任务在同一时刻进行,现代cpu都是多核多线程,即同时执行多个任务

进程与线程

进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。


一般电脑的内容就存储在ROM永久存储单元。一旦程序执行,就会进入进程,即在RAM临时存储单元之中,


线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

比如当程序要执行某一功能用到cpu,就会建立一条通道到cpu,即线程

例如8线程,cpu就会在8个线程中高速切换,速度快

我们简单总结下:

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。

线程调度

  • 分时调度

​ 每个线程轮流使用cpu,平均分配每个线程的cpu使用时间

  • 抢占式调度

    优先让优先级高的程序使用cpu,如果优先级相等就随机分配,Java就是使用抢占式调度

主线程

执行主方法的线程,就是主线程

JVM在执行main方法时,会将main方法入栈,入栈后,JVM会开辟一条通向cpu的路径,也就是主线程

多线程的创建方法

法一:

1.创建一个Thread的子类

2.重写Thread的run方法(多线程执行内容)

3.创建对象

4.调用start方法开启多线程,会执行run方法

注意:不能多次调用线程,否则程序报错,线程结束后不能重新启动

image-20200721174420810

新增线程会开辟新的栈空间

法二:

实现Runnable接口

1.创建实现Runnable接口的对象

2.重写run方法,即实现线程任务

3.创建实现接口的对象

4.创建Thread类对象,构造方法传参传递Runnable接口的实现类对象

使用Runnable接口的好处:

1.让线程更加灵活,Runnable只是一个接口而非父类,因此创建线程还可以有继承

  1. Runnable使得程序可扩展性强。普通创建线程的方法基础Thread类,线程任务和线程开启都是一个类实现,耦合性高。Runnable接口创建线程,线程任务在接口的实现类中,开启线程由创建新的Thread实现,降低程序耦合性

常用方法

  1. sleep方法,让线程以指定的毫秒数进入TIMED_WAITING状态

线程安全问题

线程安全问题的前提是线程访问了共享数据

多个线程在访问共享数据时,就会出现安全问题。要防止安全问题的产生,就必须使得一个线程在访问共享数据时,无论是否有cpu控制权,其他线程都不能访问该数据

同步

为了保证每个代码块都能进行原子操作,Java引入了线程同步机制

同步代码块

synchronized (锁对象){
    可能出现线程安全的代码(访问了共享数据的代码)
}

tips:

1.代码块中的锁对象可以使用任意对象

2.必须保证多个线程的锁对象统一

3.锁对象作用:让代码块锁住,只让一个线程在代码块中执行

同步方法

同步方法也会将局部的代码块锁住,只让一个线程执行

同步方法的锁对象就是实现类的对象,即this(Runnable接口实现对象)

tips:

对于静态同步方法,锁对象不能再是实现类对象:因为静态方法和对象无关

而是本类的Class属性--->class文件对象

同步技术原理

同步技术使用了锁对象,这个对象叫同步锁,也叫对象锁,对象监视器

举一个小栗子:

t0,t1,t2是三个线程,t0首先抢到了cpu使用权,在执行run方法时,遇到synchronized代码块,然后发现锁对象还存在,于是获取锁对象,进入到同步块执行。

此时,t1又抢到了cpu使用权,在遇到synchronize代码块时,发现锁对象不在,t1就会进入阻塞状态,必须等到t0线程归还锁对象后才能继续。t0线程只有在执行完synchronized代码块后,才会归还锁对象。

优点:程序绝对安全

缺点:不断地获取锁,归还锁,效率低

Lock锁

Lock锁在jdk1.5后出现,是比synchronize更加好用的线程安全方法

方法:

  1. 创建ReentrantLock对象(对象实现了Lock接口)
  2. 在可能出现线程不安全的代码前调用lock方法
  3. 在结束后调用unlock方法

最后的unlock方法最好是放在finally代码块中,使得无论如何锁都会被释放

线程状态

image-20200722152016050

注意Blocked和Timed_WAITING的区别

blocked:具有cpu执行资格,等待cpu空闲时执行,一般都是锁导致的

TIMED_WAITING:不具有cpu执行资格,cpu空闲也不会执行,一般是认为或者系统产生

TIMED_WAITING

例子:等待唤醒的案例:生产者与消费者

consumers: 告诉生产者自己的需求后,进入等待生产者生产

producers:在得知需求后开始生产,生产完毕唤醒消费者

始终保证消费者和生产者只有一个在执行

等待唤醒机制

1.线程通信

多个线程在处理同一资源时,动作可能不同

在多个线程并发处理时,cpu是随机选择线程的,而我们希望多个线程之间能够协调有规律完成同一个任务,此时就需要用到等待唤醒机制

三大方法:

  1. wait:线程不再活动,不会占用cpu资源,也不会去竞争锁
  2. notify:选择对象监视器中的一个对象释放,重新进入到调度队列(ready queue)之中
  3. 释放全部线程

总结: 从wait出来后,如果获得了锁,就能从waiting变为runnable,否则,会变成blocked状态

注意:wait和notify必须由同一个锁对象调用,并且notify和wait方法必须在synchronize代码块或同步方法中调用

原因:使用唤醒机制,一般是用来避免Lost wake up problem,必须在同步代码块或同步方法中调用,并且保证锁对象相同,wait方法调用后会把锁对象归还,而notify方法只是唤醒,不会直接将锁给对象,而是让对象具有竞争cpu的能力

线程池

当并发的线程过多时,并且每个线程都是执行某个任务就会销毁,此时,频繁地创建线程会导致效率过于低下。线程池的出现使得线程可以复用,在结束一个线程后,并不被销毁。

线程池,就是容纳多个线程的容器

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

使用线程池中线程对象的步骤:

  1. 创建线程池对象。

  2. 创建Runnable接口子类对象。(task)

  3. 提交Runnable接口子类对象。(take task)

  4. 关闭线程池(一般不做)。

    并发与并行

并发:多个任务在同一时间段执行,示意可看作是,cpu在执行时,交替执行多个任务

并行:多个任务在同一时刻进行,现代cpu都是多核多线程,即同时执行多个任务

进程与线程

进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。


一般电脑的内容就存储在ROM永久存储单元。一旦程序执行,就会进入进程,即在RAM临时存储单元之中,


线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

比如当程序要执行某一功能用到cpu,就会建立一条通道到cpu,即线程

例如8线程,cpu就会在8个线程中高速切换,速度快

我们简单总结下:

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。

线程调度

  • 分时调度

​ 每个线程轮流使用cpu,平均分配每个线程的cpu使用时间

  • 抢占式调度

    优先让优先级高的程序使用cpu,如果优先级相等就随机分配,Java就是使用抢占式调度

主线程

执行主方法的线程,就是主线程

JVM在执行main方法时,会将main方法入栈,入栈后,JVM会开辟一条通向cpu的路径,也就是主线程

多线程的创建方法

法一:

1.创建一个Thread的子类

2.重写Thread的run方法(多线程执行内容)

3.创建对象

4.调用start方法开启多线程,会执行run方法

注意:不能多次调用线程,否则程序报错,线程结束后不能重新启动

image-20200721174420810

新增线程会开辟新的栈空间

法二:

实现Runnable接口

1.创建实现Runnable接口的对象

2.重写run方法,即实现线程任务

3.创建实现接口的对象

4.创建Thread类对象,构造方法传参传递Runnable接口的实现类对象

使用Runnable接口的好处:

1.让线程更加灵活,Runnable只是一个接口而非父类,因此创建线程还可以有继承

  1. Runnable使得程序可扩展性强。普通创建线程的方法基础Thread类,线程任务和线程开启都是一个类实现,耦合性高。Runnable接口创建线程,线程任务在接口的实现类中,开启线程由创建新的Thread实现,降低程序耦合性

常用方法

  1. sleep方法,让线程以指定的毫秒数进入TIMED_WAITING状态

线程安全问题

线程安全问题的前提是线程访问了共享数据

多个线程在访问共享数据时,就会出现安全问题。要防止安全问题的产生,就必须使得一个线程在访问共享数据时,无论是否有cpu控制权,其他线程都不能访问该数据

同步

为了保证每个代码块都能进行原子操作,Java引入了线程同步机制

同步代码块

synchronized (锁对象){
    可能出现线程安全的代码(访问了共享数据的代码)
}

tips:

1.代码块中的锁对象可以使用任意对象

2.必须保证多个线程的锁对象统一

3.锁对象作用:让代码块锁住,只让一个线程在代码块中执行

同步方法

同步方法也会将局部的代码块锁住,只让一个线程执行

同步方法的锁对象就是实现类的对象,即this(Runnable接口实现对象)

tips:

对于静态同步方法,锁对象不能再是实现类对象:因为静态方法和对象无关

而是本类的Class属性--->class文件对象

同步技术原理

同步技术使用了锁对象,这个对象叫同步锁,也叫对象锁,对象监视器

举一个小栗子:

t0,t1,t2是三个线程,t0首先抢到了cpu使用权,在执行run方法时,遇到synchronized代码块,然后发现锁对象还存在,于是获取锁对象,进入到同步块执行。

此时,t1又抢到了cpu使用权,在遇到synchronize代码块时,发现锁对象不在,t1就会进入阻塞状态,必须等到t0线程归还锁对象后才能继续。t0线程只有在执行完synchronized代码块后,才会归还锁对象。

优点:程序绝对安全

缺点:不断地获取锁,归还锁,效率低

Lock锁

Lock锁在jdk1.5后出现,是比synchronize更加好用的线程安全方法

方法:

  1. 创建ReentrantLock对象(对象实现了Lock接口)
  2. 在可能出现线程不安全的代码前调用lock方法
  3. 在结束后调用unlock方法

最后的unlock方法最好是放在finally代码块中,使得无论如何锁都会被释放

线程状态

image-20200722152016050

注意Blocked和Timed_WAITING的区别

blocked:具有cpu执行资格,等待cpu空闲时执行,一般都是锁导致的

TIMED_WAITING:不具有cpu执行资格,cpu空闲也不会执行,一般是认为或者系统产生

TIMED_WAITING

例子:等待唤醒的案例:生产者与消费者

consumers: 告诉生产者自己的需求后,进入等待生产者生产

producers:在得知需求后开始生产,生产完毕唤醒消费者

始终保证消费者和生产者只有一个在执行

等待唤醒机制

1.线程通信

多个线程在处理同一资源时,动作可能不同

在多个线程并发处理时,cpu是随机选择线程的,而我们希望多个线程之间能够协调有规律完成同一个任务,此时就需要用到等待唤醒机制

三大方法:

  1. wait:线程不再活动,不会占用cpu资源,也不会去竞争锁
  2. notify:选择对象监视器中的一个对象释放,重新进入到调度队列(ready queue)之中
  3. 释放全部线程

总结: 从wait出来后,如果获得了锁,就能从waiting变为runnable,否则,会变成blocked状态

注意:wait和notify必须由同一个锁对象调用,并且notify和wait方法必须在synchronize代码块或同步方法中调用

原因:使用唤醒机制,一般是用来避免Lost wake up problem,必须在同步代码块或同步方法中调用,并且保证锁对象相同,wait方法调用后会把锁对象归还,而notify方法只是唤醒,不会直接将锁给对象,而是让对象具有竞争cpu的能力

线程池

当并发的线程过多时,并且每个线程都是执行某个任务就会销毁,此时,频繁地创建线程会导致效率过于低下。线程池的出现使得线程可以复用,在结束一个线程后,并不被销毁。

线程池,就是容纳多个线程的容器

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

使用线程池中线程对象的步骤:

  1. 创建线程池对象。

  2. 创建Runnable接口子类对象。(task)

  3. 提交Runnable接口子类对象。(take task)

  4. 关闭线程池(一般不做)。