java 多线程并发
来源:互联网 发布:js触发select下拉事件 编辑:程序博客网 时间:2024/06/10 21:17
一、多线程
首先搞清楚容易混淆的两个概念:
进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。
线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源
二、Java内存模型(Java Memory Model)
1、volatile关键词:
Java 语言提供了一种稍弱的同步机制,即 volatile 变量.用来确保将变量的更新操作通知到其他线程,保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新. 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的.
A线程是Worker,一直跑循环,B线程调用setDone(true),A线程即停止任务
public class Worker{ private volatile boolean done; public void setDone(boolean done){ this.done = done; } public void work(){ while(!done){ //执行任务; } } }
public class Counter { public volatile static int count = 0; public static void inc() { //这里延迟1毫秒,使得结果明显 try { Thread.sleep(1); } catch (InterruptedException e) { } count++; } public static void main(String[] args) { //同时启动1000个线程,去进行i++计算,看看实际结果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); } }).start(); } //这里每次运行的值都有可能不同,可能不为1000 System.out.println("运行结果:Counter.count=" + Counter.count); } }
该例子的运行结果不是100
原因如下:
先看图:
图片注解:
上图是JAVA内存模型
目标是定义程序中各个变量的访问规则。(包括实例字段、静态字段和构成数组的元素,不包括局部变量和方法参数)
1. 所有的变量都存储在主内存中(虚拟机内存的一部分)。
2. 每条线程都由自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
3. 线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。
规则:
1不允许read和load、store和write操作之一单独出现。
2不允许一个线程丢弃最近的assign操作,变量在工作内存中改变了之后必须把该变化同步回主内存中。
3不允许一个线程没有发生过任何assign操作把数据从线程的工作内存同步回主内存中。
4一个新的变量只能在主内存中诞生。
这4点就造成了该例子的运行结果不是100,还有一些关于lock的规则,本文没有讲到,讲太多不好消化.
read and load 从主存复制变量到当前工作内存
use and assign 执行代码,改变共享变量值
store and write 用工作内存数据刷新主存相关内容
其中use and assign可以多次出现
但是这一些操作并不是原子性,也就是在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的
2 final关键词
方案3可以解决上述问题,不过先请大家看一下2
final关键词声明的域的值只能被初始化一次,一般在构造方法中初始化。。(在多线程开发中,final域通常用来实现不可变对象)
当对象中的共享变量的值不可能发生变化时,在多线程中也就不需要同步机制来进行处理,故在多线程开发中应尽可能使用不可变对象。
另外,在代码执行时,final域的值可以被保存在寄存器中,而不用从主存中频繁重新读取
3 java基本类型的原子操作
原子操作非阻塞同步:
基于冲突检测的乐观并发策略:先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(一般是不断的尝试,直到成功为止)。
先举个栗子:
线程不安全的代码:
package com.etrip.web; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class CountServlets */ public class CountServlets extends HttpServlet { private static final long serialVersionUID = 1L; //记录访问此Servlet的数量 private static long count=0; /** * @see HttpServlet#HttpServlet() */ public CountServlets() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response) */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter pw=response.getWriter(); count++; pw.print("当前的请求次数为为:"+count); pw.flush(); pw.close(); } }
线程安全的代码:
package com.etrip.web; import java.io.IOException; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicLong; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.concurrent.atomic.*; /** * * * */ public class CountExtServlet extends HttpServlet { private static final long serialVersionUID = 1L; //记录访问此Servlet的数量 private final AtomicLong count=new AtomicLong(0); /** * @see HttpServlet#HttpServlet() */ public CountExtServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response) */ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter pw=response.getWriter(); count.incrementAndGet(); pw.print("当前的请求次数为为:"+count.get()); pw.flush(); pw.close(); } }
我想此时,大家应该对原子变量也有了解了吧。顺便说一句:
为了提高性能,AtomicLong等类在实现同步时,没有用synchronized关键字,而是直接使用了最低层(本地c语言实现代码)来完成的,因而你是看不到用synchronized关键字的。
AtomicInteger,AtomicBoolean,AtomicLong,AtomicReference等。这是由硬件提供原子操作指令实现的。在非激烈竞争的情况下,开销更小,速度更快。
三、Java提供的线程同步方式
1、synchronized关键字
关键字主要解决多线程共享数据同步问题
synchronized关键字属于互斥同步:同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。互斥方式:临界区、互斥量和信号量
方法或代码块的互斥性来完成实际上的一个原子操作。(方法或代码块在被一个线程调用时,其他线程处于等待状态)
实例方法:当前对象实例所关联的监视器对象。
/取钱
public synchronized voidsubMoney(int money){ if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; System.out.println(+System.currentTimeMillis()+"取出:"+money); }
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
synchronized (this) { if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; } System.out.println(+System.currentTimeMillis()+"取出:"+money);
2:ThreadLocal
可重入代码:可以在代码执行的任何时刻中断它,转而去执行另一段代码,而在控制权返回后,原来的程序不会出现任何错误。特征:不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数传入,不调用非可重入的方法等。如果一个方法,它的返回结果是可以预测的,只要出入了相同的数据,就能返回相同的结果,那它就满足可重入性的要求
ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLoacl的一般用法,创建一个ThreadLocal的匿名子类并覆盖initalValue(),把ThreadLoacl的使用封装在另一个类中
publicclass ThreadLocalIdGenerator{ private static finalThreadLocal<IdGenerator> idGenerator = new ThreadLocal<IdGenerator>(){ protected IdGeneratorinitalValue(){ return newIdGenerator();//IdGenerator 是个初始int value =0,然后getNext(){ return value++} } }; public static int getNext(){ return idGenerator.get().getNext(); } }
如果大家感兴趣可以继续看一下
1:生产者消费者模式:
使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种,后面的帖子会讲两种并发类型下的应用)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。其实当初这个模式,主要就是用来处理并发问题的
2:java.util.concurrent包为多线程提供了高层的API
Lock接口,表示一个锁方法:
ReadWriteLock接口,表示两个锁,读取的共享锁和写入的排他锁。
ReentrantLock类和ReentrantReadWriteLock,分别为上面两个接口的实现类。
3:线程池 :解决多线程创建销毁的消耗问题
如果有时间我一定一一总结给大家,请多提批评意见,共同进步,欢迎拍砖!
- java处理多线程并发
- JAVA多线程并发
- java处理多线程并发
- Java 多线程并发解决方案
- java 多线程 并发控制
- java高级工程师--------多线程并发
- Java 多线程 并发编程
- java 并发编程 多线程
- 【Java基础】并发 - 多线程
- Java并发多线程
- Java 多线程并发
- java多线程与并发
- Java多线程并发
- Java多线程并发管理
- Java多线程并发管理
- java 多线程并发执行
- JAVA多线程并发
- java 多线程应用(并发)
- Objective-C runtime之运行时的基本特点(三)
- HDOJ-----1715大斐波那契数
- Java面向对象
- android studio无法检出github项目,报错"/usr/local/bin/git" error=2
- 七天学会ASP.NET MVC(七)——创建单页应用
- java 多线程并发
- android 系统框架
- C++ 程序延时处理的几种方法
- 【杭电oj】1222 - Wolf and Rabbit(GCD)
- live555—VS2010/VS2013 下live555编译、使用及测试
- Android学习之使用样式节省时间
- [学习笔记]变量的定义与作用范围(使用)
- HDU 1238 最长子串
- Java并发编程:volatile关键字解析(转载)