java并发编程整理一

为什么使用多线程

多任务的处理,提高处理速度,减少相应时间,更好的体验

随着cpu的核心数量越来越多,提供了充足的硬件基础,使用多线程重复发挥机器的计算能力,合理利用资源

上下文切换

cpu通过给每个线程分配cpu时间片(时间片:一般几十毫秒,是cpu分配给每个线程的时间),实现多线程执行(无论单核与多核)。cpu通过不断切换线程,已达到多个线程执行的效果。

每次切换到另一个线程时,会保存当前线程的任务状态,以便下次切换回来。所以,线程任务从保存到再次切换回来的过程就是一次上下文切换。cpu给线程分配了多少时间片,就代表这个线程使用了多少cpu资源。

并发的缺陷

等待共享资源时,性能降低

需要处理线程的额外CPU消耗(上下文切换等)

糟糕的并发设计导致不必要的复杂度

可能会产生 死锁、饿死、竞争、活锁(各自任务可运行,整体任务无法完成)问题

饿死(当饥饿到一定程度的进程所赋予的任务即使完成也不再具有实际意义时称该进程被饿死)
考虑一台打印机分配的例子,当有多个进程需要打印文件时,系统按照短文件优先的策略排序,该策略具有平均等待时间短的优点,似乎非常合理,但当短文件打印任务源源不断时,长文件的打印任务将被无限期地推迟,导致饥饿以至饿死。

如果要大量编写复杂多线程代码,可以尝试使用Erlang

接下来,我们从基础开始,深入原理,系统的学习java 并发

什么是线程:

操作系统运算调度的最小单元,轻量级的进程,一个线程包含独立的计数器、栈、局部变量等属性,并且线程间可以共享堆内存。所谓的多线程程序,也就是处理器通过上下文切换,给不同线程分配时间片,让人感觉线程是同时执行的。

为什么要多线程(并发编程):

充分利用多核处理器
一个进程下会有多个线程,一个线程的运行会占用一个处理器核心。现在多核cpu已经司空见惯,如果我们编程还是单线程,那么多核cpu中只会有一个核心被使用,其他核心被闲置。为了重复利用cpu多核资源,提高运算能力,我们使用多线程,同一时间可以在cpu的多核上运行多个线程。

更快的响应体验

比如我们请求一个页面,如果是单线程,会挨个获取这个页面上的视频、图片、文字等等资源;如果是多线程,会并发回去这个页面上的资源,以缩短页面加载的时间。更快的速度,更好的用户体验。

线程的6种状态

NEW: 线程构建完毕,但是还没有开始运行(还未执行thread.start());

RUNNABLE: 线程的就绪与线程的运行都称为RUNNABLE状态;

BLOCKED: 阻塞状态。线程等待获取其他线程通过Synchronized持有的锁时,呈现BLOCKED状态

WAIT: 等待状态;Lock锁阻塞;object.wait();Thread.join();LockSupport.part()呈现的状态

TIME_WAITING: 有超时限制的方法通常执行后呈现TIME_WAITING状态.相当于在WAIT状态基础上增加了超市限制。如,Thread.Sleep(long n);;Thread.join(long n)

TERMINATE: 线程结束的状态

Demon线程

后台的守护线程,通过thread.setDemon(true)设置。
当进程中没有非Demon线程执行时,该进程立刻结束,也就是说Demon线程也会立刻结束。
==注意:无法保证Demon线程中的finally块一定执行。==

线程的中断

线程的中断可以理解为线程中一个标识符的变更,一个线程向改变了另一个线程的“中断标识符”。
Thread.isInterrupted(): true:该线程已经被中断;false没有被中断
Thread.interrupted(): true:该线程已经被中断;false没有被中断;该方法的执行总是会把中断标识置为true;

一些方法可以响应中断抛出InterruptedException,需要注意异常抛出后Thread.isInterrupted为false;

volatile

….. volatile Type variable;
特性:可见性
会在编译后的字节码中多出来一个lock指令,该指令会使线程对这个变量的读取都从内从中读,对这个变量的写都会立刻刷入主存中。由此保证了变量的可见性。

volatile原理:在汇编语言上可以看到,volatile变量会比普通变量多出一个“Lock”指令,通过Lock指令实现volatile。
对于一个多核处理器来说,lock指令做两件事情:
1.使当前处理器中的缓存数据写入到主内存中,并且lock指令通过锁总线或者锁缓存保证对数据读写的原子性(处理器保证的原子性);
2.缓存数据写回主存的操作会导致其他处理器的缓存失效。(通过总线通信保证缓存一致性)

synchronized

synchronized保证线程之间的互斥性、可见性、原子性。

synchronized块通过使用 monitorenter\monitorexit指令实现对代码的互斥访问;
synchronized方法通过依靠方法修饰符上的ACC_SYNCHRONIZED指令实现互斥。
本质上都是通过获取一个对象的monitor,这个获取是排他的。也就是同一时刻只有一个线程可以获取被synchronized保护对象的monitor.

volatile与synchronized的区别

1、锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。

  互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。

  可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享 变量可能是修改前的值或不一致的值,这将引发许多严重问题。(竞态条件)

2、在Java中,为了保证多线程读写数据时保证数据的一致性,可以采用两种方式:

  同步:如用synchronized关键字,或者使用锁对象

  使用volatile关键字:用一句话概括volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。

3、volatile详解

  首先我们要先意识到有这样的现象,编译器为了加快程序运行的速度,对一些变量的写操作会先在寄存器或者是CPU缓存上进行,最后才写入内存.
而在这个过程中,变量的新值对其他线程是不可见的.

  当对volatile标记的变量进行修改时,会将其他缓存中存储的修改前的变量清除,然后重新读取。这里从哪读取我并不明确,一般来说应该是先在进行修改的缓存A中修改为新值,然后通知其他缓存清除掉此变量,当其他缓存B中的线程读取此变量时,会向总线发送消息,这时存储新值的缓存A获取到消息,将新值穿给B。最后将新值写入内存。当变量需要更新时都是此步骤,volatile的作用是被其修饰的变量,每次更新时,都会刷新上述步骤。

4、volatile与synchronized

1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.

  《Java编程思想》上说,定义long或double变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作)原子性
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.

==5、volaitle变量的写操作不依赖于当前volaitle变量(volatile i++并不能保证线程安全);==

6、使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。

猜你喜欢

转载自blog.csdn.net/ymybxx/article/details/80050872
今日推荐