Java并发

1、什么是并发问题?
多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。
银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为 1000+100-50=1050。这就是典型的并发问题。如何解决?可以用锁。

2、进程VS线程
进程是以独立于其他进程的方式运行的,进程间是互相隔离的。一个进程无法直接访问另一个进程的数据。进程的资源诸如内存和CPU时间片都是由操作系统来分配。
线程又被称为轻量级进程。每个线程有它独自的调用栈, 但是在同一进程下的线程又能互相访问它们间的共享数据。每个线程都有它独自的缓存。如果一个线程读取了某些共享数据,那么它将这些数据存放在自己的缓存中以供将来再次读取。
一个 Java应用程序默认以一个进程的形式运行着。在一个 Java程序中,你将协同多个不同的线程一起完成并行运算或实现异步行为。

3、并发问题
线程有独自的调用栈,但是又能互相访问共享的数据。所以这里你会遇到两个问题,可见性和访问。

可见性问题发生于如果线程A先读取了某些共享数据,之后线程B对这些数据进行了修改,那么线程A可能看不到线程B对这数据的改动。

访问问题发生于于多个线程同时访问修改同一个共享数据。可见性及访问问题将导致:

活跃性失败——由于并发访问数据导致程序无任何反应。 譬如,死锁。

安全性失败——程序创建了错误的数据。

4、Java中的并发
Java程序可通过Thread这个类来创建线程。从 Java1.5起,在 Java.util.concurrent中提供了改进的并发库。

5、Lock的使用
用synchronized关键字可以对资源加锁。用Lock关键字也可以。它是JDK1.5中新增内容。

好处:简而言之,就是对wait的线程进行了分类,节约资源。用厕位理论来描述,则是那些蹲了一半而从厕位里出来等待的人原因可能不一样,有的是因为马桶堵了,有的是因为马桶没水了。通知(notify)的时候,就可以喊:因为马桶堵了而等待的过来重新排队(比如马桶堵塞问题被解决了),或者喊,因为马桶没水而等待的过来重新排队(比如马桶没水问题被解决了)。这样可以控制得更精细一些。
Lock方式与synchronized对应关系:
Lock await signal signalAll
synchronized wait notify notifyAll
注意:不要在Lock方式锁住的块里调用wait、notify、notifyAll

6、利用管道进行线程间通信;

7 阻塞队列
阻塞队列可以代替管道流方式来实现进水管/排水管模式(生产者/消费者).JDK1.5提供了几个现成的阻塞队列. 现在来看ArrayBlockingQueue的代码如下:
这里是一个阻塞队列
BlockingQueue blockingQ = new ArrayBlockingQueue 10;
一个线程从队列里取
for(;;){
Object o = blockingQ.take();//队列为空,则等待(阻塞)
}
另一个线程往队列存
for(;;){
blockingQ.put(new Object());//队列满,则等待(阻塞)
}
可见,阻塞队列使用起来比管道简单。

8 使用Executors、Executor、ExecutorService、ThreadPoolExecutor
可以使用线程管理任务。还可以使用jdk1.5提供的一组类来更方便的管理任务。从这些类里我们可以体会一种面向任务的思维方式。这些类是:
Executor接口。使用方法:
Executor executor = anExecutor;//生成一个Executor实例。
executor.execute(new RunnableTask1());
用意:使用者只关注任务执行,不用操心去关注任务的创建、以及执行细节等这些第三方实现者关心的问题。也就是说,把任务的调用执行和任务的实现解耦。
实际上,JDK1.5中已经有该接口出色的实现。够用了。
Executors是一个如同Collections一样的工厂类或工具类,用来产生各种不同接口的实例。
ExecutorService接口它继承自Executor. Executor只管把任务扔进 executor()里去执行,剩余的事就不管了。而ExecutorService则不同,它会多做点控制工作。比如:
class NetworkService {
private final ServerSocket serverSocket;
private final ExecutorService pool;

public NetworkService(int port, int poolSize) throws IOException {
    serverSocket = new ServerSocket(port);
    pool = Executors.newFixedThreadPool(poolSize);
}

public void serve() {
    try {
        for (;;) {
            pool.execute(new Handler(serverSocket.accept()));
        }
    } catch (IOException ex) {
        pool.shutdown(); //不再执行新任务
    }
}

}

class Handler implements Runnable {
private final Socket socket;
Handler(Socket socket) { this.socket = socket; }
public void run() {
// read and service request
}
}
ExecutorService(也就是代码里的pool对象)执行shutdown后,它就不能再执行新任务了,但老任务会继续执行完毕,那些等待执行的任务也不再等待了。
任务提交者与执行者通讯
public static void main(String args[])throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable task = new Callable(){
public String call()throws Exception{
return “test”;
}
};
Future f = executor.submit(task);
String result = f.get();//等待(阻塞)返回结果
System.out.println(result);
executor.shutdown();
}
Executors.newSingleThreadExecutor()取得的Executor实例有以下特性:
任务顺序执行. 比如:
executor.submit(task1);
executor.submit(task2);
必须等task1执行完,task2才能执行。
task1和task2会被放入一个队列里,由一个工作线程来处理。即:一共有2个线程(主线程、处理任务的工作线程)。
其它的类请参考Java Doc

9、并发流程控制
CountDownLatch 门插销计数器
启动线程,然后等待线程结束。即常用的主线程等所有子线程结束后再执行的问题。
public static void main(String[] args)throws Exception {
// TODO Auto-generated method stub
final int count=10;
final CountDownLatch completeLatch = new CountDownLatch(count);//定义了门插销的数目是10

for(int i=0;i<count;i++){
    Thread thread = new Thread("worker thread"+i){
            public void run(){
                //do xxxx                                   
                completeLatch.countDown();//减少一根门插销
            }
        };
    thread.start();
}           
completeLatch.await();//如果门插销还没减完则等待。

}
JDK1.4时,常用办法是给子线程设置状态,主线程循环检测。易用性和效率都不好。
启动很多线程,等待通知才能开始
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
final CountDownLatch startLatch = new CountDownLatch(1);//定义了一根门插销

for (int i = 0; i < 10; i++) {
    Thread thread = new Thread("worker thread" + i) {
            public void run() {
                try {
                    startLatch.await();//如果门插销还没减完则等待
                } catch (InterruptedException e) {

                }
                // do xxxx
            }
        };
    thread.start();
}
startLatch.countDown();//减少一根门插销

}
CycliBarrier. 等所有线程都达到一个起跑线后才能开始继续运行。
public class CycliBarrierTest implements Runnable {
private CyclicBarrier barrier;

public CycliBarrierTest(CyclicBarrier barrier) {
    this.barrier = barrier;
}

public void run() {
    //do xxxx;
    try {
        this.barrier.await();//线程运行至此会检查是否其它线程都到齐了,没到齐就继续等待。到齐了就执行barrier的run函数体里的内容
    } catch (Exception e) {

    }
}

/**
 * @param args
 */
public static void main(String[] args) {
    //参数2代表两个线程都达到起跑线才开始一起继续往下执行
    CyclicBarrier barrier = new CyclicBarrier(2, new Runnable() {
            public void run() {
                //do xxxx;
            }
        });
    Thread t1 = new Thread(new CycliBarrierTest(barrier));         
    Thread t2 = new Thread(new CycliBarrierTest(barrier));
    t1.start();
    t2.start();
}

}
这简化了传统的用计数器+wait/notifyAll来实现该功能的方式。
10 并发3定律
Amdahl定律. 给定问题规模,可并行化部分占12%,那么即使把并行运用到极致,系统的性能最多也只能提高1/(1-0.12)=1.136倍。即:并行对提高系统性能有上限。
Gustafson定律. Gustafson定律说Amdahl定律没有考虑随着cpu的增多而有更多的计算能力可被使用。其本质在于更改问题规模从而可以把Amdahl定律中那剩下的88%的串行处理并行化,从而可以突破性能门槛。本质上是一种空间换时间。
Sun-Ni定律. 是前两个定律的进一步推广。其主要思想是计算的速度受限于存储而不是CPU的速度. 所以要充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解.

11 由并发到并行
计算机识别物体需要飞速的计算,以至于芯片发热发烫,而人在识别物体时却一目了然,却并不会导致某个脑细胞被烧热烧焦(夸张)而感到不适,是由于大脑是一个分布式并行运行系统,就像google用一些廉价的linux服务器可以进行庞大复杂的计算一样,大脑内部无数的神经元的独自计算,互相分享成果,从而瞬间完成需要单个cpu万亿次运算才能有的效果。试想,如果在并行处理领域有所创建,将对计算机的发展和未来产生不可估量的影响。当然,其中的挑战也可想而知:许多的问题是并不容易轻易就“分割”的了的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值