Java-关于并发与多线程笔记

引子

Java的多线程(指的是这个程序(一个进程)运行时产生了不止一个线程)可以让不同的程序块一起运行,如此一来就可以让程序运行的更为顺畅,同时也可以达到多任务处理的目的,提高运行效率。

在一般情况下,程序的某些部分特定的事件或资源联系在一起,同时又不想因为它而暂停其他部分的运行,在这种情况下,就可以考虑创建一个线程,令它与资源关联到一起独立于主程序运行,通过使用多线程,可以使编程人员很方便地开发出能同时处理多个任务的功能强大的应用程序。

进程与线程

进程:

是计算机中的程序在某个数据集合上的一次独立的运行活动,是系统进行资源分配和调度的基本单位。

进程特征:
1.每个进程都有自己独立的一块内存空间和一组系统资源;
2.创建并执行一个进程的系统开销比较大;
3.进程是程序的一次基本执行过程,是系统运行程序的基本单位;

线程:

被称为轻量级进程,.是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的 资源。
虽然系统是把资源分给进程,但是CPU很特殊,是被分配到线程的,所以线程是CPU分配的基本单位。

线程特征:
1.线程自己不拥有系统资源,只拥有少量在运行中必不可少的资源,但它可以拥有与同属一个进程之间的其他线程共享进程所拥有的全部资源
2.一个线程可以创建和撤销另一个线程
3.同一进程中的多个线程可以并发执行

二者关系:

一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域。

二者差别:

1.同样作为基本的执行单位,线程是划分得比进程更小的单位。
2.每个进程都有一段专用的内存区域,而线程共享内存单元(包括代码和数据)。通过共享的内存单元来实现数据交换,实时通信与必要的同步操作。

并行与并发:

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。

并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,

在多线程编程实践中,线程的个数往往多于CPU的个数,所以一般都称多线程并发编程而不是多线程并行编程。

创建线程

在Java中,线程也是一种对象;
只有implement了Runnable接口,或者extend了Thread类的对象才能够成为线程;

线程的创建对象有两种:

1.extend继承Thread类

创建:

class 类名 extend Thread{
         属性
         方法
         修饰符 run (){             //覆盖thread里的run方法
                以线程处理的程序;
          }
}

2.implement实现Runnable接口

由于JAVA类只能单继承,但是可以实现多个接口,所以当一个类继承了另外一个类,而又想实现多线程的时候,就可以使用Runnable接口来创建线程;

定义:

class 类名 implement Runnabe
{
         属性
         方法
         修饰符  run(){
              以线程处理的程序;
          }
}

注意:无论是继承Thread类还是实现Runnable接口,运行的结果都是一样的,但是,实现Runnable接口还需要调用Thread类的start()方法才能够启动多线程,因为在Runnable接口中只有一个run方法,也就是说在Runnable接口中没有start方法,所以一个实现了Runnable接口的类必须依靠Thread中的start方法来启动多线程。利用一个Runnable接口的实例化对象作为参数去实例化Thread对象。如:

public Thread(Runnable target)

使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。

不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。

总的来说,在实际开发中,建议尽可能多的器使用Runnable接口去实现多线程机制。

不过以上两种方式都没办法拿到任务的返回结果,但是实现Callable接口可以,不过这个之后再写。

线程的基本状态与转换

线程的五个基本状态:

1.创建状态:创建一个线程对象后
2.就绪状态:调用start方法后,进入就绪状态,等待JVM里线程调度器的调度
3.运行状态:当线程得到资源后进入运行状态
4,阻塞状态:处于运行状态的线程因为某种特殊情况让出系统资源,进入阻塞状态
5.终止状态:线程执行完后或者调用stop方法时

线程基本状态转换:

1.调用start方法,线程进入就绪状态
2.调用run方法,线程进入运行状态
3.(线程睡眠)调用sleep方法,进入睡眠状态,但是线程不会释放资源,定点直接收回
4.调用wait方法,线程直接释放资源,由运行状态进入等待状态,进入等待池
5.调用notify或notifuAll方法,会唤醒在等待池中的线程,使该线程从等待状态转化为就绪状态
6.(线程合并)调用Thread类的join方法,可以合并某个线程,让当前调用进程进入阻塞状态,被调用进程进入运行状态
7.(线程让出)当线程调用Thread类的yield静态方法时,线程让出CPU资源,从运行状态进入阻塞状态

在这里插入图片描述

线程的同步

在编程过程中,为了防止多线程访问共享资源产生冲突,在Java语言中提供了线程同步机制

线程安全问题

当一个类以及很好地同步以保护它的数据时,这个类就被称为安全的;
如果线程不提供数据访问保护,那么得到的数据就可能时无效的(出现多个线程先后更改数据的情况),
例如:
在这里插入图片描述
如图,多个线程同时操作共享变量1时,会出现线程1更新共享变量1的值,但是其他线程获取到的是共享变量没有被更新之前的值。就会导致数据不准确问题。

共享内存不可见问题

在这里插入图片描述

同步代码块

是使用synchronized关键字定义的代码块,但是在同步的时候需要设置一个对象锁,一般都会给当前对象this上锁。

synchronized(this){

               ......//代码块
               
}

这样就解决了不同步的问题,但是同时可以发现,程序的执行速度变慢了。

这样就可以解决共享变量内存可见性问题。进入synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。会造成上下文切换的开销,独占锁,降低并发性。

如果在一个方法上使用了synchronized定义,那么这个方法就会被称为同步方法。

所谓同步就是指一个线程等待另外一个线程操作完再操作的情况。

共享可见变量

利用Volatile修饰变量,该关键字可以确保对一个变量的更新对其他线程马上可见。

当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。

当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。

volatile的内存语义和synchronized有相似之处,具体来说就是,当线程写入了volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取volatile变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。

线程的交互

线程间的交互指的是线程之间需要一些协调通信来完成共同一项任务,例如生产者-消费者问题。

wait(),notify(),notifyAll()

这三个方法由于需要控制对对象的控制权(monitor),所以属于Object类而不是属于线程。

wait():会把持有该对象线程的对象控制权交出去,然后处于等待状态。

notify():会通知某个正在等待这个对象的控制权的线程可以继续运行。

nofifyAll():会通知所有等待这个对象控制权的线程继续运行,如果有多个正在等待该对象控制权时,具体唤醒哪个线程,就由操作系统进行调度。

注意:无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权。

线程的调度

计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权。

线程的调度依靠线程栈模型,线程栈存储的是某时刻内存中线程调度的栈信息,当前调度的方法总是在栈顶,线程栈额内容是随着线程运行而动态变化的。

线程优先级

Java提供了一个线程调度器来监控程序启动后进去就绪状态的所有线程。

线程调度器通过线程的优先级来决定调度哪些线程执行。

一般来说,Java的线程调度器采用时间片轮转算法使多个线程轮转获得CPU的时间片。

然而根据实际情况,每个线程的重要程序也不相同,有时候我们想让一些线程优先执行,那么我们可以将他的优先级调高一下,这样它们获得的时间片会多一些。

多个线程处于就绪状态时,若这些线程的优先级相同,则线程调度器会按时间片轮转方式或独占方式来分配线程的执行时间。

Java中线程优先级用1~10来表示,分为三个级别:

低优先级:1~4,其中类变量Thread.MIN_PRORITY最低,数值为1;

默认优先级:如果一个线程没有指定优先级,默认优先级为5,由类变量Thread.NORM_PRORITY表示;

高优先级:6~10,类变量Thread.MAX_PRORITY最高,数值为10。

注意:
线程优先级拥有继承特性:也就是如果线程A启动线程B,那么线程A和B的优先级是一样的;

具有相同优先级的多个线程,若它们都为高优先级Thread.MAX_PRORITY,则每个线程都是独占式的,也就是这些线程将被顺序执行;

若它们优先级不是高优先级,则这些线程将被同时执行,可以说是无序执行,不是高优先级的线程就一定先比低优先级执行完,而是高优先级线程先执行的概率比低优先级的线程高,即线程会优先级的大小顺序执行,但是不一定是优先级较大的先执行完。

线程的休眠,让步,联合

1.利用sleep方法进行线程休眠

注意:由于使用sleep方法会抛出一个InterruptedException,所以需要在程序中使用try catch捕捉异常

2.线程让步是指暂停当前正在执行的线程对象,转而执行其他线程对象

使用yield方法进行线程让步,该方法只是令当前线程从运行状态转化为可运行状态

3.线程联合:Thread的方法join让两个线程联合起来,当线程A内部的执行到调用了线程B的join方法,就会开始执行线程B

总结

总的来说,线程可以分为以下状态:
在这里插入图片描述

Thread类的方法

String getName()  返回该线程的名称。

void setName(String name)  改变线程名称,使之与参数 name 相同。

int getPriority()   返回线程的优先级。

void setPriority(int newPriority)   更改线程的优先级。

boolean isDaemon()   测试该线程是否为守护线程。

void setDaemon(boolean on)  将该线程标记为守护线程或用户线程。

 static void sleep(long millis)       沉睡一定量的时间

void interrupt()  中断线程。

static void yield()  暂停当前正在执行的线程对象,并执行其他线程。

void join()  等待该线程终止。

void run()   执行

void start()    就绪

void notify()      唤醒等待的线程

void wait()      让其陷入等待
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值