Java并发编程详解之 线程安全和对象共享
一、简单介绍
在我们用框架编程的过程中,经常不用太关注多线程问题,这是因为例如Servlet和RMI(Remote Method Invocation,远程方法调用),RMI使得代码能够调用其他JVM中的运行的对象。这些框架负责解决一些细节问题,例如请求管理、线程创建、负载均衡,并在正确的时刻将请求分发给正确的应用程序组件。编写Servlet的开发人员不需要了解有多少请求在同一时刻要被处理,也不需要了解套接字的输入流或输出流是否被阻塞。当调用Servlet的service方法来响应Web请求时,可以以同步方式来处理这个请求,就好像它是一个单线程程序。这种方式可以简化组件的开发,并缩短掌握这种框架的学习时间,但我们想学好编程,还是需要学习并发编程
1、编程的一些原则
在编写并发应用程序时,一种正确的编程方法就是:首先使代码正确运行,然后再提高代码的速度。即便如此,最好也只是当性能测试结果和应用需求告诉你必须提高性能,以及测量结果表明这种优化在实际环境中确实能带来性能提升时,才进行优化
2、活跃性问题
多线程中提到的活跃性问题,指的就是让线程处理死锁或活锁,就是各个线程阻塞不执行,当某个操作无法继续执行下去时,这样就导致了活跃性问题
平时编程中应该怎样做到这句话?:框架通过在框架线程中调用应用程序代码将并发性引入到程序中。在代码中将不可避免地访问应用程序状态,因此所有访问这些状态的代码路径都必须是线程安全的
3、一般并发错误的修复方法
如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误。有三种方式可以修复这个问题:
(1)不在线程之间共享该状态变量
(2)将状态变量修改为不可变的变量
(3)在访问状态变量时使用同步
4、线程安全类和线程安全程序
完全由线程安全类构成的程序并不一定就是线程安全的,而在线程安全类中也可以包含非线程安全的类
二、线程安全性
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的
1、无状态对象
一个无状态的Servlet
[java]
import java.math.BigInteger;
import javax.servlet.*;
import net.jcip.annotations.*;
@ThreadSafe
public class StatelessFactorizer extends GenericServlet implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
import java.math.BigInteger;
import javax.servlet.*;
import net.jcip.annotations.*;
@ThreadSafe
public class StatelessFactorizer extends GenericServlet implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}大多数Servlet都是无状态的,从而极大地降低了在实现Servlet线程安全性时的复杂性,只有当Servlet在处理请求时需要保存一些信息,线程安全性才会成为一个问题
无状态对象一定是线程安全的
下面的Servlet 就不再是无状态的了
在没有同步的情况下统计已处理请求数量的Servlet(不要这么做)
[java]
import java.math.BigInteger;
import javax.servlet.*;
import net.jcip.annotations.*;
@NotThreadSafe
public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
private long count = 0;
public long getCount() {
return count;
}
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
++count;
encodeIntoResponse(resp, factors);
}
void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {
}
BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}
BigInteger[] factor(BigInteger i) {
// Doesn't really factor
return new BigInteger[] { i };
}
}
import java.math.BigInteger;
import javax.servlet.*;
import net.jcip.annotations.*;
@NotThreadSafe
public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
private long count = 0;
public long getCount() {
return count;
}
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
++count;
encodeIntoResponse(resp, factors);
}
void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {
}
BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}
BigInteger[] factor(BigInteger i) {
// Doesn't really factor
return new BigInteger[] { i };
}
}
count是这个对象的状态,虽然递增操作++count是一种紧凑的语法,使其看上去只是一个操作,但是这个操作并非原子的,实际上,它包含了三个独立的操作:读取count的值,将值加1,然后将计算结果写入count。这是一个“读取-修改-写入”的操作序列,并且其结果状态依赖于之前的状态。这样会导致不恰当的执行时序而出现不正确的结果,这种情况称为竞态条件
解决方法:
1、使用AtomicLong类型的变量来统计已处理请求的数量
[java]
import java.math.BigInteger;
import java.util.concurrent.atomic.*;
import javax.servlet.*;
import net.jcip.annotations.*;
@ThreadSafe
public class CountingFactorizer extends GenericServlet implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public long getCount() { return count.get(); }
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
count.incrementAndGet();
encodeIntoResponse(resp, factors);
}
}
import java.math.BigInteger;
import java.util.concurrent.atomic.*;
import javax.servlet.*;
import net.jcip.annotations.*;
@ThreadSafe
public class Co
补充:软件开发 , Java ,