Java并发(一)

一、进程

进程是程序的一次执行过程,是系统运行程序的基本单位,程序的运行就是进程从创建、运行到消亡的过程;
在Java中,运行main函数其实就是在运行一个JVM进程,main函数所在的线程就是该进程的线程之一,也是主线程。

二、线程

线程是比进程更小的单位,一个进程在运行中会产生多个线程,同一个进程的线程共享堆和方法区,但不同线程都有自己的程序计数器、虚拟机栈。本地方法栈,不与其他线程共享。

总的来说,线程是进程划分成更小的部分,进程间是相互独立的,但线程不一定,线程很可能会相互影响,线程花销小,但不利于资源的管理和安全问题,进程相反。

三、程序计数器、虚拟机栈和本地方法栈简要说明


程序计数器

程序计数器是为了记下当前线程的执行位置,因为在实际运行过程中,多个线程之间会相互切换运行,为了保证切换回来时能回到正确的执行位置,程序计数器就是负责这个。

虚拟机栈

每个Java方法执行的时,都会分配一个栈帧来存储局部变量表,操作数栈,常量池引用等信息,一个方法从被调用到执行再到结束的过程就是一个栈桢在虚拟机栈中入栈和出栈的过程。

本地方法栈

与虚拟机类似,但执行的是Naive(本地)方法,虚拟机栈和本地方法栈私用就是为了保证局部变量不被别的线程访问

四、堆和方法区简要说明

堆是用来存储对象实例的,方法区是用来存储已加载的类信息、常量、静态变量等信息,二者都是可以线程共享的。

五、并发与并行

  • 并发:在同一时间段内,多个线程都在运行。
  • 并行:在单位时间内,多个线程同时运行。

六、为什么要使用多线程

线程可以比作是轻量级的进程,是系统运行的最小单位,线程间切换和调度的消耗比进程要小,而且多核CPU可让多个线程同时运行,减少了线程上下文切换的消耗。现在的系统并发量都是要百万级甚至千万级,多线程并发机制可以提高高并发系统的性能和能力。

七、使用多线程会有什么问题

使用多线程主要是为了提高并发系统的运行效率和运行速度,但多线程不一定都能达到这一目的,且多线程会带来一些问题如内存泄漏、死锁和线程不安全等等。

八、线程的生命周期和状态

线程生命周期

Java线程的状态变化

九、上下文切换

每个线程都会自己的信息和状态(也就是上下文),例如程序计数器、方法栈等等,每当线程切换的时候,就要把这些信息保存,等到下次切回来的时候,再进行恢复;
切换过程中,保存信息和恢复信息会占用CPU和内存等系统资源,频繁的切换会降低效率。

十、线程死锁

线程死锁:多个线程被阻塞,即它们之间一个或全部都在等待某个资源被释放,但该资源被另外一个线程占有,导致一直被阻塞,程序不能正常终止。

产生死锁的4个必要条件:

  1. 互斥性:一个资源在任意时刻只能被一个线程占有。
  2. 请求和保持条件:线程在请求新的资源遭到阻塞,但不释放当前占有的资源。
  3. 不剥夺条件:线程获取的资源,在未使用完毕时,不能由别的线程占有,只能等线程自己释放。
  4. 循环等待条件:多个线程之间形成首尾相顾的资源等待情况。

如何预防死锁

预防死锁就是要破坏4个必要条件之一,第一个互斥性是不能改变的,故只能破坏后3个。

  • 破坏请求与保持条件
    破坏“请求”:线程要一次性申请整个运行期间要用的资源,全部都申请到了才能运行;
    破坏“保持”:线程申请新的资源时,必须要释放当前资源。
  • 破坏不剥夺条件:线程在申请别的资源时,若申请不到,就释放当前资源。
  • 破坏循环等待条件:对资源进行编号,线程必须按照某种顺序来编号,如升序,若线程申请了编号为5的资源,那他就只能申请编号为6及以上的资源。

如何避免死锁

避免死锁与预防死锁不同,预防死锁是一定要破坏必要条件,而避免死锁则不一定,因为必要条件都满足时也不一定会有死锁发生,避免死锁只需保证无死锁发生;

避免死锁就是在资源分配时,通过某些方法(如银行家算法)对资源进行合理的分配,使得系统进入安全状态,可以正常运行。

十一、sleep()和wait()的异同

  • sleep()没有释放锁,别的线程不能对它造成影响,wait()释放了锁,其他线程可对它进行同步操作。
  • sleep()时Thread类的方法,wait()是Object类的方法。
  • sleep()可以在线程中任意使用,wait()只能在同步锁(synchronized)中使用。
  • sleep()和wait()都可以设置时长来控制调用时间,但wait()也可以通过其他线程用notify()或notifyAll()来唤醒。
  • sleep()主要是用来暂停执行,高优先级的线程给低优先级线程让步等等;wait()用于线程间交互/通信。

十二、为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

start()会启动一个线程并使其进入就绪状态,然后进行相关准备工作,自动运行run(),此时的run()方法是在当前线程运行的,若是直接调用run(),就是在main主线程中运行,与其他某个线程无关,这并不是多线程工作。

十三、synchronized关键字

synchronized是为了解决多个线程之间访问资源的同步性,synchronized可以保证被他修饰的方法或代码块,在任意时刻只能被一个线程访问或执行。

synchronized关键字的使用

  • synchronized修饰static静态方法或者synchronized(类.class)都是给该类上锁。
  • 修饰对象实例方法或者synchronized(this / object)都是给对象实例上锁,所以如果一个线程访问synchronized修饰的static静态方法,另一个线程访问该类的synchronized修饰的对象实例方法,二者是不冲突的。
  • 构造方法不能用synchronized修饰,因为构造方法本就是线程安全的。

synchronized的底层原理

synchronized关键字的底层原理可以从字节码层面来分析。

修饰同步代码块:在同步代码块中synchronized是通过monitorenter和moitorexit两条指令来完成的,monitorenter指向同步代码块开始的位置。monitorexit指向同步代码块结束的位置。
在执行monitorenter时,会尝试获取对应的锁,若锁的计数器为0,即可获取,获取后改为1;若不为0,则会进入线程阻塞,等待另一个线程释放该锁。
在执行monitorexit时,将锁计数器设为0,表明锁被释放。

修饰方法:修饰方法不会用到monitorenter和moitorexit,而是用ACC-SYNCHRONIZED,ACC-SYNCHRONIZED是一种标识,表明这个方法是同步方法。

二者的本质都是对monitor对象监视器的获取。

synchronized 和 ReentrantLock 的区别

  1. 二者都是可重入锁,即当一个线程获得某个锁,而且还没释放时,可以再次进入该锁,而不会发生死锁,这就是可重入锁。
  2. synchronized是JVM层面实现的锁,ReentrantLock是API层面实现的锁,要用lock()和unlock()配合try()/catch()使用。
  3. synchronized是不用手动释放锁,ReentrantLock要手动释放锁。
  4. synchronized不能中断等待锁,ReentrantLock可以用lock.lockInterruptibly()来中断等待锁。
  5. ReentrantLock可是决定用公平锁还是非公平锁,synchronized只能用非公平锁,公平锁就是先等待的线程先获得锁,防止有别的线程在线程转换的间隙突然插进来。
  6. ReentrantLock可实现选择性通知,用Condition()对线程进行分组,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。即可以灵活地选择唤醒哪些锁,synchronized只能唤醒一个或者全部(notify()和notifyAll())。

当你需要实现4、5、6点需求时,就用ReentrantLock。

十四、volatile关键字

并发编程的三大特性

  1. 原子性,一个操作或者多个操作,要么全部操作都执行而且执行过程中不会被中断,要么全都不执行,这就是原子性。(volatile不能保证原子性)
  2. 可见性,一个线程对共享变量进行修改时,另一个线程可以立刻知道,这就是可见性。
  3. 有序性:代码执行过程中,编译器有可能会对字节码指令进行进行重排序和优化,重排序对于单线程来说并不会影响最后的结果,但如果多线程操作就有可能产生错误,volatile就是禁止指令重排序

volatile可以保证可见性和有序性。

从JMM(Java内存模型)来说一下可见性

JMM中变量存储再主内存中,每个线程都有自己的工作内存,工作内存中保存着线程使用过的变量的主内存副本,当线程对变量进行修改时,先对工作内存中对应的副本进行读写,然后再到主内存中的变量进行读写。

这种方式就有可能出现,线程A对某个变量进行修改,但此时只更新了工作内存的副本,主内存中的变量并未更新,线程B却在主内存中获取了该变量,那得到的是旧的数据。

用volatile关键字修饰变量,则该变量每次修改都会跳过工作内存,直接在主内存进行读写,确保了变量的可见性

synchronized和volatile的区别与联系

  1. synchronized用来修饰方法或代码块,volatile只能修饰变量。
  2. volatile是线程同步的轻量级实现,性能会比synchronized好。
  3. volatile可以实现可见性,但不能实现原子性,synchronized二者皆可。
  4. volatile主要用于解决变量在多个线程之间的可见性,synchronized用于解决多个线程在访问资源时的同步性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值