并发编程记录

synchronized:同步

对象锁:一个对象一把锁,用于实例方法,锁住对象中的所有同步实例方法

    synchronized直接加在方法上和synchronized(this)都是对当前对象加锁。

    重入锁:同步方法A中执行方法B,可以直接执行,无需抢锁,包括父子的情况。

类锁:一个类一把锁,用于静态方法,锁住类中的所有同步静态方法。

对象锁和类锁不冲突,持有类锁的不会影响同步的实例方法

锁改变:其他线程已经在队列中抢lock这把锁了,那改变了lock也还是会等待。

            其他线程没有在队列中抢lock,而是在lock改变后才来的,那就不会等lock这把锁,而是changelock这把锁。

            锁对象内的属性改变不影响锁,锁对象被new才会影响。

死锁:线程交叉持有对方需要的锁,导致线程都在等其他线程释放锁。

	private String lock1 = "lock1";
	private String lock2 = "lock2";
	
	public void execute1() {
		synchronized (lock1) {
			System.out.println("execute1 lock1---");
			synchronized (lock2) {
				System.out.println("execute1 lock2---");
			}
		}
	}
	public void execute2() {
		synchronized (lock2) {
			System.out.println("execute2 lock2---");
			synchronized (lock1) {
				System.out.println("execute2 lock1---");
			}
		}
	}

原子类:

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

i++:非原子性,拆成了几个步骤,并发时会有问题

Atomic类:用了CAS算法,保证了原子性。

CAS: 是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。首先,CPU 会将内存中将要被更改的数据与期望的值做比较。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。这一系列的操作是原子的。它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”。

       简单的来说,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V。这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。

但是多次调用之间的原子性还得同步。

	public synchronized int add(){
		try {
			count.addAndGet(1);
			Thread.sleep(10);
			count.addAndGet(2);
			Thread.sleep(10);
			count.addAndGet(3);
			Thread.sleep(10);
			
		} catch (Exception e) {
			// TODO: handle exception
		}
		return count.addAndGet(4);
	}

volatile:当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存。即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

ThreadLocal:在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量。

threadlocal结构:一个线程有一个ThreadLocalMap,map中可以存放多个threadlocal,map的key是threadlocal,值是需要设置的value,每个线程中可有多个threadLocal变量。

使用场景:如数据库连接,hibernate就用到了这个。还有如写接口时每次请求(每个线程)都要根据当前请求保存当前请求的一些数据。

单例模式:利用classLoader的比较精简的方式:

public class Singleton {

	private Singleton(){}
	static class SingletonClass{
		static Singleton singleton = new Singleton();
	}
	public static Singleton getInstance(){
		return SingletonClass.singleton;
	}
	
	public static void main(String[] args) {
		System.out.println(Singleton.getInstance());
		System.out.println(Singleton.getInstance());
		System.out.println(Singleton.getInstance());
	}
}


同步容器,并发容器

同步容器:可以简单地理解为通过synchronized来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。

如:Vector HashTable  Collections.synchronized

缺点:1、  通过同步方法将访问操作串行化,导致并发环境下效率低下。

            2、  复合操作(迭代、条件运算如没有则添加等)非线程安全,需要客户端代码来实现加锁。


并发容器:使用于高并发时。分为阻塞队列和非阻塞队列,都是线程安全的队列。

阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。

JDK7提供了7个阻塞队列。用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。 

LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。 

LinkedBlockingQueue中的锁是分离的,生产者的锁PutLock,消费者的锁takeLock。而ArrayBlockingQueue生产者和消费者使用的是同一把锁。

PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。 

DelayQueue:一个使用优先级队列实现的无界阻塞队列。 

SynchronousQueue:一个不存储元素的阻塞队列。 

LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。 

LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

public static void main(String[] args) throws Exception {
		// 非阻塞队列,通过循环CAS算法实现
		// ConcurrentLinkedQueue 基于链接节点的无界线程安全队列
		ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
		queue.add("aaa");
		
		/**
		 * 阻塞队列:通过加锁实现
		 *阻塞队列和普通队列最大不同:阻塞队列有阻塞添加和阻塞删除方法
		 *阻塞添加:当阻塞队列已满时,队列会阻塞,直到队列可以添加
		 *	添加方法:add:添加成功返回true,失败抛出异常
		 *			offer:成功返回true,如果队列已满,则返回false
		 *			put:将元素插入队列的尾部,如果队列已满,则一直阻塞
		 *阻塞删除:当阻塞队列为空时,队列会阻塞,直到队列有元素可以删除
		 *	删除方法:remove:移除指定元素,成功返回true,失败返回false
		 *			poll:获取并移除此队列的头元素,若队列为空,返回null
		 *			take:获取并移除队列的头元素,若没有元素则一直阻塞
		 */
		// 数组实现的有界阻塞队列,必须指定队列大小
		// take和put方法是同一把锁
		ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(4);
		
		arrayBlockingQueue.put("aaa");
		arrayBlockingQueue.put("ddd");
		arrayBlockingQueue.put("bbb");
		arrayBlockingQueue.put("ccc");
		
//		arrayBlockingQueue.put("hhh");// 队列已满,会阻塞在此
		System.out.println(arrayBlockingQueue.take());
		System.out.println(arrayBlockingQueue.take());
		System.out.println(arrayBlockingQueue.take());
		System.out.println(arrayBlockingQueue.take());
		
//		arrayBlockingQueue.take();// 此时队列为空,会阻塞在此
		
		// 链表实现的有界队列阻塞队列,默认值为Integer.MAX_VALUE
		// take和put方法是两把不同的锁
		LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<>();
		linkedBlockingQueue.put("aaa");
		linkedBlockingQueue.put("ddd");
		linkedBlockingQueue.put("bbb");
		linkedBlockingQueue.put("ccc");
		
//		arrayBlockingQueue.put("hhh");// 队列已满,会阻塞在此
		System.out.println(linkedBlockingQueue.take());
		System.out.println(linkedBlockingQueue.take());
		System.out.println(linkedBlockingQueue.take());
		System.out.println(linkedBlockingQueue.take());
		
//		arrayBlockingQueue.take();// 此时队列为空,会阻塞在此
		
	}


非阻塞队列:使用循环CAS的方式来实现。

ConcurrentLinkedQueue:基于链接节点的无界线程安全队列


ConcurrentHashMap:默认将hash表分为16个桶,每个桶一把锁。1.8有些变化,用了CAS算法,有更进一步的改进。

CopyOnWrite类:cow是计算机中通用的一种策略,读写分离的思想,读和写不同的容器。修改的时候,copy一个副本出去改,改完再将之前的引用指向修改后的。适用于读多写少的并发场景。注意:只保证了最终一致性。改动的不会立即生效。

如CopyOnWriteArrayList:add的时候,有加锁,只copy了一个副本出来,不然copy多个出来 改完后指向会出问题。读的时候就无所谓了。

ThreadPoolExecutor:

public static void main(String[] args) {
		
		BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(5);
		
		ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
				1, // 核心线程数
				5, // 最大线程数
				60, // 线程空闲多长时间被销毁
				TimeUnit.SECONDS, // 空闲时间单位 
				queue, // 指定队列
				new MyRejected()
				);
		
		for (int i = 1; i <= 11; i++) {
			final int j = i;
			poolExecutor.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(j + "");
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
		/**
		 * 输出:1 8 7 9 10 拒绝策略 2 5 4 3 6
		 * 分析:核心线程为1,最大线程为5,队列长度为5,
		 * 		第1个runnable:因为核心线程数为1,一进来就执行了
		 * 		第2-6个runnable:因为队列长度为5,所以被放到了队列中
		 * 		第7-11个runnable:因为最大线程数为5,7-11有5个包括核心的1个,共有6个,所以执行了前5个,最后一个被拒绝了
		 * 			所以是先执行第一个,然后7 8 9 10,第11个走了拒绝策略,最后是2 3 4 5 6
		 */
	}
	
	static class MyRejected implements RejectedExecutionHandler{
		public MyRejected() {
			
		}

		@Override
		public void rejectedExecution(Runnable runnable, ThreadPoolExecutor paramThreadPoolExecutor) {
			System.out.println("拒绝策略:" + runnable.toString());
		}
	}

CountDownLatch:类似计数器的功能。比如有一个任务1,它要等待其他2个任务执行完毕之后才能执行。

public static void main(String[] args) {

		/**
		 * 可以想象为有三个人,其中一个人已经在100米处了,其他两个人还在起点,在100米处的人需要等其他两个人到了100米的地方才可以往前走。
		 */
		CountDownLatch latch = new CountDownLatch(2);
		ExecutorService threadPool = Executors.newFixedThreadPool(3);

		for (int i = 1; i <= 2; i++) {
			final int j = i;
			threadPool.execute(new Thread("t" + i) {
				public void run() {
					System.out.println("线程:" + Thread.currentThread().getName() + " 执行");
					try {
						Thread.sleep(j * 1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成");
					latch.countDown();
				};
			});
		}
		
		System.out.println("线程:" + Thread.currentThread().getName() + " 执行");
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成");

		threadPool.shutdown();
	}

CyclicBarrier:回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。当所有等待线程都被释放以后,CyclicBarrier可以被重用。

public static void main(String[] args) {
		
		/**
		 * 可以想象为3个人走路,走的速度都不一样,走到一定的程度时,比如100米时,有3个栅栏拦着,需要这三个人每人拿掉一把,然后才可以一起走
		 */
		CyclicBarrier barrier = new CyclicBarrier(3);// 3即代表着有3个人一起走路
		
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		for (int i = 1; i <= 3; i++) {
			final int j = i;
			threadPool.execute(new Thread("t" + i){
				@Override
				public void run() {
					System.out.println("线程:" + Thread.currentThread().getName() + " 开始执行");
					try {
						Thread.sleep(j * 1000);// 代表走的速度都不一样
						System.out.println("线程:" + Thread.currentThread().getName() + " 开始等待其他线程执行完");
						barrier.await();// 代表有栅栏拦在这,需要三个人都执行await方法才可以继续往前走
					} catch (InterruptedException e) {
						e.printStackTrace();
					} catch (BrokenBarrierException e) {
						e.printStackTrace();
					}
					System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成");
				}
			});
		}
		threadPool.shutdown();
	}

Semaphore:信号量,可以控制同时访问的线程个数。

public static void main(String[] args) {
		ExecutorService threadPool = Executors.newCachedThreadPool();
		// 只允许5个线程同时访问
		Semaphore semaphore = new Semaphore(5);
		for (int i = 1; i <= 20; i++) {
			final int j = i;
			threadPool.execute(new Thread("t" + j) {
				@Override
				public void run() {
					try {
						semaphore.acquire();// 获取许可
						System.out.println("线程:" + Thread.currentThread().getName() + " 开始执行");
						Thread.sleep(2000);
						System.out.println("线程:" + Thread.currentThread().getName() + " 执行完成");
						semaphore.release();// 释放
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}

		threadPool.shutdown();
	}
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于 某个线程A等待若干个其他线程执行完任务之后,它才执行;
CyclicBarrier一般用于 一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。


Future:异步获取多线程的执行结果。可以实现:1.启动多线程任务 2.处理其他事 3.收集多线程任务结果。

public static void main(String[] args) throws Exception {
		ExecutorService threadPool = Executors.newFixedThreadPool(3);

		// 1、启动多线程任务
		Future<String> future = threadPool.submit(new Callable<String>() {
			@Override
			public String call() throws Exception {
				System.out.println("开始执行---");
				Thread.sleep(3000);
				System.out.println("执行完毕---");
				return "执行结果---";
				
			}
		});
		
		// 2、处理其他事
		Thread.sleep(2000);

		// future还没执行完时会阻塞在此处
		// 3、收集多线程任务结果
		System.out.println(future.get());
		threadPool.shutdown();
	}


猜你喜欢

转载自blog.csdn.net/ch_show/article/details/80207928