1.什么是进程?什么是线程?
进程:是一个软件(或应用程序)。
线程:是一个进程中的一个执行单元,是进程的最小基本单位。
2.进程和线程之间的关系
进程:可以看做一个车间。
线程:可以看做一个车间中流水线上的员工。
注意:进程与进程之间的内存独立不共享。
例:A车间生产的产品和B车间生产的产品是没有关系的,二者不共享资源。
两个线程之间是什么关系?
在Java中:线程A和线程B,堆内存和方法区是内存共享的。但是栈内存是相互独立的,一个线程一个栈。
例:现在如果有5个线程,就会有5个栈空间,每个栈和每个栈之间互不干扰,是独立执行的。这是多线程的并发。
Java中的多线程机制是为了提高程序的处理效率。
进程与线程的区别总结:
根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
3.线程的生命周期
线程的生命周期,主要有五种状态:
①新建状态(New):当线程对象创建后就进入了新建状态;
②就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态;
注意:处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了start()方法此线程立即就会执行
③运行状态(Running):当CPU调度处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态;
注意:就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态执行,先得处于就绪状态
④阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行;
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
(1)等待阻塞:运行状态中的线程执行wait()方法,此线程进入到等待阻塞状态。
(2)同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程占用),它会进入同步阻塞状态。
(3)其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态。方sleep()状态超时、join()等待线程终止或者I/O处理完毕是线程重新转入就绪状态。
⑤死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
4.在Java中,实现线程有两种方式
第一种方式:一个类直接继承java.lang.Thread,重写run方法
步骤:(1)创建对象,new继承线程的类;
(2)启动线程,调用线程对象的start()方法
public class ThreadTest02 {
public static void main(String[] args) {
MyThread t = new MyThread();
// 启动线程
t.start();
// 这里的代码还是运行在主线程中。
for(int i = 0; i < 1000; i++){
System.out.println("主线程--->" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中(分支栈)。
for(int i = 0; i < 1000; i++){
System.out.println("分支线程--->" + i);
}
}
}
注意:
.t.run()不会启动线程,只是普通的通过对象调用方法。不会分配新的线程。
.t.start()方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间。
第二种方式:一个类实现java.lang.Runnable接口,实现run方法。
步骤:(1)创建对象,new线程类,传入可运行的类或接口。
(2)启动线程,调用start()方法。
// 定义一个可运行的类
public class MyRunnable implements Runnable {
public void run(){
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
public class ThreadTest03 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
for(int i = 0; i < 100; i++){
System.out.println("主线程--->" + i);
}
}
}
// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("分支线程--->" + i);
}
}
}
使用匿名内部类创建(常用):
public class ThreadTest04 {
public static void main(String[] args) {
// 创建线程对象,采用匿名内部类方式。
Thread t = new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("t线程---> " + i);
}
}
});
// 启动线程
t.start();
for(int i = 0; i < 100; i++){
System.out.println("main线程---> " + i);
}
}
}
注意:第二种方式,实现接口比较常用,因为一个类实现了接口,还可以继承其它类,相对于第一种方式,第二种更灵活。
5.获取当前线程对象、名字、修改线程对象名字的几个方法
方法名 | 作用 |
static Thread currentThread() | 获取当前线程的对象 |
String getName() | 获取当前线程的名字 |
void setName(String name) | 修改当前线程的名字 |
在没有设置当前线程名字的时候,线程会有默认的名字,如下:
.Thread-0
.Thread-1
.Thread-2
....
class MyThread2 extends Thread {
public void run(){
for(int i = 0; i < 100; i++){
// currentThread就是当前线程对象。当前线程是谁呢?
// 当t1线程执行run方法,那么这个当前线程就是t1
// 当t2线程执行run方法,那么这个当前线程就是t2
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "-->" + i);
}
}
}
6.线程的sleep方法
方法名 | 作用 |
static void sleep(long millis) | 让当前线程休眠milis秒 |
(1)该方法是静态方法
(2)参数是毫秒
(3)作用:让当前线程进入休眠,进入阻塞状态
public class ThreadTest06 {
public static void main(String[] args) {
//每打印一个数字睡1s
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
// 睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
7.Java中与线程调度有关的方法
7.1实例方法:
方法名 | 作用 |
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
void join() | 讲一个线程合并到当前线程中,当前线程受阻,加入的线程执行到结束 |
说明:线程的默认优先级是5,最低优先级是1,最高优先级是10。
优先级高的线程获取CPU的时间片可能会多一点(不是绝对的)。
class MyThread1 extends Thread {
public void doSome(){
MyThread2 t = new MyThread2();
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
}
}
class MyThread2 extends Thread{
}
7.2常量:
常量名 | 备注 |
static int MAX_PRIORITY | 最高优先级为10 |
static int MIN_PRIORITY | 最低优先级为1 |
static int NORM_PRIORITY | 默认优先级为5 |
7.3静态方法:
方法名 | 作用 |
static void yield() | 让当前线程暂停,回到就绪状态,让给其它线程 |
yield()方法不是阻塞方法。是把当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从运行状态回到就绪状态。
public class ThreadTest12 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable6());
t.setName("t");
t.start();
for(int i = 1; i <= 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyRunnable6 implements Runnable {
@Override
public void run() {
for(int i = 1; i <= 10000; i++) {
//每100个让位一次。
if(i % 100 == 0){
Thread.yield(); // 当前线程暂停一下,让给主线程。
}
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
8.数据在多线程并发环境下会存在的安全问题
需要满足三个条件:
①多线程并发;
②有共享数据;
③共享数据有修改的可能
如何解决?
让线程排队执行(不能并发)。这种机制被称为线程同步机制。
9.线程安全
9.1synchronized 线程同步
语法格式:
synchronized(){
// 线程同步代码块。
}
重点:synchronized后面的小括号中传的是一个数据对象,这个数据对象必须是多线程共享的数据
。才能达到多线程排队。
9.2在方法上也可以使用synchronized
synchronized出现在实例方法上,锁的一定是this,不能是其他对象。这种方式不灵活,有局限性
10.死锁
在开发中是禁止出现死锁的,但在面试的时候会问。应用场景:并发场景,多个线程。线程之间存在共享数据的时候互不相让。
死锁是一种状态,当两个线程互相持有对方所需要的资源的时候,这两个线程又都不主动释放资源,就会导致死锁。代码无法正常执行,这两个线程就会僵持住。
class DeadLock implements Runnable {
private boolean flag;//标记
private Object obj1;//对象1
private Object obj2;//对象2
public DeadLock(boolean flag, Object obj1, Object obj2) {
this.flag = flag;
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run() {
if (flag) {//如果flag = true 让线程1执行这个if语句里面的代码
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + "拿到了obj1资源");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行了");
synchronized (obj2) {//想用obj2这个资源
System.out.println(Thread.currentThread().getName() + "拿到obj2资源");
}
}
}
if (!flag) {//如果flag=false 线程2 执行这个if语句里面的代码
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + "拿到了obj2资源");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行了");
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + "拿到obj1资源");
}
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
DeadLock deadLock1 = new DeadLock(true, obj1, obj2);
new Thread(deadLock1, "线程1").start();
DeadLock deadLock2 = new DeadLock(false, obj1, obj2);
new Thread(deadLock2, "线程1").start();
}
}
11.守护线程
11.1Java中线程分为两大类:
一类是:用户线程(非守护线程)
一类是:守护线程(后台线程)
其中最有代表性的是:垃圾回收线程(守护线程)
11.2守护线程的特点
一般情况下守护线程是一个死循环,所有的用户线程只要结束,守护线程就会自动结束。
其中,主线程main方法是一个非守护线程(用户线程)。
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("守护线程:" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.isDaemon());//false 非守护线程
// thread.setDaemon(true);//设置为守护线程
Thread thread1 = new Thread(new MyThread8());
//System.out.println(thread1.isDaemon());
thread1.setDaemon(true);
thread1.start();
for (int i = 0; i < 200; i++) {
System.out.println("主线程:" + i);
}
}
}
12.关于Object类下的三个方法
方法名 | 作用 |
void wait() | 让当前对象的线程等待(会释放之前占有的锁) |
void notify() | 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁) |
void notifyAll() | 唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁) |
方法的详解:
①wait和notify方法不是线程对象的方法地方,在Java中任何一个Java对象都有这两个方法,因为这两个方法时Object类中自带的。
注意:wait方法和notify方法不是通过线程对象调用的
Object o = new Object();
o.wait();
以上代码的作用:让正在对象o上活动的线程进入等待状态,直到被唤醒为止。
o.wait()方法的调用会让当前线程(正在对象o上活动的线程)进入等待状态。
②notify()方法
Object o = new Object();
o.notify();
以上代码的作用:唤醒正在对象o上等待的线程。
③notifyAll()方法
Object o = new Object();
o.notifyAll();
以上代码的作用:唤醒对象o上处于等待的所有线程。
总结:
(1)wait和notify方法不是线程对象的方法,是Java对象都有的方法。
(2)wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
(3)wait方法的作用:让正在对象上活动的线程进入等待状态,并且释放掉线程之前占有的对象的锁。
(4)notify方法的作用:让正在对象上等待的线程唤醒,只是通知,不会释放对象上之前占有的锁。
13.生产者消费者模式(wait()和notify())
13.1什么是“生产者和消费者模式”?
.生产线程负责生产,消费线程负责消费。
.生产线程和消费线程要达到均衡。
.这是一种特殊的业务需求,在这种情况下需要使用wait方法和notify方法。
class Goods {
private String name;//商品的名字
private double price;//商品的价格
private boolean isProduct;//商品是否需要生产
//true需要生产 false 不需要生产
public Goods(String name, double price, boolean isProduct) {
this.name = name;
this.price = price;
this.isProduct = isProduct;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public boolean isProduct() {
return isProduct;
}
public void setProduct(boolean product) {
isProduct = product;
}
}
class Customer implements Runnable {//消费者线程
private Goods goods;
public Customer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
//消费者一直消费 生产者一直生产
while (true) {
synchronized (goods) {
//需要一直消费 判断商品是否有无
// //true需要生产 false 不需要生产
if (!goods.isProduct()) {
//不需要生产直接购买的
System.out.println("消费者购买:" + goods.getName() + ",价格为:"+ goods.getPrice());
//购买完以后,商品没了,商品标记为true
goods.setProduct(true);
//唤醒生产者 让其生产
goods.notify();
} else {
//需要生产 消费者线程等待
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Productor implements Runnable {//生产者线程
private Goods goods;
public Productor(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
int count = 0;
while (true) {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods) {
// true需要生产 false 不需要生产
if (goods.isProduct()) {//true
//造车,如果是奇数的话,造玛莎拉蒂 如果是偶数的话,造劳斯莱斯
if (count % 2 == 0) {
//偶数
goods.setName("劳斯莱斯");
goods.setPrice(8.9);
} else {
//奇数
goods.setName("玛莎拉蒂");
goods.setPrice(7.6);
}
//生产者生产者完以后,将isProcut标记为
goods.setProduct(false);
System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());
count++;
//生产者生产完以后 唤醒消费者。
goods.notify();
} else{
//不需要生产的
try {
goods.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Goods goods = new Goods("红旗", 9.9, false);
Customer customer = new Customer(goods);
Productor productor = new Productor(goods);
new Thread(customer).start();
new Thread(productor).start();
/**
* 消费者购买:红旗,价格为:9.9
* 生产者生产了:劳斯莱斯,价格为:8.9
* 消费者购买:劳斯莱斯,价格为:8.9
* 生产者生产了:玛莎拉蒂,价格为:7.6
* 消费者购买:玛莎拉蒂,价格为:7.6
* 生产者生产了:劳斯莱斯,价格为:8.9
* 消费者购买:劳斯莱斯,价格为:8.9
* 生产者生产了:玛莎拉蒂,价格为:7.6
* 消费者购买:玛莎拉蒂,价格为:7.6
* 生产者生产了:劳斯莱斯,价格为:8.9
*/
}
}
14.线程池
线程池是一个容纳了多个线程的容器,其中的线程可以反复的使用。省去了频繁创建线程的对象的操作,无需反复创建线程而消耗更多的资源。
在Java中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每个线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:
①通过ThreadPoolExecutor手动创建线程池。
②通过Executors执行器自动创建线程池。
以上两类创建线程池的方式又有7种具体的实现方法,如下:
(1)Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
(2)Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
(3)Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
(4)Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
(5)Executors.newWorkStealingPool:创建一个抢占执行的线程池(任务执行顺序不确定)(jdk1.8添加)。
(6)创建一个可以缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。
(7)ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置7个参数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
下面介绍ThreadPoolExecutor的7个参数:
①corePoolSize:核心线程数。线程池中保持最小活动数的线程数量。当线程池的线程数量小于核心线程数时,一个新的任务请求被提交上来时,不管其它线程是否处于空闲状态,都会新建一个线程来处理这个请求。
②MaximumPoolSize:最大线程数。线程池运行的最大线程数量。
③keepAliveTime:存活时间。空闲线程等待工作的时间(以纳秒为单位)。如果当前线程池中的线程数,超出的部分线程如果空闲的时长大于存活时长,那么它们将会被终止运行。当线程池不被频繁使用的时候,这提供了一种减少资源消耗的方法。
④TimeUnit:存活时间的单位。默认是纳秒,可以更改。
⑤BlockingQueue:阻塞队列。任何实现了BlockingQueue接口的实现类都可以用来传输和保存提交的任务,阻塞队列的使用和线程池大小相关。
如果运行的线程少于核心线程数,Executor总是倾向于添加一个新线程而不是排队;
如果核心线程数或更多线程正在运行(不超过最大线程数),Executor总是倾向于排队请求,而不是添加一个新线程;
如果没有达到最大线程数并且队列未满,将创建新的线程执行任务,如果线程数大于最大线程数,任务将会被拒绝。
无界队列:是一个没有预定义容量的队列,使用无界队列LinkedBlockingQueue将导致新任务一直在等待,当核心线程数的线程处于工作状态时。所以,不会有超过核心线程数的线程被创建,也就是说最大线程数是不起作用的。、
有界队列:如ArrayBlockingQueue,它能在有限的最大线程数内防止资源耗尽,但是它也更难调整和控制。
拒绝任务:在调用execute提交任务时,在Executory已经关闭或者有界队列的最大线程数和队列满的情况下会被拒绝。
⑥ThreadFactory:
⑦ RejectedExecutionHandle:
第7中方式的案例:
import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo5 {
public static void main(String[] args) {
test();
}
public static void test () {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
//执行任务
for (int i = 0; i < 21; i++) {
int index = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + "被执行, 线程为:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//threadPoolExecutor.shutdown();
}
}
}
以上代码的分析:
LinkedBlockingQueue:最大线程数不起作用,无界队列。
5个任务->核心5个线程 任务不会放到队列中排队,队列中是0个任务;
6个任务->核心5个线程 队列中1个任务,只要线程执行完5个以后,对于最后一个任务会随机从5个线程中抽取一个然后执行最后的线程。
12个任务->核心5个线程 队列中有7个......
ArrayBlockingQueue<>(10):有界队列,只能放10个任务,最大线程数是10
5个任务->核心5个线程 队列中没有任务;
8个任务->核心5个线程 队列中有3个任务;
12个任务->核心5个线程 队列中有7个任务,有界队列中可以放10个任务;
17个任务->5个核心已经用上了,队列中也已经存够10个,多出来两个任务,再开2个
20个任务->5个核心用上,10个队列用上,再开5个新线程。