java并发你必须会的编程

转载 2016年08月28日 23:58:07

1.  Fork/Join框架

 

Fork/Join框架是JDK7提供的一个用于并行执行任务的框架,是一个把大任务切分为若干子任务并行的执行,最终汇总每个小任务后得到大任务结果的框架。我们再通过Fork和Join来理解下Fork/Join框架。Fork就是把一个大任务划分成为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果。

使用Fork/Join框架时,首先需要创建一个ForkJoin任务,它提供在任务中执行fork()和join操作的机制。通常情况下,我们不需要直接继承ForkJoinTask,只需要继承它的子类,Fork/Join框架提供了两个子类:RecursiveAction用于没有返回结果的任务;RecursiveTask用于有返回结果的任务。ForkJoinTask需要通过ForkJoinPool来执行。

任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。(工作窃取算法work-stealing)

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;


public class CountTask extends RecursiveTask<Integer>{
private static final int THRESHOLD = 10;
private int start;
private int end;

public CountTask(int start,int end){
this.start=start;
this.end=end;
}

protected Integer compute() {
int sum = 0;
boolean canCompute = (end-start) <= THRESHOLD;
        if(canCompute){
            for(int i=start;i<=end;i++){ sum += i; }
        }
        else{
            int middle = (start+end)/2;
            CountTask leftTask = new CountTask(start,middle);
            CountTask rightTask = new CountTask(middle+1,end);
            leftTask.fork();//将任务放入队列并安排异步执行,一个任务应该只调用一次fork()函数,除非已经执行完毕并重新初始化。
            rightTask.fork();//等待计算完成并返回计算结果
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
            sum = leftResult+rightResult;
        }
        return sum;
}

public static void main(String [] args) throws InterruptedException, ExecutionException{
ForkJoinPool forkJoinPool = new ForkJoinPool();
CountTask task = new CountTask(1,100);
Future<Integer> result = forkJoinPool.submit(task);// RecursiveTask<V> extends ForkJoinTask<V>
System.out.println(result.get());// ForkJoinTask<T> submit(ForkJoinTask<T> task) {

if(task.isCompletedAbnormally()){
         System.out.println(task.getException());
    }
}
}

2. 锁降级

锁降级是指写锁降级成读锁。如果当前线程拥有写锁,然后将其释放,最后获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,最后释放(先前拥有的)写锁的过程。参考下面的示例:

private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();
    private volatile static boolean update = false;

    public void processData()
    {
        r.lock();
        if(!update)
        {
            //必须先释放读锁
            r.unlock();
            //锁降级从写锁获取到开始
            w.lock();
            try
            {
                if(!update)
                {
                    //准备数据的流程(略)
                    update = true;
                }
                r.lock();
            }
            finally
            {
                w.unlock();
            }
            //锁降级完成,写锁降级为读锁
        }

        try
        {
            //使用数据的流程(略)
        }
        finally
        {
            r.unlock();
        }
    }

锁降级中的读锁是否有必要呢?答案是必要。主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。

3 . 实现线程安全的SimpleDateFormat

package ThreadLocalTest;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {

private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static ThreadLocal threadLocal = new ThreadLocal(){
  protected synchronized Object initialValue() {
            return new SimpleDateFormat(DATE_FORMAT);
        }
};

    public static DateFormat getDateFormat() {
        return (DateFormat) threadLocal.get();
    }

    public static Date parse(String textDate) throws ParseException {
        return getDateFormat().parse(textDate);
    }
}

<p>System.out.println(DateUtil.getDateFormat().format(newDate()));//输出当前日期</p>

点击打开链接

4.Callable& Future

Callable一般是和ExecutorService配合来使用的

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

也就是说Future提供了三种功能:

1)判断任务是否完成;

2)能够中断任务;

3)能够获取任务执行结果。

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


public class CallableAndFuture {

public static void main(String[] args) {
Callable<Integer>callable = new Callable<Integer>() {

@Override
public Integer call() throws Exception {
return new Random().nextInt(50);
}
};

FutureTask<Integer> f = new FutureTask<Integer>(callable);
new Thread(f).start();

try {
Thread.sleep(1000);
System.out.println(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
}catch (ExecutionException e) {
e.printStackTrace();
}

}
}

5. 线程池

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);  
for (int i = 1; i 10; i++) {  
    final int index = i;  
    fixedThreadPool.execute(new Runnable() {  
         @Override  
         public void run() {  
             String threadName = Thread.currentThread().getName();  
             Log.v("zxy", "线程:"+threadName+",正在执行第" + index + "个任务");  
             try {  
                    Thread.sleep(2000);  
             } catch (InterruptedException e) {  
                    e.printStackTrace();  
             }  
         }  
     });  
 }

点击打开链接

6. 写一个死锁程序

public class Test {

static Object obj1=new Object();
static Object obj2=new Object();

private static void fun1(){
synchronized (obj2) {
synchronized (obj1) {
//业务逻辑
}
}
}

private static void fun2(){
synchronized (obj1) {
synchronized (obj2) {
}
}
}
}
//然后执行的时候只需要开两个线程同时执行fun1和fun2就行了~

一般造成死锁必须同时满足如下4个条件:

1,互斥条件:线程使用的资源必须至少有一个是不能共享的;

2,请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其它线程持有的资源;

3,非剥夺条件:分配资源不能从相应的线程中被强制剥夺;

4,循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。

因为要产生死锁,这4个条件必须同时满足,所以要防止死锁的话,只需要破坏其中一个条件即可。

7.  生产者消费者问题

1. 阻塞队列实现:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class ProducerConsumerPattern {
 
    public static void main(String args[]){
 
     BlockingQueue sharedQueue = new LinkedBlockingQueue();

     Thread prodThread = new Thread(new Producer(sharedQueue));
     Thread consThread = new Thread(new Consumer(sharedQueue));

     prodThread.start();
     consThread.start();
    }
 
}
 
//Producer Class in java
class Producer implements Runnable {
 
    private final BlockingQueue sharedQueue;
 
    public Producer(BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
 
    @Override
    public void run() {
        for(int i=0; i<10; i++){
            try {
                System.out.println("Produced: " + i);
                sharedQueue.put(i);
            } catch (InterruptedException ex) {
                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
 
}
 
//Consumer Class in Java
class Consumer implements Runnable{
 
    private final BlockingQueue sharedQueue;
 
    public Consumer (BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
 
    @Override
    public void run() {
        while(true){
            try {
                System.out.println("Consumed: "+ sharedQueue.take());
            } catch (InterruptedException ex) {
                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
 
}

wait\notify实现

public class Dept {

private int capacity;// 容量
private int size;// 数量

public Dept(int capacity) {
this.capacity = capacity;
}

public synchronized void produce(int val) {

try {
int left = val;// left表示“想要生产的数量”
while (left > 0) {
while (size >= capacity)
// 库存已满时,等待“消费者”消费产品。
wait();

// 获取“实际生产的数量”(即库存中新增的数量)
// 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库)
// 否则“实际增量”=“想要生产的数量”
int inc = (size + left) > capacity ? (capacity - size) : left;
size += inc;
left -= inc;

System.out.printf(
"%s  生产:(%3d) --> 剩余=%3d, 该次生产数量=%3d, 当前仓库数量=%3d\n",
Thread.currentThread().getName(), val, left, inc, size);

notifyAll();// 通知消费者可以消费了

}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public synchronized void consume(int val) {
try {
// left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费)
int left = val;
while (left > 0) {
// 库存为0时,等待“生产者”生产产品。
while (size <= 0)
wait();
// 获取“实际消费的数量”(即库存中实际减少的数量)
// 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”;
// 否则,“实际消费量”=“客户要消费的数量”。
int dec = (size < left) ? size : left;
size -= dec;
left -= dec;
System.out.printf(
"%s 消费(%3d) <-- 需求量=%3d, 实际消费=%3d, 仓库剩余=%3d\n",
Thread.currentThread().getName(), val, left, dec, size);
notifyAll();
}
} catch (InterruptedException e) {
}
}

public String toString() {
return "capacity:" + capacity + ", actual size:" + size;
}
}
public class Producer {

     private Dept dept;

      public Producer(Dept dept) {
          this.dept = dept;
      }

     // 消费产品:新建一个线程向仓库中生产产品。
      public void produce(final int val) {
          new Thread() {
              public void run() {
                  dept.produce(val);
              }
          }.start();
      }
}
public class Customer {

     private Dept dept;

      public Customer(Dept depot) {
          this.dept = depot;
      }

     // 消费产品:新建一个线程从仓库中消费产品。
      public void consume(final int val) {
          new Thread() {
              public void run() {
                  dept.consume(val);
              }
          }.start();
      }
}
public class TestDemo1 {

public static void main(String[] args) throws InterruptedException {

         Dept mDepot = new Dept(100);
         Producer mPro = new Producer(mDepot);
         Customer mCus = new Customer(mDepot);

         mPro.produce(60);

         Thread.sleep(1000);

         mPro.produce(120);
         Thread.sleep(1000);
         mCus.consume(90);
         Thread.sleep(1000);
         mCus.consume(150);
         Thread.sleep(1000);
         mPro.produce(110);
}

}

java并发你必须会的编程

1.  Fork/Join框架 Fork/Join框架是JDK7提供的一个用于并行执行任务的框架,是一个把大任务切分为若干子任务并行的执行,最终汇总每个小任务后得到大任务结果的框架。我们再通...

java反射机制-一种必须掌握的编程思想

暑假跟团队开发了一个小型的网站,后台用到的是ssm框架,说到这里,不得不提到在做项目的时候遇到的许多问题,都是通过读源码解决的。 在读源码的过程中,不得不说到一个知识点---java反射机制,下面...
  • kl1106
  • kl1106
  • 2017年10月24日 16:25
  • 53

黑马程序员java基础网络编程必须掌握的经典代码

/* 需求:建立一个文本转换服务器。 客户端给服务端发送文本,服务单会将文本转成大写在返回给客户端。 而且客户度可以不断的进行文本转换。当客户端输入over时,转换结束。 分析...
  • suxi001
  • suxi001
  • 2014年06月27日 17:20
  • 267

你必须知道的261个Java语言问题笔记- Java编程基础1

Java编程基础1

黑马程序员——java编程那些事儿____java中必须理解的一些问题

-------android培训、java培训、期待与您交流! ---------- 这些问题都需要叙述,所以会看的没有条理,只要认真读,就会发现逻辑感很强 问题一...

你必须知道的261个Java语言问题笔记- Java编程基础2

Java编程基础2

编程之美7:字符串,那些你必须要会的事。

本系列收录了常见字符串面试和笔试中的八道题,更新于2015年4月23日。 如果有问题或想法,请直接留言,交流。题目一:字符串移位包含问题描述:给定两个字符串s1和s2,要求判定s2是否能够被通过循环...

编程之美7:字符串,那些你必须要会的事。

题目一:字符串移位包含 问题描述: 给定两个字符串s1和s2,要求判定s2是否能够被通过循环移位得到的字符串包含。例如,给定s1 = AABCD和s2 = CDAA,返回true;给定s1...

线程安全的问题,这是编程中肯定会遇到,遇到必须要解决的问题

趣IT科技 2017-01-13 17:41 在计算机编程中,线程的出现极大的提高了我们的运行效率,所以程序中好多都是多线程,然而多线程的使用却存在一些问题,就是当他操作共享数据的时候,如果...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:java并发你必须会的编程
举报原因:
原因补充:

(最多只允许输入30个字)