一、多线程
首先搞清楚容易混淆的两个概念:
进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。
线程:表示程序的执行流程,是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:线程池 :解决多线程创建销毁的消耗问题
如果有时间我一定一一总结给大家,请多提批评意见,共同进步,欢迎拍砖!