1、什么是进程
应用程序
进程:正在运行的程序。
2、什么是线程
- 线程:又称 轻量级进程(Light Weight Process)。
- 进程中的一条执行路径,也是CPU的基本调度单位。
- 一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。
3、进程和线程的区别
- 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
- 一个程序运行后至少有一个进程。
- 一个进程可以包含多个线程,但是至少需要有一个线程否则这个进程是没有意义。
- 京城键不能共享数据段地址,但同进程的线程之间可以。
4、线程的组成
任何一个线程都具有基本的组成部分:
- CPU时间片:操作系统(OS)会为每个线程分配执行时间。
- 运行数据:
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
- 线程的逻辑代码
5、线程的特点
1、线程抢占式执行
- 效率高
- 可防止单一线程产时间独占CPU
2、在单核CPU中,宏观上同时执行,微观上顺序执行。
6、创建线程的三种方式
1、继承 Thread类,重写run 方法
package com.kingtl.ThreadDemo1;
/**
* 线程类
* @author kingtl
*/
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程..........."+ i);
}
}
}
package com.kingtl.ThreadDemo1;
public class TestThread {
public static void main(String[] args) {
// 1、 创建线程对象
MyThread myThread = new MyThread();
// 2、启动线程
myThread.start();
// 主线程执行
for (int i = 0; i < 50; i++) {
System.out.println("主线程================" + i);
}
}
}
2、实现 Runnable 接口
3、实现 Callable 接口
7、获取和修改线程名称
- 获取线程ID和线程名称
- 在Thread的子类中调用 this,getId() 或 this.getName()
- 使用 Thread.currentThread().getId() 和 Thread.currentThread().getName()
package com.kingtl.ThreadDemo1;
/**
* 线程类
* @author kingtl
*/
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 第一种 方式
// this.getId() 获取线程id
// this.getName() 获取线程的名称
//System.out.println("线程id:"+this.getId()+"线程名称:"+ this.getName()+"子线程........"+i);
// 第二种方式 Thread.currentThread() 获取当前线程
System.out.println("线程id" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName()+"子线程........"+i);
}
}
}
- 修改线程名称
- 调用线程对象的setName() 方法
- 使用线程子类的构造方法赋值。
package com.kingtl.ThreadDemo1;
/**
* 线程类
* @author kingtl
*/
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 第一种 方式
// this.getId() 获取线程id
// this.getName() 获取线程的名称
//System.out.println("线程id:"+this.getId()+"线程名称:"+ this.getName()+"子线程........"+i);
// 第二种方式 Thread.currentThread() 获取当前线程
System.out.println("线程id " + Thread.currentThread().getId() + " 线程名称:" + Thread.currentThread().getName()+" 子线程........"+i);
}
}
}
package com.kingtl.ThreadDemo1;
public class TestThread {
public static void main(String[] args) {
// 1、 创建第一个线程对象
MyThread myThread = new MyThread("我的子线程1");
// 2、启动线程
// 修改线程名称
//myThread.setName("我的子线程1");
myThread.start();
// 1、 创建第一个线程对象
MyThread myThread2 = new MyThread("我的子线程2");
//myThread2.setName("我的子线程2");
myThread2.start();
// 主线程执行
for (int i = 0; i < 50; i++) {
System.out.println("主线程================" + i);
}
}
}
8、线程的状态(基本)
NEW, //初始状态
RUNNABLE, //就绪 运行 状态
BLOCKED, // 阻塞状态
WAITING, // 无限时等待状态
TIMED_WAITING, // 限时等待状态
TERMINATED; // 终止状态
初始状态 New
线程对象被创建,即为初始状态。值在堆中开辟内存,与常规对象无异。
RUNNABLE
就绪状态 Ready
调用start() 之后,进入就绪状态。等待OS选中,并分配时间片。
运行状态 Running
获得时间片之后,进入运行状态,如果时间片到期,则回到就绪状态。
终止状态 Terminated
主线程main() 或 独立线程run() 结束,进入终止状态,并释放持有的时间片。
限时等待 Timed Waiting
sleep() 到期,则回到就绪状态。
无限期等待 Waiting
Join() 加入线程执行完毕,阻塞的线程就可以继续执行。
阻塞状态 Blocked
synchronized
9、常见方法
休眠
// 当前线程主动休眠 millis 毫秒
public static void sleep(long millis)
放弃
// 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
public static void yield()
加入
// 允许其他线程加入到当前线程中, 加入当前线程,并阻塞当前线程,直到加入线程执行完毕!当前线程才会继续执行!
public final void join()
优先级
// 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
线程对象.setPriority()
守护线程
线程对象.setDaemon(true); // 设置为守护线程
// 线程有两类:用户线程(前台线程)、守护线程(后台线程)
// 如果程序中 所有前台线程都执行完毕了,后台线程会自动结束。
// 垃圾回收器线程属于守护线程。
10、线程安全
10.1 线程安全问题
- 当多线程并发访问临界资源(共享资源)时,如果破话原子操作,可能会造成数据不一致。
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
10.2 同步方式(1)
- 同步代码块
synchronized(临界资源对象) { // 对临界资源对象加锁
//代码(原子操作)
}
注:
- 每一个对象都有一个互斥锁标记,用来分配给线程的。
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
- 线程退出同步代码块时,会释放响应的互斥锁标记。
10.3 同步方式(2)
- 同步方法:
synchronized 返回值类型 方法名(形参列表0){ // 对当前对象(this) 加锁
// 代码(原子操作)
}
注:
- 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
- 线程退出同步方法时,会释放响应的互斥锁标记。
10.4 同步规则
- 注意
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
- 已知JDK中线程安全的类:
- StringBuffer
- Vector
- Hashtable
- 以上类中的公开方法,均为 synchronized 修饰的同步方法。
11、死锁
- 死锁
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
12、线程通信
等待
public final void wait()
public final void wate(long timeout)
// 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的队列中。释放鄋,进入等待队列
通信
public final void notify()
public final void notifyAll()
13、生产者和消费者的问题
- 若干个生产者在生产产品,这些产品将提供给若干消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中去取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
13、线程池
线程池的概念
- 线程容器,可设定线程分配的数量上限。
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
- 避免频繁的创建和销毁。
创建线程池
常用的线程池接口和类(所在包java.util.concurrent)JUC
- Executor:线程池的顶级接口。
- ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
- Executors工具类:通过此类可以获得一个线程池。
- 通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
- 通过 newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,没有上线。
package com.kingtl.JUCDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*线程池的创建
* Executor:线程池的根接口,execute()
* ExecutorService:包含管理线程池的一些方法,submit shutdown
* ThreadPoolExecutor
* ScheduleThreadPoolExector
* Executors:创建线程池的工具类
* (1)创建固定线程个数线程池
* (2)创建缓存线程池,由任务的多少决定
* (3)创建单线程
* (4)创建调度线程 调度:周期、定期执行
*/
public class Demo01 {
public static void main(String[] args) {
// 1.1 创建固定线程个数的线程池
//ExecutorService es = Executors.newFixedThreadPool(4);
// 1.2 创建缓存线程池,线程个数由任务个数决定
ExecutorService es = Executors.newCachedThreadPool();
// 1.3 创建单线程线程池
//Executors.newSingleThreadExecutor();
// 1.4 创建调度线程池 调度:周期、定时执行
//Executors.newScheduledThreadPool(corePoolSize)
//2、提交任务
Runnable runnable = new Runnable() {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票");
ticket--;
}
}
};
//3、提交任务
for (int i = 0; i < 4; i++) {
es.submit(runnable);
}
// 4、关闭线程池
es.shutdown(); //等待所有任务执行完毕,然后关闭线程池,不接受新任务。
}
}
14、Callable接口
Callable接口的使用
package com.kingtl.JUCDemo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Callable接口的使用
* Callable 和 Runnable 接口的区别
* (1)Callable接口中call方法有返回值,Runnable接口run方法没有返回值
* (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
*/
public class Demo02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 功能需求:使用 Callable 实现 1-100 的和
// 1、创建 Callable对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算");
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
};
// 2、把 Callable对象 转成可执行任务
FutureTask<Integer> task = new FutureTask<>(callable);
// 3、 创建线程
Thread thread = new Thread(task);
// 4、启动线程
thread.start();
// 5、获取结果(等待call执行完毕,才会返回)
Integer sum = task.get();
System.out.println("结果是:"+sum);
}
}
Callable 和 Runnable 接口的区别
(1) Callable接口中call方法有返回值,Runnable接口run方法没有返回值
(2) Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
Callable结合线程池使用
package com.kingtl.JUCDemo;
import java.util.concurrent.*;
/**
* 使用线程池计算1-100的和
*
*/
public class Demo03 {
public static void main(String[] args) throws Exception {
// 1、创建线程池
ExecutorService es = Executors.newFixedThreadPool(1);
// 2、提交任务 Future 表示 将要执行完任务的结果
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算");
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
});
// 3、获取结果
System.out.println(future.get());
// 4、关闭线程池
es.shutdown();
}
}
15、Future接口
- Futrue:表示将要完成任务的结果。
- 练习:使用两个线程,并发计算1-50、51-100 的和,再进行汇总统计。
package com.kingtl.JUCDemo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 使用两个线程,并发计算1-50、51-100 的和,再进行汇总统计。
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
// 1、创建线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 2、提交任务
Future<Integer> future1 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
System.out.println("1-50 计算结束!");
return sum;
}
});
Future<Integer> future2 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 51; i <= 100; i++) {
sum += i;
}
System.out.println("51-100 计算结束!");
return sum;
}
});
// 3、获取结果
int sum = future1.get() + future2.get();
System.out.println("结果是:" + sum);
// 4、关闭线程池
es.shutdown();
}
}
- 表示
ExecutorService.submit()
锁返回的状态结果,就是call()
的返回值。 - 方法:V get() 以阻塞形式等待 Future 中的异步处理结果(call() 的返回值)
16、线程的同步和异步
线程的同步
- 形容一次方法调用,同步一旦开始,调用者必须
等待
该方法返回,才能继续。
- 单条执行路径
线程的异步
- 形容一次方法调用,异步一旦开启,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。
- 多条执行路径
17、Lock 接口
- JDK5加入,与 synchronized 比较,显示定义,结构更灵活。
- 提供更多实用性方法,功能更强大、性能更优越。
- 常用方法
// 获取锁,如锁被占用,则等待。
void lock()
// 尝试获取锁(成功返回 true。失败返回 false, 不阻塞)
boolean tryLock()
// 释放锁
void unlock()
重入锁
- ReentrantLock:Lock接口的实现类,与synchronized 一样具有互斥锁功能。
package com.kingtl.Lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
private int ticket = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+ "卖了第"+ticket+"张票");
ticket--;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
package com.kingtl.Lock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
ExecutorService es = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
es.submit(ticket);
}
es.shutdown();
}
}
读写锁
- ReentrantReadWriteLock
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
- 支持多次分配读锁,是多个读操作可以并发执行。
- 互斥规则
- 写-写:互斥,阻塞。
- 读-写:互斥,读阻塞写、写阻塞读。
- 读-读:不互斥、不阻塞。
- 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
ReentrantReadWriteLock
package com.kingtl.Lock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
public static void main(String[] args) {
final MyClass mc = new MyClass();
Runnable task1 = new Runnable() {
@Override
public void run() {
try {
mc.setValue(1);
} catch (Exception e) {
e.printStackTrace();
}
}
};
Runnable task2 = new Runnable() {
@Override
public void run() {
try {
mc.getValue();
} catch (Exception e) {
e.printStackTrace();
}
}
};
ExecutorService es = Executors.newFixedThreadPool(20);
long startTime = System.currentTimeMillis();
for (int i = 0; i < 2; i++) {
es.submit(task1); // 提交两次写任务
}
for (int i = 0; i < 18; i++) {
es.submit(task2); // 提交18次 读任务
}
es.shutdown();
while (!es.isTerminated()) {} //如果线程未全部结束,则空转等待
System.out.println(System.currentTimeMillis() - startTime);
}
}
class MyClass{
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); // 获得读锁
ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); // 获得写锁
private int value;
//读 方法
public int getValue() throws Exception{
readLock.lock();
try {
Thread.sleep(1000); //休眠 1秒
return value;
} finally {
readLock.unlock(); // 释放读锁
}
}
// 写 方法
public void setValue(int value) throws Exception{
writeLock.lock();
try {
Thread.sleep(1000);
this.value = value;
} finally {
writeLock.unlock();
}
}
}
18、线程安全的集合
Collection 体系集合
除 Vector以外的线程安全集合。
Map安全集合体系
Collections 中的工具方法
- Collections工具类中提供了多个可以获得线程安全集合的方法
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
public static <T> List<T> snchronizedList(List<T> list)
public static <T> Set<T> synchronizedSet(Set<T> s)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
- JDK1.2提供,接口统一、维护性高,但是性能没有提升,均以 synchronized实现!
CopyOnWriteArrayList
- 线程安全的ArrayList,加强版的读写分离。
- 写有锁,读无锁,读写之间不阻塞,优于读写锁。
- 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
- 使用方式与 ArrayList 无异。
package com.kingtl.Lock;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 使用多线程操作 CopyOnWriteArrayList
*/
public class Demo02 {
public static void main(String[] args) {
// 1、创建集合
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 2、使用多线程
ExecutorService es = Executors.newFixedThreadPool(5);
// 3、提交任务
for (int i = 0; i < 5; i++) {
es.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
list.add(Thread.currentThread().getName()+"...."+new Random().nextInt(1000));
}
}
});
}
// 4、关闭线程池
es.shutdown();
while (!es.isTerminated()) {}
// 5、打印结果
System.out.println("元素个数:" + list.size());
for (String s : list) {
System.out.println(s);
}
}
}
CopyOnWriteArraySet
- 线程安全的 Set,底层使用
CopyOnWriteArrayList
实现。 - 唯一不同在于,使用
addIfAbsent()
添加元素,会遍历数组。 - 如 存在元素,则不添加(扔掉副本)。
19、Queue 接口(队列)
-
Collection 的子接口,表示队列 FIFO(First In First Out) 先进先出
-
常用方法:
-
抛出异常:
// 顺序添加一个元素 (到达上限后,再添加则会抛出异常) boolean add(E e) // 获得第一个元素并移除 (如果队列没有元素时,则抛出异常) E remove() // 获得第一个元素但不移除 (如果队列没有元素时,则抛出异常) E element()
-
返回特殊值:
推荐使用
// 顺序添加一个元素(到达上限后,再添加则会返回 false) boolean offer(E e) // 获得第一个元素并移除(如果队列没有元素时,则返回null) E poll() // 获得第一个元素但不移除(如果队列没有元素时,则返回 null) E peek()
package com.kingtl.Queue; import java.util.LinkedList; import java.util.Queue; public class Demo01 { public static void main(String[] args) { // 1、创建队列 LinkedList只能单线程 Queue<String> queue = new LinkedList<>(); // 2、入队 queue.offer("苹果"); queue.offer("橘子"); queue.offer("葡萄"); queue.offer("西瓜"); queue.offer("榴莲"); // 3、出队 System.out.println(queue.peek()); System.out.println("=================="); System.out.println("元素个数:" + queue.size()); int size = queue.size(); for (int i = 0; i < size; i++) { System.out.println(queue.poll()); } System.out.println("出队结束!"+ queue.size()); } }
-
ConcurrentLinkedQueue
- 线程安全、可高效读写的队列,高并发下性能最好的队列。
- 无锁、CAS比较交换算法,修改的方法包含是三个核心参数(V,E,N)
- V:要更新的变量 、E:预期值、N:新值
- 只要当 V==E时,V=N;否则表示已被更新过,则取消当前操作。
package com.kingtl.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* ConcurrentLinkedQueue
*/
public class Demo02 {
public static void main(String[] args) throws Exception {
// 1、创建一个安全队列
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
// 2、入队
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
queue.offer(i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 6; i <= 10; i++) {
queue.offer(i);
}
}
});
// 3、启动线程
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("--------出队---------");
// 4、出队操作
int size = queue.size();
for (int i = 0; i < size; i++) {
System.out.println(queue.poll());
}
}
}
BlockingQueue接口(阻塞队列)
-
Queue 的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
-
方法:
// 将指定的元素插入此队列中,如果没有可用空间,则等待。 void put(E e) // 获取并移除 此队列头部元素,如果没有可用元素,则等待 E take()
-
可用于解决生产者、消费者问题。
阻塞队列
- ArrayBlockingQueue
- 数组结构实现,有界队列。(手动固定上限)
- LinkedBlockingQueue
- 链表结构实现,有界队列。(默认上限Integer.MAX_VALUE)
package com.kingtl.Queue;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 阻塞队列的使用
* 案例1:创建一个有界队列,添加数据
* 案例2:使用阻塞队列实现生产者和消费者
*/
public class Demo03 {
public static void main(String[] args) throws Exception {
// 创建一个有界队列,添加数据
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(5);
// 添加元素
queue.put("aaa");
queue.put("bbb");
queue.put("ccc");
queue.put("ddd");
queue.put("eee");
// 删除元素
queue.take();
System.out.println("已经添加了 5个元素");
queue.put("xyz");
System.out.println("已经添加了6个元素");
System.out.println(queue.toString());
}
}
package com.kingtl.Queue;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 案例2:使用阻塞队列实现生产者和消费者
*/
public class Demo04 {
public static void main(String[] args) {
// 1、创建队列
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(6);
// 2、创建两个线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
queue.put(i);
System.out.println(Thread.currentThread().getName()+ "生产了第" + i +"号面包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"king");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
Integer num = queue.take();
System.out.println(Thread.currentThread().getName()+ "消费了第" + i +"号面包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"geek");
// 启动线程
t1.start();
t2.start();
}
}
ConcurrentHashMap
JDK1.8之前:
- 初始容量默认为16段(Segment),使用分段锁设计
- 不对整个Map加锁,而是为每个Segment 加锁。
- 当多个对象存入同一个 Segment 时,才需要互斥。
- 最理想状态为16个对象分别存入16个Segment,并行数量16.
- 使用方式与 HashMap无异。
JDK1.8之后
使用 CAS
package com.kingtl.Queue;
import java.util.concurrent.ConcurrentHashMap;
public class Demo05 {
public static void main(String[] args) {
// 1、创建集合
ConcurrentHashMap<String, String> hashmap = new ConcurrentHashMap<>();
// 2、使用多线程添加数据
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
hashmap.put(Thread.currentThread().getName()+"---"+j,j+"");
System.out.println(hashmap);
}
}
}).start();
}
}
}
20、总结
ExecutorService
线程池接口、Executors工厂。Callable
线程任务、Future 异步返回值。Lock
、ReentrantLock
重入锁、ReentrantReadWriteLock
读写锁。CopyOnWriteArrayList
线程安全的ArrayList。CopyOnWriteArraySet
线程安全的Set。ConcurrentLinkedQueue
线程安全的Queue。ArrayBlockingQueue
线程安全的阻塞Queue。(生产者、消费者)ConcurrentHashMap
线程安全的 HashMap。