JavaSE---多线程

一、多线程

1.1 进程和线程

  • 进程
    • 运行中的程序称之为进程
    • 特点:
      • 在单核CPU下,通过一个时间点上只能有一个程序在运行。交替执行
      • 宏观并行、微观串行
  • 线程
    • 进程组成部分,线程用进程中,单一的执行的顺序控制流程。同时也是CPU的调度单位
    • 进程是可以有多个线程的,他们之间独立运行。交替执行。称之为多线程
  • 区别
    • 进程是系统分配资源的单位,线程是CPU的调度单位
    • 一个程序,至少包含一个进程
    • 一个进程至少包含一个线程。线程是不能独立运行的,必须依附在进程中
    • 进程之间是不能共享数据段地址,但是同一个进程下的线程是可以共享的

1.2 线程的组成部分

  • CPU时间片
    • 操作系统会为每个线程分配时间
  • 运行数据
    • 堆空间:存储线程需使用的对象。多个线程可以共享堆中的对象
    • 栈空间:存储线程需使用的局部变量。每个线程都拥有自己的栈空间
  • 线程的逻辑代码

二、创建线程【重点】

2.1 方式1:通过继承Thread类

public class Demo01 {
	public static void main(String[] args) {
		//创建并启动线程
		//创建线程对象
		MyThread t1 = new MyThread();
		//启动线程
		t1.start();
		
		MyThread t2 = new MyThread();
		t2.start();
		
	}
}

/**
 * 1、写一个类继承Thread类
 * 2、重写run方法,编写线程逻辑代码
 * 3、创建并启动线程
 */


//创建方式1:继承Thread类
class MyThread extends Thread{
	//重写父类的run方法(线程的逻辑代码)
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			//Thread.currentThread()获取当前线程对象    getName();  获取线程的名称
			System.out.println(Thread.currentThread().getName()+"----->"+i);
		}
	}
}

2.2 方式2:通过实现Runnable接口

public class Demo02 {
	public static void main(String[] args) {
		//创建并启动线程
		
		//1、创建线程对象
		Thread t1 = new Thread(new MyThread2());
		t1.start();
		
		Thread t2 = new Thread(new MyThread2());
		t2.start();
		
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"--->"+i);
		}
	}
}

/**
 * 1、写一个类继承实现Runnable接口
 * 2、重写run方法,编写线程逻辑代码
 * 3、创建并启动线程
 */

//创建方式2:实现Runnable接口
class MyThread2 implements Runnable{  //并不是一个线程类,只是一个线程任务
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"--->"+i);
		}
	}
}

2.3 两种方式的区别

两种方式的区别:

  • 1、java只支持单继承,所以继承Thread类就没办法继承其他的类,所以使用Runnable接口更灵活
  • 2、继承自Thread类,表示这个类就是一个线程类,可以直接启动线程实现Runnable接口,表示这个类是一个线程任务,需要创建线程对象从而执行这个线程任务

2.4 启动线程需要注意的问题

  • 1、不要调用run方法
  • 2、一个线程只能调用一次start方法

2.5 线程的状态(基本)

初始状态 ------ 就绪状态 ------- 运行状态 -------- 终止状态

在这里插入图片描述

2.6 线程常见的方法

  • 1、设置线程名称 (setName、getName、Thread.currentThread获取当前线程对象)
    • 如果没有设置线程名称,那么默认的名称为Thread-0 Thread-N
  • 2、设置线程的优先级 (setPriority、getPriority)
    • 如果没有设置线程的优先级,那么默认的优先级为5
    • 线程有的优先级为1~10之间,设置优先级只是提高了抢占CPU的概率
  • 3、线程休眠 (Thread.sleep(毫秒数))
    • 让当前线程进入到休眠状态,并让出CPU使用权,直到休眠结束,才会继续抢占CPU
  • 4、线程礼让(Thread.yeild())
    • 让出CPU使用权,但是立马又会去重新抢占CPU
  • 5、线程加入(join())
    • 在当前线程中加入另一线程,必须要将另一个线程执行完之后才会继续执行当前线程

2.7 线程的状态(等待)

在这里插入图片描述

三、线程安全

3.1 线程安全问题

当多线程并发访问临界,如果破坏原子操作,可能会造成数据不一致

临界资源:共享资源(同一个对象),一次只可以有一个线程操作,才可以保证准确性

原子操作:不可拆分的步骤,被视作一个整体。其步骤不能打乱和缺省

3.2 线程同步

  • 方式一:同步代码块
    • synchronized(临界资源对象){ //互斥锁标记
    • ​ //原子代码
    • }
  • 方式二:同步方法
    • public synchronized void sale(){ //互斥锁标记是this对象
    • ​ //原子代码
    • }

3.3 线程同步卖票案例

3.3.1 线程不安全

  • 循环中的代码并非原子操作,所以在线程执行的过程中,会有其他线程执行,会造成ticket < 0无效
  • ticket-- 并非原子操作,其包含有三个步骤
    • 1、从内存中(主内存)拿ticket变量
    • 2、将ticket-1(工作内存)
    • 3、将结果赋值给内存中(主内存)
class TicketRunnable implements Runnable{
	int ticket = 100;
	@Override
	public void run() {
		while(true) {
			if(ticket < 0) {
				break;
			}
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//获取线程的名称
			System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票");
		}
	}
}

3.3.2 同步代码块解决

class TicketRunnable implements Runnable{
	int ticket = 50;
	final Object obj = new Object();
	@Override
	public void run() {
		while(true) {
			synchronized (obj) { //互斥锁对象(可以是任意的java对象,但是要保证对象唯一)
				if(ticket < 0) {
					break;
				}
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//获取线程的名称
			System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票");
			}
		}
	}
}

3.3.3 同步方法解决

class TicketRunnable implements Runnable{
	int ticket = 50;
	final Object obj = new Object();
	@Override
	public void run() {
		while(true) {
			sale();
		}
	}
	
	//同步方法的锁对象是this,所以要保证当前对象的唯一
	public synchronized void sale() {
		if(ticket < 0) {
			System.exit(0);
		}
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//获取线程的名称
		System.out.println(Thread.currentThread().getName()+"卖出了:"+ ticket-- +"号票");
	}
}

四、线程的生命周期

线程的5种状态:创建状态---->就绪状态---->运行状态---->阻塞\等待状态---->终止状态
在这里插入图片描述

五、线程死锁

当第一个线程拥有A对象的锁标记,并等待B对象的所标记。同时第二个线程拥有B对象锁标记,同时等待A对象的锁标记时,产生死锁

public class TestDeadLock {
	public static void main(String[] args) {
		Boy boy = new Boy();
		Gilr gilr = new Gilr();
		boy.start();
		gilr.start();
	}
}

class  MyLock{
	static Object obj1 = new Object(); //左筷子
	static Object obj2 = new Object(); //右筷子
}

class Boy extends Thread{
	@Override
	public void run() {
		synchronized (MyLock.obj1) { //拥有左筷子   锁
			System.out.println("boy获取到了左筷子,等待右筷子");
			synchronized (MyLock.obj2) {
				System.out.println("boy可以吃饭");
			}
		}
	}
}
class Gilr extends Thread{
	@Override
	public void run() {
		synchronized (MyLock.obj2) {
			System.out.println("Gilr拥有右筷子,等待左筷子");
			synchronized (MyLock.obj1) {
				System.out.println("Gilr可以吃饭");
			}
		}
	}
}

六、线程通信

若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

  • wait()
    • 当前线程释放锁对象, 并处于阻塞状态,进入等待队列,直到有别人唤醒
  • notify()、notifyAll()
    • 随机唤醒一个正在等待的线程
    • 唤醒所有等待队列中的线程

**[注意:所有的等待、通知方法必须在对加锁的同步代码块中

public class TestProducerAndComsuer {
	static Shop shop = new Shop();
	public static void main(String[] args) {
		ProducerThread producerThread = new ProducerThread();
		ConsumerThread consumerThread = new ConsumerThread();
		producerThread.start();
		consumerThread.start();
	}
}
/**
 * 商品
 */
class Phone{
	String name;
}
/**
 * 缓冲区
 */
class Shop{
	//表示买卖的商品    如果为null表示没有商品需要生产,否则表示已有商品需要消费
	Phone phone;
	//进货 (生产)
	public synchronized void putPhone(Phone phone) { //参数表示进货的商品
		if(this.phone != null) { //表示有商品,需要等待
			try {
				System.out.println("表示有商品,需要等待");
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//表示没有商品需要生产
		System.out.println("正在生产商品"+phone.name);
		
		//1、模拟生产商品
		this.phone = phone;
		//2、通过消费者进行消费
		this.notify();
	}
	//卖货(消费)
	public synchronized void getPhone() {
		if(this.phone == null) { //表示没有商品需要等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//表示有商品需要消费
		System.out.println("正在消费商品"+ phone.name);
		
		//1、模拟消费商品
		this.phone = null;
		//2、通知生产者生产
		this.notify();
	}
}
/**
 * 消费者线程
 */
class ConsumerThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			TestProducerAndComsuer.shop.getPhone();
		}
	}
}
/**
 * 生产者线程
 */
class ProducerThread extends Thread{
	@Override
	public void run() {
		for (int i = 10; i < 20; i++) {
			Phone phone = new Phone();
			phone.name = "IPhone"+i;
			TestProducerAndComsuer.shop.putPhone(phone);
		}
	}
}

七、线程池

7.1 线程池概念

  • 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。
  • 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用
  • 线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。

7.2 线程池实现原理

在这里插入图片描述

7.3 线程池中常见的类

常用的线程池接口和类(所在包java.util.concurrent)。

Executor:线程池的顶级接口。

ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。

Executors工厂类:通过此类可以获得一个线程池。

方法名描述
newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,无上限。
public class TestThreadPool1 {
	public static void main(String[] args) {
		//1、获取线程池对象     线程池数量为3
		ExecutorService es = Executors.newFixedThreadPool(3);
		//2、通过线程池提交并执行线程任务    (线程会自动启动线程并执行线程任务(执行run方法))
		es.submit(new MyTask()); 
		es.submit(new MyTask()); 
		es.submit(new MyTask()); 
		es.submit(new MyTask()); 
		//3、关闭线程池   (当所有的线程任务都执行完成之后关闭)
		es.shutdown();
	}
}

class MyTask implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"--->"+i);
			
		}
	}
}
public class TestThreadPool2 {
	public static void main(String[] args) {
		//1、创建线程池对象     动态个数的线程池  (如果线程任务执行完,会执行下一个线程任务)
		ExecutorService es = Executors.newCachedThreadPool();
		//2、通过线程池对象启动并执行线程任务
		es.submit(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"--->"+i);
				}
			}
		});
		es.submit(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"--->"+i);
				}
			}
		});
		es.submit(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"--->"+i);
				}
			}
		});
		es.submit(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"--->"+i);
				}
			}
		});
		//3、关闭线程池
		es.shutdown();
	}
}

7.4 线程池 ThreadPoolExecutor类

线程池7大核心参数

  • corePoolSize
    • 线程池中常驻核心线程数
  • maximumPoolSize
    • 线程池能够容纳同时执行的最大线程数,此值必须大于1
  • keepAliveTime
    • 多余空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到剩下corePoolSize为止。
  • unit
    • keepAliveTime的单位
  • workQueue
    • 里面放了被提交但是尚未执行的任务
      • ①ArrayBlockingQueue
      • ②LinkedBlockingQuene
      • ③SynchronousQuene
      • ④PriorityBlockingQueue
  • threadFactory
    • 表示线程池中工作线程的线程工厂,用于创建线程
  • handler
    • 拒绝策略,当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时,对任务的拒绝方式。
      • ①CallerRunsPolicy
        • 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
      • ②AbortPolicy
        • 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
      • ③DiscardPolicy
        • 该策略下,直接丢弃任务,什么都不做。
      • ④DiscardOldestPolicy
        • 该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

7.5 Callable接口

  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
  • Callable具有泛型返回值、可以声明异常。

public interface Callable< V >{
public V call() throws Exception;
}

public class TestCallable1 {
	public static void main(String[] args) {
		//1、创建线程池对象
		ExecutorService es = Executors.newFixedThreadPool(2);
		//2、通过线程池提交线程并执行任务
		es.submit(new MyCallable());
		//3、关闭线程池
		es.shutdown();
	}
}
class MyCallable implements Callable{//此泛型规定了方法的返回值
	@Override
	public Object call() throws Exception {
		
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"--->"+i);
			
		}
		return null;
	} 
}

7.6 Future接口

  • Future接口表示将要执行完任务的结果。
  • get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。
public class TestCallable2 {
	/**
	 * Runnable接口和Callable接口的区别?
	 * 		1、这两个接口都可以当做线程任务提交并执行
	 *      2、Callable接口执行完线程任务之后有返回值,而Runnable接口没有返回值
	 *      3、Callable接口中的call方法已经抛出了异常,而Runnable接口不能抛出编译异常
	 *  Future接口:	
	 *  	用于接口Callable线程任务的返回值。
	 *      get()方法当线程任务执行完成之后才能获取返回值,这个方法是一个阻塞式的方法   
	 *      
	 *   随堂案例:
	 *   	使用两个线程,并发计算1-100的和,   一个线程计算1~50,另一个线程计算51~100, 最终汇总结果    
	 */
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//1、创建线程池对象
		ExecutorService es = Executors.newFixedThreadPool(2);
		//2、通过线程池提交线程并执行任务
		Future<String> future = es.submit(new MyCallable1());
		//获取线程任务的返回值
		System.out.println(future.get());
		System.out.println("哈哈哈哈哈哈");
		//3、关闭线程池
		es.shutdown();
	}
}


class MyCallable1 implements Callable<String>{
	@Override
	public String call() throws Exception {
		for (int i = 0; i < 10; i++) {
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName()+"--->"+i);
		}
		
		return "这是Callable线程任务的返回值";
	}
	
}

案例:计算1-1000结果,使用四个线程分别计算?即:第一个线程计算1-250 第二个 251~500 …

public class Test01 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//1、创建线程池
		ExecutorService es = Executors.newFixedThreadPool(2);
		//2、提交两个线程任务
		Future<Integer> f1 = es.submit(new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				int sum = 0;
				for (int i = 0; i < 51; i++) {
					sum = sum + i;
				}
				return sum;
			}
		});
		Future<Integer> f2 = es.submit(new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				int sum = 0;
				for (int i = 51; i < 101; i++) {
					sum += i;
				}
				return sum;
			}
		});
		
		System.out.println(f1.get() + f2.get());
		//3、关闭线程池
		es.shutdown();
	}
}

八、 Lock锁

  • JDK5加入,与synchronized比较,显示定义,结构更灵活。
  • 提供更多实用性方法,功能更强大、性能更优越。

8.1 Lock锁

ReentrantLock:

  • Lock接口的实现类,与synchronized一样具有互斥锁功能。
public class TestLock {
	public static void main(String[] args) {
		//1、创建线程池对象
		ExecutorService es = Executors.newFixedThreadPool(4);
		//2、通过线程池提交线程任务
		TicketTask task = new TicketTask();
		es.submit(task);
		es.submit(task);
		es.submit(task);
		es.submit(task);
		//3、关闭线程池
		es.shutdown();
	}
}
class TicketTask implements Runnable{
	static int ticket = 30;
	//jdk1.5之后加入
	Lock lock = new ReentrantLock();//重入锁
	Object obj = new Object();
	@Override
	public void run() {
		while(true) {
			try {
				lock.lock();    //上锁
				if(ticket < 0) {
					break;
				}
				//System.out.println(10/0);
				try {
					Thread.sleep(30);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			System.out.println(Thread.currentThread().getName()+"--->卖出了"+ticket--+"号票");
			}finally {
				lock.unlock();  //释放锁
			}
		}
	}
}

8.2 读写锁

ReentrantReadWriteLock:

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
  • 支持多次分配读锁,使多个读操作可以并发执行。

互斥规则:

  • 写-写:互斥,阻塞。
  • 读-写:互斥,读阻塞写、写阻塞读。
  • 读-读:不互斥、不阻塞。
  • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
public class TestReadWriteLock {
	static User user = new User();
	public static void main(String[] args) {
		//创建一个大小为10的线程池
		ExecutorService es = Executors.newFixedThreadPool(10);
		
		long start = System.currentTimeMillis();
		
		//8个线程读  2个线程写
		for (int i = 0; i < 2; i++) {
			es.submit(new WriteThread());
		}
		for (int i = 0; i < 8; i++) {
			es.submit(new ReadThread());
		}
		es.shutdown();
		//判断线程池中的任务是否执行结束,如果结束了返回true
		//System.out.println(es.isTerminated());
		//代码空转
		while(true) {
			if(es.isTerminated() == true) {
				break;
			}
		}
		long end = System.currentTimeMillis();
		System.out.println("耗时为:"+(end-start));
	}
	
}

class ReadThread implements Runnable{
	@Override
	public void run() {
		System.out.println(TestReadWriteLock.user.getName());
	}
}
class WriteThread implements Runnable{
	@Override
	public void run() {
		TestReadWriteLock.user.setName("cxk");
	}
}

class User{
	String name;
	//创建读写锁对象
	ReadWriteLock rwl = new ReentrantReadWriteLock();
	//读锁
	Lock readLock = rwl.readLock();
	//写锁
	Lock writeLock = rwl.writeLock();
	
	public void setName(String name) {
		try {
			writeLock.lock();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			this.name = name;
		} finally {
			writeLock.unlock();
		}
	}
	public String getName() {
		try {
			readLock.lock();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return name;
		}finally {
			readLock.unlock();
		}
	}
}

8.3 重入锁

重入锁:

  • 重入锁也叫作递归锁,指的是同一个线程外层函数获取到一把锁后,内层函数同样具有这把锁的控制权限

  • synchronized和Lock锁都可以实现锁的重入

public class TestReentrantLock {	
	public static void main(String[] args) {
		//启动线程
		new Thread(new MyRunnable()).start();
	}
}
class MyRunnable implements Runnable{
	@Override
	public void run() {
		a();
	}
	//当前锁对象为this
	public synchronized void a() {
		System.out.println("a");
		b();
	}
	//当前锁对象为this
	public synchronized void b() {
		System.out.println("b");
		c();
	}
	public synchronized void c() {
		System.out.println("c");
	}
}

8.4 公平锁

公平锁和非公平锁

  • 非公平锁:优先使用上一个线程接着执行下面的线程任务
    • synchronized是非公平锁的实现,无法实现公平锁
    • lock锁默认是非公平锁,如果想要实现公平锁,那么需要在构造方法设置为true
  • 公平锁:让每个线程都公平去执行线程任务
    • lock锁可以实现公平锁
    • synchronized无法实现公平锁
//Lock锁实现公平锁 参数为true表示是公平锁,默认是false表示非公平锁
Lock lock = new ReentrantLock(true);

8.5 synchronized锁升级

锁的状态总共有四种

  • 无锁
    • 当锁对象被创建,还没有线程进入的时候,此时锁对象处于无锁状态
  • 偏向锁
    • 当有线程A进入同步代码并获得锁对象,此时会保存线程ID。以后此线程A进入到同步代码中则无需使用CAS加锁或者释放锁。只需要验证线程ID即可。
  • 轻量级锁(自旋锁)
    • 当前锁处于偏向锁,此时后线程B进入到同步代码。这是线程AB会使用CAS竞争,并升级为轻量级锁
  • 重量级锁
    • 如果线程没有获得轻量级锁,线程会通过CAS自旋获取锁对象,如果自旋次数大于阈值(10次),则升级为重量级锁
Java对象头(MarkWord)
在这里插入图片描述
锁升级过程
在这里插入图片描述

九、线程安全的集合

9.1 集合结构

在这里插入图片描述

9.2 通过Collections获取线程安全集合

Collections工具类中提供了多个可以获得线程安全集合的方法。

方法名
public static Collection synchronizedCollection(Collection c)
public static List synchronizedList(List list)
public static Set synchronizedSet(Set s)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
public static SortedSet synchronizedSortedSet(SortedSet s)
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

9.3 CopyOnWriteArrayList

  • 线程安全的ArrayList,加强版读写分离。
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁。
  • 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
  • 使用方式与ArrayList无异。
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();

9.4 CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现。
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组。
  • 如存在元素,则不添加(扔掉副本)。
CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>();

9.5 ConcurrentHashMap

JDK1.7实现

  • 初始容量默认为16段(Segment),使用分段锁设计。
  • 不对整个Map加锁,而是为每个Segment加锁。
  • 当多个对象存入同一个Segment时,才需要互斥。
  • 最理想状态为16个对象分别存入16个Segment,并行数量16。
  • 使用方式与HashMap无异。

JDK1.8实现

  • 内部使用CAS交换算法+synchronized实现多线程并发安全
  • CAS有三个值
    • X:要更新的值
    • Y:预期值
    • Z:新值
    • 当X==Y的时候才会去修改,当 X!=Y的时候则放弃修改
  • ABA问题
    • 当Y值经过多次修改之后,又被回到了原来的Y值。此时就是ABA问题
    • 使用版本号解决

ConcurrentHashMap与HashMap的比较 案例

public class Demo02 {
	public static void main(String[] args) {
		//HashMap<String,String> map = new HashMap<String, String>();
		ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>(23);
		ExecutorService es = Executors.newFixedThreadPool(10);
		for (int i = 0; i < 10; i++) {
			es.submit(new Runnable() {
				@Override
				public void run() {
					//向map集合中添加10个元素
					for (int j = 0; j < 10; j++) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						map.put("key"+j, "value"+j);
					}
				}
			});
		}
		es.shutdown();
		while(!es.isTerminated()) {}
		System.out.println(map);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值