进程和线程
1.什么进程
- 正在运行的程序,是系统进行资源分配的基本单位
- 目前操作系统都是支持多线程,可以同时执行多个进程,通过进程id(pid)区分
- 单核CPU在同一个时刻,只能运行一个进程;宏观并行,微观串行
2.什么线程
- 线程,又称为轻量级进程,进程中的一条执行路径,也是CPU的基本调度单位,一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程
3.进程和线程的区别
- 1.进程是操作系统资源分配的而基本单位,而线程是cpu的基本调度单位
- 2.一个程序运行后至少有一个进程
- 3.一个进程可以包含多个线程,但是至少需要有个线程,否则这个进程是没有意义的
- 4.进程间不能共享数据段地址,但同进程的线程之间可以
线程的组成和特点
1.线程的组成
- 任何一个线程都具有基本的组成部分:
- cpu时间片:操作系统(OS)会为每个线程分配执行时间
- 运行数据
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
- 线程的逻辑代码
2.线程的特点
- 1.线程抢占式执行
- 效率高
- 可防止单一线程长时间独占cpu
- 2.在单核cpu中,宏观同时执行,微观串行就是按顺序执行
线程的创建
1.继承Tread类(重写run方法)
/**
* 线程类
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("子线程-------"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread();
//启动线程,不能调用run方法
myThread.start();
//主线程执行
for (int i = 0; i <100 ; i++) {
System.out.println("主线程=========="+i);
}
}
}
1.1获取和修改线程名称
- 获取线程id和名称
- 1.在Thread的子类中调用this.getId()或this.getName()
- 2.使用Thread.currentThread().getId()和Thread.currentThread().getName()
- 修改线程名称
- 1.调用线程对象的setName()方法
- 2.使用线程子类的构造方法赋值
- 第一种方式获取id和名称,局限性:要继承Thread类
/**
* 线程类
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("线程id:"+this.getId()+"线程名字:"+this.getName()+"子线程-------"+i);
}
}
}
- 第二种方式获取id和名称
/**
* 线程类
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
// 第一种方法获取id和名称
// System.out.println("线程id:"+this.getId()+"线程名字:"+this.getName()+"子线程-------"+i);
// 第二种方法获取id和名称
System.out.println("线程id:"+Thread.currentThread().getId()+"线程名字:0"+Thread.currentThread().getName()+"子线程-----------"+i);
}
}
}
- 修改线程名称
/**
* 线程类
*/
public class MyThread extends Thread{
public MyThread(){
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
// 第一种方法获取id和名称
// System.out.println("线程id:"+this.getId()+"线程名字:"+this.getName()+"子线程-------"+i);
// 第二种方法获取id和名称
System.out.println("线程id:"+Thread.currentThread().getId()+"线程名字:0"+Thread.currentThread().getName()+"子线程-----------"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建线程对象
MyThread myThread = new MyThread("我的子线程1");
//启动线程,不能调用run方法
myThread.start();
MyThread myThread1 = new MyThread();
myThread1.setName("我的子线程2");
myThread1.start();
//主线程执行
for (int i = 0; i <1000 ; i++) {
System.out.println("主线程=========="+i);
}
}
}
- 四个窗口卖票案例
public class TestWin {
public static void main(String[] args) {
TicketWin w1 = new TicketWin("窗口1");
w1.start();
TicketWin w2 = new TicketWin("窗口2");
w2.start();
TicketWin w3 = new TicketWin("窗口3");
w3.start();
TicketWin w4 = new TicketWin("窗口4");
w4.start();
}
}
public class TicketWin extends Thread{
public TicketWin(){
}
public TicketWin(String name){
super(name);
}
private int ticket=100;
@Override
public void run() {
while (true) {
if (ticket <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}
}
}
2.实现Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+".........."+i);
}
}
}
public class TestRunnable {
public static void main(String[] args) {
//1.创建MyRunnable对象,表示线程要执行的功能
MyRunnable myRunnable = new MyRunnable();
//2.创建线程对象
Thread thread = new Thread(myRunnable, "我的线程1");
//3.启动
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println("main.........."+i);
}
}
}
- 使用匿名内部类创建
public class TestRunnable {
public static void main(String[] args) {
// //1.创建MyRunnable对象,表示线程要执行的功能
// MyRunnable myRunnable = new MyRunnable();
// //2.创建线程对象
// Thread thread = new Thread(myRunnable, "我的线程1");
// //3.启动
// thread.start();
//
// for (int i = 0; i < 50; i++) {
// System.out.println("main.........."+i);
// }
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+".........."+i);
}
}
};
Thread thread = new Thread(runnable, "线程1");
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println("main.........."+i);
}
}
}
3.Callable接口
- jdk5.0加入,与Runnable接口相似,实现之后代表一个线程任务
- Callable具有泛型返回值,要声明异常
public class CallableTest {
public static void main(String[] args) throws Exception {
//创建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 = 0; i < 100; i++) {
sum+=i;
Thread.sleep(100);
}
return sum;
}
};
//把Callable对象转换成可执行对象
FutureTask<Integer> task=new FutureTask<>(callable);
//创建线程
Thread thread=new Thread(task);
//启动线程
thread.start();
//获取结果(等待call方法执行完毕才返回)
Integer sum = task.get();
System.out.println("结果是"+sum);
}
}
线程的状态
1.阻塞常用方法
- 休眠
- public static void sleep(long millis)
- 当前线程主动休眠 millis毫秒
public class SleepThread extends Thread{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"......."+i);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestSleep {
public static void main(String[] args) {
SleepThread s1 = new SleepThread();
SleepThread s2 = new SleepThread();
s1.start();
s2.start();
}
}
- 放弃
- public static void yield()
- 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
public class YieldThread extends Thread{
@Override
public void run() {
for (int i = 0; i <100; i++) {
System.out.println(Thread.currentThread().getName()+"......."+i);
yield();
}
}
}
public class TestYield {
public static void main(String[] args) {
YieldThread thread1 = new YieldThread();
YieldThread thread2 = new YieldThread();
thread1.start();
thread2.start();
}
}
- 加入
- public final void join()
- 允许其他线程加入到当前线程中
- 加入当前线程main,并阻塞当前线程,直到加入线程执行完毕
public class JoinThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 50 ; i++) {
System.out.println(Thread.currentThread().getName()+".........."+i);
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestJoin {
public static void main(String[] args) {
JoinThread j1 = new JoinThread();
j1.start();
try {
j1.join();//加入当前线程main,并阻塞当前线程,直到加入线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"........"+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 优先级
- 线程对象。setPriority()
- 线程优先级为1-10,默认为5,优先级越高,表示获得cpu机会越多
public class TestPriority {
public static void main(String[] args) {
PriorityThread p1 = new PriorityThread();
PriorityThread p2 = new PriorityThread();
PriorityThread p3 = new PriorityThread();
p1.setPriority(1);
p1.start();
p2.start();
p3.start();
}
}
public class PriorityThread extends Thread{
@Override
public void run() {
for (int i = 0; i <50 ; i++) {
System.out.println(Thread.currentThread().getName()+"==========="+i);
}
}
}
- 守护线程
- 线程对象.setDaemon(true);设置为守护线程
- 线程有两类:用户线程(前台线程),守护线程(后台线程)
- 如果程序中所有前台线程都执行完毕了,后台线程会自动结束
- 垃圾回收器线程属于守护线程
public class TestDeamon {
public static void main(String[] args) {
DeamonThread d1 = new DeamonThread();
d1.setDaemon(true);
d1.start();
for (int i = 0; i <10 ; i++) {
System.out.println("主线程----------"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class DeamonThread extends Thread{
@Override
public void run() {
for (int i = 0; i <50 ; i++) {
System.out.println(currentThread().getName()+"-----------"+i);
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的安全
- 多线程安全问题
- 当多线程并发访问临界资源时,如果破坏了原子操作,可能会造成数据不一致
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可被打乱或缺省
public class ThreadSafe {
private static int index=0;
public static void main(String[] args) throws InterruptedException {
String[] strings = new String[5];
Runnable runnableA = new Runnable() {
@Override
public void run() {
strings[index] = "hello";
index++;
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
strings[index] = "world";
index++;
}
};
Thread a = new Thread(runnableA, "A");
Thread b = new Thread(runnableB, "B");
a.start();
b.start();
a.join();
b.join();
System.out.println(Arrays.toString(strings));
}
}
1.同步代码块
- sychronized(临界资源对象){//对临界资源对象加锁}
//代码(原子操作)
}
注:每个对象都有一个互斥锁标记,用来分配给线程的
只有拥有对象互斥锁标记的线程,才进入对该对象加锁的同步代码块
线程退出同步代码块时,会释放相应的互斥锁标记
public class ThreadSafe {
private static int index=0;
public static void main(String[] args) throws InterruptedException {
String[] s = new String[5];
Runnable runnableA = new Runnable() {
@Override
public void run() {
synchronized (s){
s[index] = "hello";
index++;
}
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
synchronized (s){
s[index] = "world";
index++;
}
}
};
Thread a = new Thread(runnableA, "A");
Thread b = new Thread(runnableB, "B");
a.start();
b.start();
a.join();
b.join();
System.out.println(Arrays.toString(s));
}
}
2.同步方法
synchronized返回值类型 方法名称(参数列表){//对当前对象(this)加锁
//代码(原子操作)
}
public class ThreadSafe {
private static int index=0;
public static void main(String[] args) throws InterruptedException {
String[] s = new String[5];
/* Runnable runnableA = new Runnable() {
@Override
public void run() {
synchronized (s){
s[index] = "hello";
index++;
}
}
};*/
Runnable runnableA = new Runnable() {
@Override
public synchronized void run() {//锁 this
s[index] = "hello";
index++;
}
};
Runnable runnableB = new Runnable() {
@Override
public synchronized static void run() {//静态方法 锁Ticket.class
s[index] = "world";
index++;
}
};
Thread a = new Thread(runnableA, "A");
Thread b = new Thread(runnableB, "B");
a.start();
b.start();
a.join();
b.join();
System.out.println(Arrays.toString(s));
}
}
3.同步规则
- 注意:
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁的标记
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用
- 已知jdk中线程安全的类
- StringBuffer
- Vector
- Hashtable
- 以上类中公开方法,均为synchronized修饰的同步方法
4.经典问题
- 死锁
- 当第一个线程拥有A对象标记,并等待B对象锁标记,同时第二个线程拥有B对象锁的标记,并等待A对象标记时,产生死锁
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能会产生死锁
5.线程通信
- 等待
- public final void wait()
- public final void wait(long timeout)
- 必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记,同时此线程阻塞在o的等待队列中,释放锁,进入等待队列
- 通知
- public final void notify()
- public final void notifyAll()
public class BankCard {
//余额
private double money;
//标记
private boolean flag=false;//true 代表有钱可以取钱,false没钱可以存钱
//存钱
public synchronized void save(double m){//this
if (flag){
try {
this.wait();//进入等待队列,释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money+=m;
System.out.println(Thread.currentThread().getName()+"存了"+m+" 金额是"+money);
//修改标记
flag=true;
//唤醒取钱线程
this.notify();
}
//取钱
public synchronized void take(double m){
if (!flag){
try {
this.wait();//进入等待队列,释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money-=m;
System.out.println(Thread.currentThread().getName()+"取了"+m+" 金额是"+money);
//修改标记
flag=false;
//唤醒存钱线程
this.notify();
}
}
public class SubMoney implements Runnable{
private BankCard bankCard;
public SubMoney(BankCard bankCard) {
this.bankCard = bankCard;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.take(1000);
}
}
}
public class AddMoney implements Runnable{
private BankCard bankCard;
public AddMoney(BankCard bandCard){
this.bankCard=bandCard;
}
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
bankCard.save(1000);
}
}
}
线程池
1.线程池的概念
- 问题:线程是宝贵的内存资源,单个线程约占1mb空间,过多的分配线程易造成内存溢出;频繁的创建及销毁线程会增加虚拟机回收的频率,资源开销,造成程序性能下降
- 线程池
- 线程容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象
- 避免频繁的创建和销毁
2.线程池原理
- 将任务提交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程
3.创建线程池
- 常用的线程池接口和类(所在包java.util.concurrent)
- Executor:线程池的顶级接口
- ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码
- Executors工厂类:通过此类可以获得一个线程池
- 通过newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量
- 通过newCachedThreadPool()获取动态数量的线程池,如不够则创建新的,没有上限
/**
* 线程池创建
* Executor 线程池的根接口 executor()
* ExecutorService 包含管理线程池的一些方法
* ThreadPoolExecutor
* ScheduledThreadPoolExecutor
* Executors 创建线程的工具类
* 1)创建固定线程的个数的线程池
* 2)创建缓存线程池,由任务的多少所决定
* 3)创建单线程池
* 4)创建调度线程池 调度:周期,定时执行
*/
public class Demo01 {
public static void main(String[] args) {
//1)创建固定线程的个数的线程池
ExecutorService es = Executors.newFixedThreadPool(4);
//1.1动态创建线程池.由任务个数所决定
Executors.newCachedThreadPool();
//1.2创建单线程线程池
Executors.newSingleThreadExecutor();
//1.3创建调度线程池
Executors.newScheduledThreadPool();
//2)创建任务
Runnable runnable = new Runnable() {
private Integer 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();
}
}
3.1Callable结合线程池使用
public class Demo02 {
public static void main(String[] args) throws Exception{
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(1);
//提交任务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 = 0; i < 100; i++) {
sum += i;
Thread.sleep(10);
}
return sum;
}
});
//获取任务结果
System.out.println(future.get());
//关闭线程池
es.shutdown();
}
}
4.Future接口
- Future:表示将要完成任务的结果
- 表示ExecutorService.submit()所返回的状态结果,就是call()的返回值
- 方法:V.get()以阻塞形式等待Future中的异步处理结果也就是call()的返回值
- 需求:使用两个线程,并发计算1-50,51-100的和,在进行汇总统计
public class Demo03 {
public static void main(String[] args) throws Exception{
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//提交任务
Future<Integer> future1 = 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 <=50 ; i++) {
sum+=i;
}
return sum;
}
});
//获取任务结果
Integer sum1 = future1.get();
//提交任务
Future<Integer> future2 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算");
int sum=0;
for (int i = 51; i <=100 ; i++) {
sum+=i;
}
return sum;
}
});
//获取任务结果
Integer sum2 = future2.get();
int sum = sum1 + sum2;
System.out.println("两个线程的总和是"+sum);
//关闭线程池
es.shutdown();
}
}
线程的同步和异步
1.线程的同步
- 同步:
- 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能够继续
- 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能够继续
2.线程的异步
- 异步:
- 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知后立刻返回。二者竞争时间片,并发执行
- 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知后立刻返回。二者竞争时间片,并发执行
Lock接口
- jdk5,与synchronized比较,显示定义,结构灵活
- 提供更多实用性方法,功能强大,性能更加优越
- 常用方法:
- void lock() //获取锁,如锁被占用,则等待
- boolean tryLock() //尝试获取锁(成功则返回true,否则false,不阻塞)
- void unlock()//释放锁
1.重入锁
- ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能
public class TestMylist {
public static void main(String[] args) throws Exception {
MyList myList=new MyList();
Runnable r1 = new Runnable() {
@Override
public void run() {
myList.add("hello");
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
myList.add("world");
}
};
Thread t1= new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Arrays.toString(myList.getStr()));
}
}
public class MyList {
//创建锁
private Lock lock=new ReentrantLock();
private String[] str={"A","B","","",""};
private int count=2;
public void add(String value){
lock.lock();
try {
str[count]=value;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
} finally {
lock.unlock();
}
}
public String[] getStr(){
return str;
}
}
2.读写锁
- ReentrantReadWriteLock
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁
- 支持多次分配读锁,使多个读操作可以并发执行
- 互斥规则
- 写-写 :互斥,阻塞
- 读-写:互斥,读阻塞写,写阻塞读
- 读-读:不互斥,不阻塞
- 在读操作远远高于写操作的环境中,可保障线程安全的情况下,提高虚拟性效率
public class TestReadWrite {
public static void main(String[] args) {
ReadWriteDemo readWriteDemo = new ReadWriteDemo();
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(20);
Long start=System.currentTimeMillis();
for (int i = 0; i <2 ; i++) {
es.submit(new Runnable() {
@Override
public void run() {
readWriteDemo.setValue("张三:"+new Random().nextInt(100));
}
});
}
for (int i = 0; i <18 ; i++) {
es.submit(new Runnable() {
@Override
public void run() {
readWriteDemo.getValue();
}
});
}
//关闭线程池
es.shutdown();
while (!es.isTerminated()){
}
long end=System.currentTimeMillis();
System.out.println("用时:"+(end-start));
}
}
public class ReadWriteDemo {
//创建读写锁
private ReentrantReadWriteLock rr1=new ReentrantReadWriteLock();
//获取读锁
private ReentrantReadWriteLock.ReadLock readLock=rr1.readLock();
//获取写锁
private ReentrantReadWriteLock.WriteLock writeLock= rr1.writeLock();
private String value;
public String getValue() {
readLock.lock();
try{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("读取"+value);
return value;
}finally {
readLock.unlock();
}
}
public void setValue(String value) {
writeLock.lock();
try{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("写入"+value);
this.value = value;
}finally {
writeLock.unlock();
}
}
}
线程安全的集合
- Collection体系集合中除Vector以外的线程安全集合
- Map安全集合体系
1.CopyOnWriteArrayList
- 线程安全的ArrayList,加强版的读写分离
- 写有锁,读无锁,读写之间不阻塞,优于读写锁
- 写入时,先copy一份容器副本,再添加元素,最后替换引用
- 使用方式与ArrayList无异
public class Demo01 {
public static void main(String[] args) {
//创建CopyOnWriteArrayList对象
CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(5);
//提交任务
for (int i = 0; i <5; i++) {
es.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
strings.add(Thread.currentThread().getName()+"======"+new Random().nextInt(1000));
}
}
});
}
//关闭线程池
es.shutdown();
while (!es.isTerminated()){}
System.out.println("元素个数为:"+strings.size());
for (String string : strings) {
System.out.println(string);
}
}
}
2.CopyWriteArraySet
- 线程安全的set,底层使用CopyWriteArrayList实现,有序
- 唯一不同之处,add添加元素是调用addIfabsent()添加元素,会遍历数组
- 如存在元素,则不添加(扔掉副本)
public class Demo02 {
public static void main(String[] args) {
//创建CopyWriteArraySet对象
CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet();
set.add("北京");
set.add("上海");
set.add("广州");
set.add("北京");//添加不了
System.out.println(set.toString());
}
}
3.Queue接口(队列)
- Collection的子接口,表示队列FIFO,先进先出
- 常用方法:
- 抛出异常:
- boolean add(E e)//顺序添加一个元素(达到上限后,再添加则会抛出异常)
- E remove() //获得第一个元素并移除(如果队列没有元素,则抛出异常)
- E element()//获得第一个元素但不移除(如果队列没有元素,则抛出异常)
- 返回特殊值:推荐使用
- boolean offer(E e)//顺序添加一个元素(达到上限后,再添加则会返回false)
- E poll() //获得第一个元素并移除(如果队列没有元素,则返回null)
- E peek()//获得第一个元素但不移除(如果队列没有元素,则返回null)
- 抛出异常:
/**
* Queue接口使用,LinkedList线程不安全
*/
public class Demo03 {
public static void main(String[] args) {
//创建队列
Queue<String> queue=new LinkedList<>();
//入队
queue.offer("苹果");
queue.offer("香蕉");
queue.offer("梨子");
queue.offer("葡萄");
queue.offer("苹果");
System.out.println(queue.peek());
System.out.println(queue.size());
//出队
for (int i = 0; i < 5; i++) {
queue.poll();
}
System.out.println(queue.size());
}
}
3.1ConcurrentLinkedQueue
- Queue的实现类,线程安全,可高效读写的队列,高并发下最好的队列
- 无锁,CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
- V:要更新的变量,E:预期值,N:新值
- 只有V==E时才V=N;否则表示已更新过,取消当前的操作
public class Demo04 {
public static void main(String[] args) throws Exception{
//创建安全队列
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
//入队操作
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
queue.offer(i);
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 5; i < 10; i++) {
queue.offer(i);
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
//出队操作
int size=queue.size();
for (int i = 0; i < size; i++) {
System.out.println(queue.poll());
}
}
}
3.2BlockingQueque接口(阻塞队列)
- Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法
- 方法
- void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待
- E take()//获取并移除此队列头部元素,如果没有可用元素,则等待
3.2.1ArrayBlockingQueue
- 数组结构实现,有界队列(手工固定上限)
public class Demo05 {
public static void main(String[] args) throws Exception{
//创建ArrayBlockingQueue
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
//添加元素
queue.put("aaa");
queue.put("bbb");
queue.put("ccc");
queue.put("ddd");
queue.put("eee");
System.out.println(queue.size());
//删除元素
queue.take();
queue.put("fff");
System.out.println(queue.size());
}
}
3.2.2LinkedBlockingQueue
- 链表结构实现,有界队列(默认上限Integer.Max_VALUE)
4.ConcurrentHashMap
- jdk1.7
- 初始容量默认为16段(Segment),使用分段式锁设计
- 不对整个Map加锁,而是为每个Segment加锁
- 当多个对象存入同一个Segment时,才需互斥
- 最理想状态为16对象分别存入16g个Segement,并行数量16
- 使用方式与HashMap无异
- jdk1.8
- 使用CAS无锁算法
public class Demo06 {
public static void main(String[] args) {
//创建集合
ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<>();
//使用多线程添加数据
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();
}
}
}