并行程序设计模式+并发数据结构+多线程常用知识(executor,中断等)

大量相互独立且同类的任务进行并发处理,会将程序的任务量分配到不同的任务中,这样才能真正获得性能的提升。——《java并发编程实践》

多线程访问同一个变量,没有正确的同步,有三个方法:

  • 不使用跨线程共享变量
  • 使变量不可变
  • 任何访问状态变量的时候使用同步

线程安全

1.不要让this引用在构造期间逸出。如果在构造期间启动一个线程,this引用则能被新线程共享

2.要确保线程限制对象不会从它所在的线程中逸出

3.栈限制,只能通过本地变量才能触及对象

4.threadlocal,将每个线程与持有数据的对象关联在一起

5.不变对象永远是安全的

future模式

开另一个线程完成耗时长的任务,让主线程可以先完成其他任务而不必先等那个任务完成,jdk内置的future模式的类有future,callable,futuretask等。(可以用callable<void>表示无返回值)

自定义future提交给线程实现(new Thread(futuretask).start();),或者用线程池

(使用的线程池实现的)



package yarnsls.javaFile.com;


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

public class testThread {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 第一步 网购厨具
        Callable<Chuju> onlineShopping = new Callable<Chuju>() {

            @Override
            public Chuju call() throws Exception {
                System.out.println("第一步:下单");
                System.out.println("第一步:等待送货");
                Thread.sleep(5000);  // 模拟送货时间
                System.out.println("第一步:快递送到");
                return new Chuju();
            }
            
        };
        FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
        new Thread(task).start();
        // 第二步 去超市购买食材
        Thread.sleep(2000);  // 模拟购买食材时间
        Shicai shicai = new Shicai();
        System.out.println("第二步:食材到位");
        // 第三步 用厨具烹饪食材
        if (!task.isDone()) {  // 联系快递员,询问是否到货
            System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");
        }
        Chuju chuju = task.get();
        System.out.println("第三步:厨具到位,开始展现厨艺");
        cook(chuju, shicai);
        
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
    
    //  用厨具烹饪食材
    static void cook(Chuju chuju, Shicai shicai) {}
    
    // 厨具类
    static class Chuju {}
    
    // 食材类
    static class Shicai {}

}

使用的线程池实现的,主要这几句,其他基本一样。

public class Main {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        chuju cj = new chuju();
        shicai sc = new shicai();
        Future<Boolean> f1 = es.submit(cj);
        Future<Boolean> f2 = es.submit(sc);
        try{
            Boolean b1 = f1.get();//会阻塞当前线程,call方法中有sleep
            Boolean b2 = f2.get();
            System.out.println(b1);
            System.out.println(b2);
            if(b1 && b2){
                zuofan zf = new zuofan();
                es.submit(zf);
            }
        }catch(InterruptedException e){
            e.printStackTrace();
        }catch (ExecutionException e){
            e.printStackTrace();
        }
        es.shutdown();
    }
}

可以配合concurrenthashmap<A , Future<V>>延迟计算实现线程安全的高速缓存《java并发编程实现》P108 

 putifabsent:如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null

futuretask可以直接run也可以新建线程调用线程的start方法来调用run。如果直接get会先启动,然后阻塞等待任务完成,如下图

别人的话:(ExecutorService的submit方法获取返回值get获取数据的时候,会导致get所在的线程发生堵塞,直到你返回值的线程执行完后,get线程才获取最终的结果)

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_多线程

 

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_java 同一条数据并发编辑 并发容器_02

 

master-worker模式

Master-Worker模式是常用的并行计算模式,它的核心思想是系统有两类进程协作工作,Master负责接收和分配任务,Worker负责处理子任务。当各个Worker进程处理完成后,会将结果返回给Master,由Master做总结归纳。其好处是将一个大的任务分解成若干个小任务并行执行,从而提高系统的吞吐量。

Guarded suspension

使用一个队列存储服务请求,防止服务请求来的太快。每次从队列中提取请求对象加以处理。

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_java 同一条数据并发编辑 并发容器_03

不变模式

比只读属性具有更强的一致性和不变性,一旦创建,内部状态将永远不会发生改变。

生产者-消费者模式


 

多线程常用

信号量semaphore:

volatile:只能保证其可见性,但不能保证原子性

  • 写入变量时不依赖变量的当前值;或者能够确保只有单一的线程能修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束
  • 访问变量时,没有其他的原因需要加锁

https://www.jianshu.com/p/157279e6efdb 

内存可见性、重排序、顺序一致性、volatile、锁、final:

runnable,thread,executor: (前面还有一个callable,有返回的runnable)

runnable相当于线程任务类,thread实现了runnable接口,但run方法几乎没用,需要传入任务并调用start方法,executor是线程池架构,需要传入runnable或者callable任务。

executor

讲解:

executor里常用的executor工厂类,均实现了ThreadPoolExecutor,是对其的包装,可以自己实现该类。

使用executor优化的例子:

第一个是完全在主线程中解决问题,适用于少数特例(吞吐量和响应性都不好),任务量少但生命周期长时,或仅服务一个用户是,在同一时间只需处理一个请求。

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_线程池_04

第二个是优化一些——显式创建线程。问题在于:1.线程生命周期的开销,特别是如果请求是频繁且轻量的,那频繁创建新线程会消耗大量的计算资源。2.稳定性,可创建线程数目有上限,易发生OOM的问题

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_线程池_05

第三个是使用executor,使用线程池。为任务提交和任务执行直接的解耦提高了标准的方法。(下面的例子是创建了一个定长的线程池)

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_java 同一条数据并发编辑 并发容器_06

executor的shutdown()会启动一个平稳关闭的过程(停止接受任务,等待已提交任务完成),shutdownnow会直接关闭

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_多线程_07

使用completionservice来获得executor的处理结果,实现效果是每下载好一个图片就显示出来。整合了executor和blockingqueue《java并发编程实践》p130

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_线程池_08

1.ExecutorService接口继承了Executor接口,是Executor的扩展子接口;

2.Executor中的execute接口的是实现Runable接口的对象;而ExecutorService中的submit接收的实现Runable接口的对象或者callable接口的对象;

3.Executor中的execute方法没有返回值,而submit方法有future返回值;

4.ExecutorService还提供了控制线程池的方法;

使用ExecutorService中的invokeAll方法或者Future的get(secondsnumber,TimeUnit.SECONDS)

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_多线程_09

 

ThreadLocal:ThreadLocal用于保存某个线程共享变量:对于同一个ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

class bank{
	ThreadLocal<Integer> t = new ThreadLocal<Integer>(){
		protected Integer initialValue() {
			return 100;
		}
	};
	public int get(){
		return t.get();
	}
	public void set() {
		t.set(t.get()+10);
	}
}
class transfer implements Runnable{
	bank bank;
	public transfer(bank bank){
		this.bank = bank;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			bank.set();
			System.out.println(Thread.currentThread()+":"+bank.get());
		}
	}
}
public class testThread {
	Semaphore semaphore = new Semaphore(10, true);

	private String[] str = new String[]{"123","456"};
	public void change(){
		this.str[0] = new String("456");
	}
//	导致私有的str泄露
	public String[] getString(){
		return str;
	}
	
	

    public static void main(String[] args) throws InterruptedException, ExecutionException {
    	bank bank= new bank();
    	transfer t = new transfer(bank);
    	Thread t1 = new Thread(t);
    	Thread t2 = new Thread(t);
    	t1.start();
    	t2.start();
    	t1.join();
    	t2.join();
    	System.out.println("final : "+bank.get());
    	
    	System.exit(0);
    }
}

优化线程池的大小:

Ncpu为cpu的数量

ucpu为目标cpu的使用率 0-1

w/c为等待时间与计算时间的比率

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_线程池_10

@GuardedBy(lock):线程只有在持有了一个特定的锁(lock)后,才能访问某个域或方法; 
@GuardedBy("this"):包换在对象中的内部锁(方法或域是这个对象的一个成员); 
@GuardedBy("fieldName"):值与filedName引用的对象相关联的锁,或者是一个隐式锁(filedName没有引用一个Lock),或者是一个显示锁(filedName引用了一个Lock); 
@GuardedBy("ClassName.fieldName"):类似于@GuardedBy("fieldName"),不过所引用的锁对象是存储在另一个类(或本类)中的静态域; 
@GuardedBy("methodName()"):锁对象是methodName()方法的返回值; 
@GuardedBy("ClassName.class"):ClassName类的直接量对象。 

synchronized是Java提供的内部锁,里边有类锁和对象锁;在静态方法中,我们一般使用类锁,在实例方法中,我们一般使用对象锁

synchronized(this) // 表示锁住的是对象,同一对象调用该函数会串行执行,不同对象调用该函数会并行执行
synchronized(GuardedByTest.class) // 表示锁住该类,不管是不是同一对象执行该函数都是串行执行

是一个同步锁,后面持有这个锁时才能更改这个变量(synchronized(locksname){ number++;}) ;构造了一个原子操作

加锁不一定线程安全,要保证同步发生在正确的锁上。如下图,客户端加锁必须保证客户端代码与对象保护自己状态时使用的是相同的锁(一图错误,二图正确)

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_System_11

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_多线程_12

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_java 同一条数据并发编辑 并发容器_13

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_java 同一条数据并发编辑 并发容器_14

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_多线程_15

JUC:java.util.concurrent

中断:他不会真正中断一个正在运行的线程,仅仅是发出中断请求,线程会在下一个方便的时刻中断(取消点)。如wait,sleep,join等方法接受到中断请求会抛出一个异常。中断通常是实现取消的最好选择。


并发数据结构

1.并发list

vector和copyonwritearraylist是线程安全的list。(如必须使用arraylist则需要使用Collections.synchronizedList(List list))

CopyOnWriteArrayList在写的时候加锁,读的时候不需要加锁。在改变对象时,先获得对象的一个副本,然后对副本进行修改,最后将副本写回。

vector所有操作都使用了同步关键字synchronized,在高并发的情况下,大量锁回拖累性能

2.并发set

CopyOnWriteSet,和上面的一样,适合读多写少的高并发场合

3.并发map

之前写过一些concurrenthashmap的东西:

4.并发queue


一个ConcurrentLinkedQueue高性能队列:和并发map差不多,都是通过cas实现线程安全

一个是BlockingQueue阻塞队列

单生产者,单消费者  用 LinkedBlockingqueue

多生产者,单消费者   用 LinkedBlockingqueue

单生产者 ,多消费者   用 ConcurrentLinkedQueue

多生产者 ,多消费者   用 ConcurrentLinkedQueue

理解:防止进入堵塞状态,进入空转(多个消费者)

适用阻塞队列的好处:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距。

当许多线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。

LinkedBlockingQueue 多用于任务队列,简化多线程见的数据共享

ConcurrentLinkedQueue  多用于消息队列,高并发状态下的高性能

java 同一条数据并发编辑 并发容器 java并行调用同一个方法_多线程_16

具体什么情况用什么还是有点不清楚。。。有点乱

5.并发qeque

双端队列适用于窃取工作(work stealing)的模式(类似于阻塞队列和生产者消费者模式)。每个消费者完成了双端队列中的全部任务后可以偷取其他消费者的双端队列中的末尾任务。