《一遍文章让你快速了解JAVA---并发编程基础》

现代操作系统在运行一个程序时,会为其创建一个进程。例如,启动一个Java程序,操作系统就会创建一个Java进程。线程是现代操作系统调度的最小单元,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计算器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。今天主要以两个方面让大家更快的了解并发编程!

一、基本概念与方法

二、线程安全问题与解决

(一)、线程与进程

进程是CPU分配资源的最小单位,由一个或多个线程组成。

线程是CPU进行调度的最小单位,被称为轻量级线程。

一个程序至少一个进程,一个进程至少一个线程

(二)、Java中线程的三种创建方式

(1)继承Thread类,并重写run()方法

(2)实现Runnable接口,并重写run()方法

(3)实现Callable接口,并重写call()方法;此种方法有返回值,且需要使用FutureTask类进行封装

实现接口与继承Thread类的比较:

Java中只能单继承,但是可以实现多个接口;使用接口的方法更适合扩展

继承整个Thread类的方法开销过大

若想在线程执行体中(即run方法体中)访问当前线程,继承方式可以直接通过this;而接口方法要通过Thread.currrentThread()

此外实现Runnable接口创建的线程可以处理同一资源,从而实现资源的共享

(三)、线程的状态

(1)新建状态:创建后未启动

(2)就绪状态:调用start()方法后进入该状态,与其他就绪状态线程一起竞争CPU,等待CPU的调度。

(3)运行状态:就绪状态的线程获得CPU时间片,真正的执行run()方法。线程只能从就绪状态进入运行状态

(4)阻塞状态:线程由于如下所示的各种原因进入阻塞,线程挂起

该线程调用Thread.sleep()方法

等待阻塞,线程中的共享变量调用了wait()方法

I/O流方式,如read()方法,receive()方法等待数据

同步阻塞,线程因无法获得目标资源的锁而被挂起

(四)、sleep()方法和wait()方法

sleep()是Thread类中的静态方法,调用Thread.sleep(time)后线程休眠time毫秒,休眠过程中线程不会释放拥有的对象锁。如果该线程睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,该线程会在调用sleep()方法的地方抛出InterruptedException。

wait()是Object类中的方法,当线程调用一个共享变量的wait()方法是,该线程会被挂起并且释放该对象锁,进入等待此对象的等待锁定池,直到其他线程调用了该共享对象的notify()或者notifyAll()方法。其中,notify()是在等待锁定池中随机唤醒一个线程,notifyAll()是唤醒所有因该对象的wait()方法而挂起的线程。

注意:调用共享变量的wait()、notify()、notifyAll()方法,需要先获得共享变量的对象锁。被唤醒的线程不会立即执行,需要和其他线程一起竞争对象锁(由调用notify()方法的线程所释放的对象锁)。

(五)、join()方法和yield()方法

join()方法,Thread类的成员方法,插队方法,线程A的执行体中调用 B.join(),B代表线程B,则线程A会阻塞,让B线程插队。参数可以传入时间(毫秒),表示允许插队运行的时间长度。

yield()方法,Thread类的静态方法,礼让方法,线程A调用Thread.yield()方法后会让出CPU使用权,进入就绪状态,与其他处于就绪状态的线程一起竞争CPU。(实际上,调用yield()方法之后,线程调度器会从线程就绪队列中获取一个线程优先级最高的线程,而该线程的优先级会变为1)

(六)、线程中断

线程中断是线程间的一种协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。

interrupt()方法,中断线程,将线程的中断标志设置为true。当线程因调用wait()、join()、sleep()等方法进入阻塞时,其他线程调用该线程的interrupt()方法,该线程会抛出InterruptedException并返回。如果调用线程的interrupt()方法后未抛出InterruptedException,则应通过interrupted()方法判断当前线程是否被中断来返回线程(如在执行体中使用该方法作为线程执行前提条件)

(七)、守护线程与用户线程
守护线程

用户线程

守护线程是服务于用户线程的,可以通过调用setDaemon(true)方法将用户线程设置为守护线程

两者可以通过JVM是否等待线程结束来区分,JVM只会等待用户线程结束;守护线程不会影响JVM的退出,不管其是否运行结束都会随着JVM的结束而结束。即用户线程全部结束时,程序终止,并杀死所有守护线程。如main函数就是一个用户线程,而垃圾回收线程就是一个守护线程。

(八)、ThreadLocal的使用

ThreadLocal由JDK包提供,它提供了线程本地变量,即每个访问ThreadLocal变量的线程都会有一个该变量的随机副本。线程对该变量进行操作时,实际上是对自己的本地内存里的变量进行操作,从而避免了多线程共享一个变量时的安全问题。如在封装MyBatisUtil工具包时,其中就用到了将SqlSession的实例对象存储在ThreadLocal的实例对象中,每次通过

get获取,使用完后关闭SqlSession实例对象,并set(null)将ThreadLocal清空;tl是ThreadLocal的实例对象。

二。线程安全问题与解决

(一)、Java中的线程安全问题

当多个线程对共享资源进行访问时,只有当至少有一个线程修改共享资源时才会存在线程安全问题。典型的如计数器类实现中的丢失修改问题。

(二)、共享变量的内存可见性问题

Java中所有的变量存放在主存中,而线程使用变量时会把主内存里面的变量复制到自己的工作内存中,线程读写变量时操作的是自己工作变量中的内存,然后将自己工作内存中的变量刷新到主内存中。因此,当线程A和线程B同时处理一个共享变量时,会存在内存不可见的问题。

(三)、锁的概念

(1)乐观锁与悲观锁:是从数据库概念中引入的词。悲观锁指认为数据很容易被其他线程修改,因此会在数据被处理前对数据进行加锁,使得整个处理过程中数据处于锁定状态。乐观锁则是认为数据在一般情况下不会造成冲突,因此在访问数据前不会加排它锁,只有在数据提交更新时,才会正式的对数据冲突与否进行检测。

(2)独占锁与共享锁:根据锁只能被单个线程持有还是能被多个线程持有,分为独占锁(排它锁)和共享锁。独占锁是一种悲观锁,每次访问资源前都先加上互斥锁,只允许同一时间由一个线程读取数据。而共享锁是一种乐观锁,允许多个线程同时进行读操作。

(3)公平锁与非公平锁:根据线程获取锁的抢占机制,可以分为公平锁与非公平锁。公平锁表示线程获取锁的顺序是按照线程请求锁的时间早晚来决定的,即早到早得。而非公平锁则不一定先到先得。ReentrantLock提供的锁默认是非公平锁。一般来说,在没有公平性需求的前提下,尽量使用非公平锁,因为公平锁会带来性能开销。

(4)可重入锁:一个线程再次获取它自己已经获得的锁时,则称为可重入锁。可重入的原理是在锁内部维护一个线程表示,线程表示来指示该锁目前被哪个线程占有,然后关联一个计数器来表示该锁是否被线程占用,0为未被占用,1为已占用,此后每次重入则计数器+1.

(5)自旋锁:自旋锁是指线程在获取锁失败时不会马上挂起,而是在不放弃CPU使用权的情况下,多次尝试获取该锁(默认10次)。一般而言,当线程获取锁失败后,会切换到内核状态而被挂起;当该线程获取锁后又需要将其切换到内核状态而唤醒该线程,而用户状态切换到内核状态的开销是比较大的,即自旋锁是使用CPU时间换取线程阻塞与调度的开销。

(四、)synchronized的使用

synchronized是Java提供的一种原子性内置锁。是一种排它锁,同时也是非公平的。synchronized可以解决共享变量的内存可见性问题。

进入synchronized块的语义是,把块内使用的变量从线程的工作内存中清除,这样线程就会直接从主内存中去获取块内需要使用的变量。

退出synchronized块的语义是,将synchronized块内对共享变量的修改刷新到主内存中。

(五)、volatile的使用

使用锁的方式解决共享变量内存可见性的问题太过繁琐,开销太大,因此Java提供了一种弱形式的同步,即volatile关键字。

类成员变量或者类静态成员变量被volatile修饰后主要有两个特性

(1)解决不同线程对该变量进行的操作时的可见性问题。因为线程在操作volatile修饰的变量时,不会把值缓存到寄存器或者其他地方,而是直接把值刷新会主内;当其他线程获取该变量时,会从主内存中重新获取最新值,而不是使用当前线程工作内存中的值。

(2)禁止指令重排,一定程度上能保证有序性。具体情况是,写volatile变量时,写之前的操作不会被编译器重排序到volatile写之后。读volatile变量时,读之后的操作不会被编译器重排序到volatile读之前。

(六)、Java中的CAS操作

Java中使用锁来处理并发会产生线程上下文切换和重新调度的开销。而非阻塞的volatile关键字只能保证共享变量的可见性,不能解决读-改-写等原子性问题。因此JDK提供了非阻塞原子性操作,即CAS(Compare

and Swap)操作,它通过硬件保证了比较-更新操作的原子性。

CAS操作有个经典的ABA问题,大概意思是

线程1获取变量X的值(A),然后修改变量X的值为B,这种情况下即使使用CAS操作成,程序也不一定运行正确。因为可能存在线程2在1获取变量X后,使用CAS操作修改了X的值为B,然后又使用CAS操作修改X的值为A,这样线程1修改变量X的值是,已经是此A非彼A了。

ABA问题大概流程:1.CASget(X-A) --->2.CASset(X-B)--->2.CASset(X-A)--->1.CASset(X-B)。

ABA问题的产生是因为变量的状态值产生了环形转换,即变量值从A到B,然后再从B到A。如果规定变量的值只能朝着一个方向转换,则不会出现该问题。因此JDK中的AtomicStampedReference类给每个变量的状态值都配置了一个时间戳死,以避免ABA问题发生。

原文地址:https://blog.51cto.com/14257001/2407820

时间: 06-12

《一遍文章让你快速了解JAVA---并发编程基础》的相关文章

4.java并发编程艺术-java并发编程基础

java从诞生开始就明智的选择了内置对多线程的支持,这使得java语言相比同一时期的其他语言具有明显的优势.线程作为操作系统调度的最小单元,多个线程能够同时执行,这将显著提升程序的性能,在多核环境中表现的更加明显.但是,过多的创建线程和对线程的不当管理也容易造成问题.本章将着重介绍java并发编程的基础知识,从启动一个线程到线程间不同的通信方式,最后通过简单的线程池示例以及应用(简单的Web服务器)来串联本章所介绍的内容. 1.线程简介 1.1 什么是线程 现代操作系统中在运行一个程序时,会为其

java并发编程基础概念

1.什么是进程和线程 1.1 进程是程序运行资源分配的最小单位 进程是操作系统进行资源分配的最小单位,其中资源包括:CPU.内存空间.磁盘IO等,同一进程中的多个线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 进程是程序在计算机上的一次执行活动.当你运行一个程序,你就启动了一个进程.显然,程序是死的.静态的,进程是活的.动态的.进程可以分为系统进程和用户进程.凡是用于完成操作

Java 并发编程基础导航

一.中断模型:http://ifeve.com/java-interrupt-mechanism/ 1. 协作制度, 你要我中断,只是给我一个信号,我啥时候中断,是我的事情. 比如 T1调用T2.interrupt(),    T2 如果没有写  if (Thread.intterrupted()) { return or do something}或者 if ( Thread.isintterupted()) { return or do something}  T2鸟都不鸟T1 而且 如果T

Java并发编程-基础概念全解

1.基础 1.1.什么是进程和线程 进程和线程都是操作系统所运行的程序运行的基本单元.进程可以说是是线程的集合. 进程:从系统资源讲,进程都有自己独立的地址空间,一个进程的崩溃不会影响另一个进程的执行. 线程:进程中的一个执行路径,一个进程中可以同时有多个线程在执行,当其中一个线程对公共资源做了修改,其他线程是可以看到的. 1.2.什么是并行和并发 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时. 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正

java并发编程基础——线程的创建

一.基础概念 1.进程和线程 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小.(线程是cpu调度的最小单位) 2.并发性和并行性 并行:是指同一时刻,有多条指令在多个处理器上同时执行 并发:是指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行.通常多个进程可以在当个处理器上并发执行. 3.多进程和多线

Java并发编程基础

线程作为操作系统调度的最小单元,多个线程能够同时执行,这将显著提升程序性能,在多核环境中表现得更加明显.但是,过多地创建线程和对线程的不当管理也容易造成问. 什么是线程 现代操作系统在运行一个程序时,会为其创建一个进程.例如,启动一个Java程序,操作系统就会创建一个Java进程.现代操作系统调度的最小单元是线程,也叫轻量级进程(LightWeight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器.堆栈和局部变量等属性,并且能够访问共享的内存变量.处理器在这些线程上

java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析

ReentrantLock是一个较为常用的锁对象.在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用. 概念 一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相同的一些基本行为和语义,但功能更强大. 名词解释: 互斥 表示同一时刻,多个线程中,只能有一个线程能获得该锁.但是多个线程都可以调用lock方法,只有一个会成功,其他的线程会被阻塞,直到该锁被释放 可重入 模仿synchronized 的语义:如果线程进入由线程已经拥有的监控器保护的 synchro

Java并发编程有多难?这几个核心技术你掌握了吗?

本文主要内容索引 1.Java线程 2.线程模型 3.Java线程池 4.Future(各种Future) 5.Fork/Join框架 6.volatile 7.CAS(原子操作) 8.AQS(并发同步框架) 9.synchronized(同步锁) 10.并发队列(阻塞队列) 本文仅分析java并发编程中的若干核心问题,对于上面没有提到但是又和java并发编程有密切关系的技术将会不断添加进来完善文章,本文将长期更新,不断迭代.本文试图从一个更高的视觉来总结Java语言中的并发编程内容,希望阅读完

Java并发编程(一)

Java并发编程(一) 之前看<Thinking In Java>时,并发讲解的挺多的,自己算是初步了解了并发.但是其讲解的不深入,自己感觉其讲解的不够好.后来自己想再学一学并发,买了<Java并发编程实战>,看了一下讲的好基础.好多的理论,而且自我感觉讲的逻辑性不强.最后,买了本<Java并发编程的艺术>看,这本书挺好的,逻辑性非常强. 1. 概述 本篇文章主要内容来自<Java并发编程的艺术>,其讲解的比较深入,自己也有许多不懂的地方,然后自己主要把它讲