关于进程、线程的基础掌握

一、进程和线程

1、进程

1.1 概念

进程就是正在运行的程序,也就是代表了程序所占用的内存区域

1.2 特点
  • 独立性:进程是系统中独立存在的实体,可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有进程本身的允许下,一个用户进程不能直接访问其他进程的地址空间。
  • 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。再进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的
  • 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响

2、线程

2.1 概念

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位,一个进程可以开启多个线程。
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。
简而言之,一个进程运行后至少一个线程,一个进程里可以包含了多个线程
如果一个进程只有一个线程,这种进程被称为单线程
在这里插入图片描述

2.2 进程和线程的关系

在这里插入图片描述
从上图中可以看出一个操作系统中可以有多个进程,一个进程中可以有多个线程,每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。(记清这个关系,非常重要!)

2.3 特性
2.3.1 随机性

 在同一时刻,只能有一个程序在执行,我们感觉的这些程序在同时进行,实际上是因为cpu在高校的切换着,时间是纳秒级的

2.3.2 多线程的特性
  1. 原子性

一个操作或者多个操作,要么全部执行并且执行过程不会被任何因素打断,要么就都不执行

  1. 可见性

当多个线程访问同一个变量的时候,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值

若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程2没看到这就是可见性问题。

  1. 有序性

程序执行的顺序按照代码的先后顺序执行

一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

2.3.3 线程的状态/线程的生命周期

在这里插入图片描述
线程生命周期,总共有五种状态

  1. 新建状态(new):当线程对象创建后就进入了新建状态,如 Thread t=new Thread();
  2. 就绪状态(Runable):当调用线程对象的start()方法(t.start(); ),线程进入了就绪状态。处于就绪状态的线程,只能说明此线程做好了准备,随时等待cpu调度运行,并不是说执行了t.start()此线程就会执行;
  3. 运行状态(Running):当cpu开始调度处于就绪状态的线程时,此下称才是得以真正执行,即进入到了运行状态。注:就绪状态时进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先要处于就绪状态中
  4. 阻塞状态(Blocked):处于运行状态的线程由于某些原因,暂时放弃对cpu的使用全,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被cpu调用以进入到运行状态
  5. 根据阻塞产生的原因不用,阻塞状态又可以分为三种:
  • 等待阻塞:运行状态中的线程执行wait()方法,使本线程将进入到阻塞状态
  • 同步阻塞:线程在获取去synchronized同步锁失败(因为锁被其他线程所占用),它会进入同步阻塞状态
  • 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态,当sllep()状态超时,join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
  1. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束了生命周期

3 线程创建

3.1 继承Thread
3.1.1 概述

  本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。这种方式实现多线程很简单,通过自己的类extend Thread,并重写run()方法,就可以启动新线程并执行自己定义的run()方法。
但是不建议使用此方法定义线程,因为采用继承Thread的方式定义线程后,你不能在继承其他的类了,导致线程的可扩展性大大的降低,因为Java是单继承
在这里插入图片描述

3.1.2 流程
  1. 定义Thread的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体
  2. 创建Thread子类的实例,也就是创建了线程对象
  3. 启动线程,即调用线程的start()方法
3.1.3 Thread类的属性和方法
  • 嵌套摘要
类型属性说明
static classThread.State线程状态。
static interfaceThread.UncaughtExceptionHandler当 Thread 因未捕获的异常而突然终止时,调用处理程序的接口。
  • 属性(字段摘要)Property
类型属性功能
static intMAX_PRIORITY线程可以具有的最高优先级。
static intMIN_PRIORITY线程可以具有的最低优先级。
static intNORM_PRIORITY分配给线程的默认优先级。
  • 常用方法 Method
返回值方法名属性
StringgetName()返回该线程的名称。
static ThreadcurrentThread()返回对当前正在执行的线程对象的引用。多用于实现Runnable创建线程时,调用Thread类方法的时侯
voidsetName(String name)改变线程名称,使之与参数 name 相同。
static voidsleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
voidstart()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
Thread.StategetState()返回该线程的状态。
voidinterrupt()中断线程。
voidjoin()等待该线程终止。
voidjoin(long millis)等待该线程终止的时间最长为 millis 毫秒。
static voidyield()暂停当前正在执行的线程对象,并执行其他线程
voidnotify()/notifyAll()唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

wait()和sleep都能让线程从运行状态进入到阻塞状态,但是wait()会失去锁,而sleep不会失去锁

3.1.4 实例代码
public class ThreadTest {
    public static void main(String[] args) {
        Thread1 t = new Thread1();
        t.start();
    }
}
class Thread1 extends Thread{


    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(getName()+i);
        }
    }
}
3.2 实现Runnable接口
3.2.1 概念

如果自己的写的类已经extends另外一个类,就无法多继承,此时,可以实现一个Runnable接口,实现这个接口的时候要重写run()方法

3.2.2 Runnable接口方法

void run()

 使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。
3.2.3 代码示例
public class ThreadTest {
    public static void main(String[] args) {
        Thread1 t = new Thread1();
        Thread thread = new Thread(t);
        thread.start();
    }
}
class Thread1 implements Runnable{


    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
3.3 通过实现Callable接口创建线程
3.3.1 概述

在这里插入图片描述

3.3.2 流程
  1. 实现Callable接口,重写call方法,方法体写入我们的代码
  2. 子类的实例交给FutureTask类来包装,该FutureTask对象封装了该Callback对象的call()方法的返回值
  3. 使用FutureTask对象对位Thread对象的额target创建并启动新线程
  4. 调用FutureTask对象的get()方法来获取子线程执行结束后的返回值
public class testCallable implements Callable {
    public static void main(String[] args) {
        testCallable tt = new testCallable();
        FutureTask futureTask = new FutureTask<>(tt);
        new Thread(futureTask).start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object call() throws Exception {
        int i=0;
        for (;i<10;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
        return i;
    }
}
3.4 通过线程池创建线程
public class testCallable {
    private static int POOL_NUM=10;

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i=0;i<POOL_NUM;i++){
           RunnableThread thread = new RunnableThread();
           executorService.execute(thread);
        }
    }
}

class RunnableThread implements Runnable{
    private int THREAD_NUM=1;
    @Override
    public void run() {
        for (int i=0;i<THREAD_NUM;i++){
            System.out.println("线程"+Thread.currentThread()+i);
        }

    }
}
	//执行结果
	线程Thread[pool-1-thread-1,5,main]0
	线程Thread[pool-1-thread-2,5,main]0
	线程Thread[pool-1-thread-2,5,main]0
	线程Thread[pool-1-thread-2,5,main]0
	线程Thread[pool-1-thread-1,5,main]0
	线程Thread[pool-1-thread-1,5,main]0
	线程Thread[pool-1-thread-2,5,main]0
	线程Thread[pool-1-thread-3,5,main]0
	线程Thread[pool-1-thread-4,5,main]0
	线程Thread[pool-1-thread-5,5,main]0
3.5 线程创建方式的比较
方式优点缺点
Thread编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。线程类已经继承了Thread类,所以不能再继承其他父类
Runnable线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
CallableRunnable规定(重写)的方法是,run()Callable规定(重写)的方法是call()Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。Call方法可以抛出异常,run方法不可以。运行Callable任务可以拿到一个Future对象,表示异步计算的结果。存取其他项慢
Pool线程池可以创建固定大小,这样无需反复创建线程对象,线程是比较耗费资源的资源同时线程不会一直无界的创建下去,拖慢系统编程繁琐,难以理解

4 多线程并发

4.1 为什么会有并发
  1. Java内存模型规定了所有的变量都存储在主内存中,每条线程都有自己的工作内存
  2. 线程的内存中保存了该线程中用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存
  3. 线程访问一个变量,首先将变量从内存中拷贝到中作内存,对变量的写操作,不会马上同步到主内存
  4. 不同的线程之间也无法直接访问对方内存中的变量,线程变量的传递均需要自己的工作内存和主内存之间进行数据同步进行
4.1.1 并发的的条件和后果

条件:在多线程程序中,这些线程有着共享的数据,这些线程对共享数据进项操作
后果:用商品作为例子,高并发的情况下,容易出现超卖和重卖的情况

4.2 Java内存模型(JMM)

Java内存模型(JMM)作用于工作内存(本地内存)和主存之间数据同步过程,它规定了如何做数据同步以及什么时候做数据同步
在这里插入图片描述

4.3 并发的三要素

原子性:在一个操作中,CPU不可以在中途暂停然后再调度,即不被中断操作,要么执行完成,要么执行失败。
可见性:多个线程访问同一个变量是,一个线程改变了这个变量的值,其他变量能看到修改的值。
有序性:程序执行的顺序按照代码的先后顺序执行

4.4 阻塞锁和非阻塞锁

阻塞,本质上是等待,让当前线程等待。

1.阻塞锁
就是一直循环获取锁,直到别的线程是否锁,然后该线程获取到锁为止。

2.非阻塞锁
只获取一次,立即返回。

4.5 锁的公平和非公平

公平就是排队,是按顺序来的;不公平就是靠竞争,随机的。

1.公平锁
线程排队队列

2.非公平锁 //jdk显式锁,默认使用非公平锁
竞争

4.6 乐观锁和悲观锁

乐观锁:表示多线程每次对共享资源进行操作的时候都会出现并发问题,因此在当前线程获取到锁的时候会阻塞其他的线程获取锁
悲观锁:所有线程访问共享资源的时候不会出现冲突,既然没有冲突,就不用阻塞其他的线程,如果出现冲突就是使用

4.6 并发的解决方案
  1. 当只有一个线程写,其它线程都是读的时候,可以用volatile修饰变量
  2. 当多个线程写,那么一般情况下并发不严重的话可以用Synchronized,Synchronized并不是一开始就是重量级锁,在并发不严重的时候,比如只有一个线程访问的时候,是偏向锁;当多个线程访问,但不是同时访问,这时候锁升级为轻量级锁;当多个线程同时访问,这时候升级为重量级锁。所以在并发不是很严重的情况下,使用Synchronized是可以的。不过Synchronized有局限性,比如不能设置锁超时,不能通过代码释放锁。
  3. ReentranLock 可以通过代码释放锁,可以设置锁超时。
  4. 高并发下,Synchronized、ReentranLock 效率低,因为同一时刻只有一个线程能进入同步代码块,如果同时有很多线程访问,那么其它线程就都在等待锁。这个时候可以使用并发包下的数据结构,例如ConcurrentHashMap,LinkBlockingQueue,以及原子性的数据结构如:AtomicInteger。
4.7 并发解决的参考链接

点击查看,更详细的链接

4.8 同步锁synchronized
4.8.1 概念

把有可能出现问题的代码包起来,一次只让一个线程执行。通过sychroized关键字实现同步。
当多个对象操作共享数据时,可以使用同步锁解决线程安全问题

4.8.2 格式
sychronized(对象){
需要同步的代码
}
4.8.2 特点
  1. 前提1,同步需要两个或者两个以上的线程
  2. 前提2,多线程键必须使用同一个锁
  3. 同步的缺点是会将地程序的执行效率,为了保证线程安全,必须牺牲性能
  4. 可以修饰方法,修饰后称为同步方法,使用锁对象是this
  5. 可以修饰代码块,修饰后称为同步代码块,锁对象可以是任意的
4.8.3 CAS(compare and swap)
1 概述

什么时候使用CAS(compare/比较 and swap/交换)
当使用乐观锁策略的时候,出现冲突的时候,无锁操作使用CAS(compare and swap)又叫做比较交换来鉴别是否出现冲突,出现冲突就重试当前操作,直到没有冲突为止(有点像条件判断,满足条件才会结束,对于没有冲突的情况直接过滤,有冲突的情况会提供解决方案,能提高程序的效率)

2 CAS操作过程

  AS比较交换的过程可以通俗的理解为CAS(V,O,N),包含三个值分别为:V-内存地址存放的实际值O-预期的值(旧值)N-更新的值。当V和O相同时,也就是旧值和内存中实际值相同,表明该值没有被其他线程更改过,即该旧值O就是目前来说最新的值了,自然而然可以将新值复制给V。反之,V和O不相同,表明已经被其他线程改过了,则旧值O不是最新版本的值了,所以不能把新值N赋值V,返回V即可。当多线程使用CAS操作一个变量时,只有一个变量成功,并成功更新,其余会失败。失败的线程会重新尝试,当然也可以重新挂起线程
  元老级的Synchronized(未优化前)最主要的问题是:在存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS并不是武断的间线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫做非阻塞同步。这是两者主要的区别。

3 参考链接

Sychronized的详情链接点击

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值