JavaSE学习历程
第一章:Java初识
第二章:Java语言基础
第三章:选择结构与分支结构
第四章:循环结构
第五章:方法/函数
第六章:数组
第七章:面向对象
第八章:三大特性
第九章:三个修饰符
第十章:接口
第十一章:常用类
第十二章:集合
第十三章:异常
第十四章:多线程
1 什么是线程
1.1 进程
进程:程序是静止,只有真正运行的时的程序,才能被称为进程.
特点:
- 单核CPU在任何时间点上只能运行一个进程
- 宏观并行,微观串行.
1.2 线程
线程:又称轻量级进程(Light Weight Process)。
- 程序中的一个顺序控制流程,同时也是CPU的基本调度单位。
- 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。
比如:
- 迅雷是一个进程,当中的多个下载任务即是多个线程。
- Java虚拟机是一个进程,默认包含主线程(main),通过代码创建多个独立线程,与main并发执行。
1.3 进程和线程区别
- 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
- 一个程序运行后至少有一个进程。
- 一个进程可以包含多个线程,但是至少需要有一个线程。
- 进程间不能共享数据段地址,但同进程的线程之间可以。
1.4 线程的组成
任何一个线程都具有基本的组成部分:
- CPU时间片:操作系统(OS)会为每个线程分配执行时间。
- 运行数据:
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
- 线程的逻辑代码。
创建线程[重点]
2.1 创建线程的方式
Java中创建线程主要有两种方式:
- 继承Thread类
- 实现Runnable接口
2.2 继承Thread类
步骤:
- 编写类、继承Thread.
- 重写run()方法.
- 创建线程对象.
调用start()方法启动线程.
2.3 实现Runnable接口
步骤:
- 编写类实现Runnable接口,并重写run方法
- 创建Runnable实现类对象
- 创建线程对象,传递实现类对象
- 启动线程.
2.4 案例
/**
* @Classname MyThread
* @Description TODO:
* @Created by xx
*/
public class TestMyThread {
public static void main(String[] args) {
//继承Thread类
//1.创建线程对象
MyThread myThread = new MyThread();
//2.启动线程
myThread.start();
MyThread myThread2 = new MyThread();
myThread2.start();
//实现Runnable接口
//1.创建MyRunnable对象,表示线程要执行的功能
MyRunnable myRunnable = new MyRunnable();
//2.创建线程对象
Thread thread = new Thread(myRunnable);
//3.启动线程
thread.start();
MyRunnable myRunnable1 = new MyRunnable();
Thread thread1 = new Thread(myRunnable, "myRunnable线程1");
//3.启动线程
thread1.start();
for (int i = 0; i < 20; i++) {
System.out.println("线程id:" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName() + "主线程" + i);
}
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
// System.out.println("子线程:"+i);
//第一种方法获取线程id与name--->要求类继承Thread
// System.out.println("线程id:"+this.getId()+" 线程名称:"+this.getName()+" 子线程............."+i);
//第二种方法获取线程id与name--->不要求类继承Thread
System.out.println("线程id:" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName() + "子线程继承Thread" + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("线程id:" + Thread.currentThread().getId() + "线程名称:" + Thread.currentThread().getName() + "子线程实现Runnable" + i);
}
}
}
3 线程的状态
3.1 线程状态(基本)
线程状态:新建、就绪、运行、终止.
3.2 常见方法
方法名 | 说明 |
---|---|
public static void sleep(long millis) | 当前线程主动休眠 millis 毫秒。 |
public static void yield() | 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。 |
public final void join() | 允许其他线程加入到当前线程中。 |
public void setPriority(int) | 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。 |
public void setDaemon(boolean) | 设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程) |
3.3 线程状态(等待)
案例:
/**
* @Classname TestJoin
* @Description TODO:测试结合(阻塞)方法
* @Created by xx
*/
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Sleep sp = new Sleep();
Thread t = new Thread(sp,"子线程");
t.start();
//加入线程,阻塞当前线程
t.join();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
/**
* @Classname TestSleep
* @Description TODO:测试线程休眠方法
* @Created by xx
*/
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
Sleep sp = new Sleep();
Thread t = new Thread(sp,"子线程");
t.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
//睡眠线程1000时间
Thread.sleep(1000);
}
}
}
class Sleep implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
/**
* @Classname TestYield
* @Description TODO:测试放弃方法
* @Created by xx
*/
public class TestYield {
public static void main(String[] args) {
Sleep sp = new Sleep();
Thread t = new Thread(sp,"子线程");
t.start();
//放弃线程--->进入休眠状态
Thread.yield();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
/**
* @Classname TestPriority
* @Description TODO:编写一个Java程序(包括一个主程序类,一个Thread类的子类)。在主程序中创建2个线程(用子类),将其中一个线程的优先级设为10,另一个线程的优先级设为6。让优先级为10的线程打印200次“线程1正在运行”,优先级为6的线程打印200次“线程2正在运行”。
* @Created by xx
*/
public class TestPriority {
public static void main(String[] args) {
//创建对象
Priority p1 = new Priority();
Priority p2 = new Priority();
//命名
p1.setName("线程1正在运行");
p2.setName("线程2正在运行");
//设置优先级
p1.setPriority(10);
p2.setPriority(6);
//启动线程
p1.start();
p2.start();
}
}
class Priority extends Thread{
@Override
public void run() {
super.run();
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
4 线程安全
为什么会出现线程安全问题?
- 需求:A线程将“Hello”存入数组;B线程将“World”存入数组。
- 线程不安全:
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
案例:多线程访问共享资源可能导致线程不安全
public class ThreadSafe {
private static int index=0;
public static void main(String[] args) throws Exception{
//创建数组
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));
}
}
4.1 同步代码块
语法
synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}
- 每个对象都有一个互斥锁标记,用来分配给线程的。
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
+[线程退出同步代码块时,会释放相应的互斥锁标记。
class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
//对象锁
synchronized (this) {
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName() +"买票,剩余:" + ticket);
ticket--;
}
}
}
}
4.2 线程状态(阻塞)
线程状态:新建、就绪、运行、阻塞、终止。
4.3 同步方法
语法:
synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁
// 代码(原子操作)
}
- 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
- 线程退出同步方法时,会释放相应的互斥锁标记。
- 如果方式是静态,锁是类名.class。
4.4 同步规则
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
JDK中线程安全的类:
- StringBuffer
- Vector
- Hashtable
以上类中的公开方法,均为synchonized修饰的同步方法。
4.5 死锁
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
/**
* @Classname TestDeadLock
* @Description TODO:测试死锁
* @Created by xx
*/
public class TestDeadLock {
public static void main(String[] args) {
Boy boy=new Boy();
Girl girl=new Girl();
girl.start();
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
boy.start();
}
}
class MyLock {
//两个锁(两个筷子)
public static Object a=new Object();
public static Object b=new Object();
}
class Boy extends Thread{
@Override
public void run() {
synchronized (MyLock.a) {
System.out.println("男孩拿到了a");
synchronized (MyLock.b) {
System.out.println("男孩拿到了b");
System.out.println("男孩可以吃东西了...");
}
}
}
}
class Girl extends Thread {
@Override
public void run() {
synchronized (MyLock.b) {
System.out.println("女孩拿到了b");
synchronized (MyLock.a) {
System.out.println("女孩拿到了a");
System.out.println("女孩可以吃东西了...");
}
}
}
}
/*
运行结果:===>正常版
女孩拿到了b
女孩拿到了a
女孩可以吃东西了...
男孩拿到了a
男孩拿到了b
男孩可以吃东西了...
*/
/*
运行结果:===>死锁版
女孩拿到了b
男孩拿到了a
*/
5 线程通信
5.1 线程通信方法
方法 | 说明 |
---|---|
public final void wait() | 释放锁,进入等待队列 |
public final void wait(long timeout) | 在超过指定的时间前,释放锁,进入等待队列 |
public final void notify() | 随机唤醒、通知一个线程 |
public final void notifyAll() | 唤醒、通知所有线程 |
5.2 生产者消费者
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
案例
/**
* @Classname TestProducerConsumer
* @Description TODO:测试生产者消费者问题
* @Created by xx
*/
public class TestProducerConsumer {
public static void main(String[] args) {
Doll doll = new Doll();
Produce produce = new Produce(doll);
Consume consume = new Consume(doll);
Thread yy = new Thread(produce, "yy生-");
Thread xx = new Thread(produce, "xx生-");
Thread zz = new Thread(produce, "zz生-");
Thread ww = new Thread(consume, "ww消-");
Thread xf = new Thread(consume, "xf消-");
yy.start();
xx.start();
zz.start();
ww.start();
xf.start();
}
}
/**
* 玩具娃娃类
*/
class Doll {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Doll() {
}
public Doll(int count) {
this.count = count;
}
/**
* 生产娃娃
*/
public synchronized void produceDoll(int count) throws InterruptedException {
while (this.getCount() > 25) {
//有 25个娃娃不生产
this.wait();
}
this.count += count;
System.out.println(Thread.currentThread().getName() + "生产娃娃" + ",共有" + this.getCount() + "件娃娃");
this.notifyAll();
}
/**
* 购买娃娃
*/
public synchronized void buyDoll(int count) throws InterruptedException {
while (this.getCount() <= 0) {
//没有娃娃不购买
this.wait();
}
this.count -= count;
System.out.println(Thread.currentThread().getName() + "购买娃娃" + ",还剩" + this.getCount() + "件娃娃");
this.notifyAll();
}
}
/**
* 生产者
*/
class Produce implements Runnable {
private Doll dl;
public Produce() {
}
public Produce(Doll dl) {
this.dl = dl;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
dl.produceDoll(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 消费者
*/
class Consume implements Runnable {
private Doll dl;
public Consume(Doll dl) {
this.dl = dl;
}
public Consume() {
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
dl.buyDoll(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6 线程池[重点]
6.1 为什么需要线程池
- 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。
- 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。
6.2 线程池原理
线程池维护着一个队列,队列中保存着处于等待(空闲)状态的线程.不用每次都创建新的线程.
7.3 线程池API
常用的线程池接口和类所在包(java.util.concurrent).
Executor:线程池的顶级接口.
ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码.
Excutors工厂类:通过此类可以获得一个线程池.
方法名 | 描述 |
---|---|
newFixedThreadPool(int nThreads) | 获取固定数量的线程池。参数:指定线程池中线程的数量。 |
newCachedThreadPool() | 获得动态数量的线程池,如不够则创建新的,无上限。 |
newSingleThreadExecutor() | 创建单个线程的线程池,只有一个线程。 |
newScheduledThreadPool() | 创建固定大小的线程池,可以延迟或定时执行任务。 |
案例:测试线程池:
import java.util.concurrent.*;
/**
* @Classname Demo0301
* @Description TODO:4.使用两个线程,并发计算1~50、51~100的和,再进行汇总统计。
* @Created by xx
*/
public class Demo04 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(4);
//2.提交任务
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 50; i++) {
// System.out.println(Thread.currentThread().getName()+"===>"+i);
sum += i;
}
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++) {
// System.out.println(Thread.currentThread().getName()+"===>"+i);
sum += i;
}
return sum;
}
});
System.out.println("1-50和:" + future.get());
System.out.println("51-100和:" + future2.get());
System.out.println("总和:" + (future2.get() + future.get()));
//关闭线程池
es.shutdown();
}
}
6.4 Callable接口
public interface Callable< V >{
public V call() throws Exception;
}
- JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
- Callable具有泛型返回值、可以声明异常。
Runnable接口和Callable接口的区别:
- Callable接口中call方法有返回值,Runnable接口中run方法没有返回值
- Callable接口中call方法有声明异常,Runnable接口中run方法没有异常.
6.5 Future接口
- Future接口表示要执行完任务的结果.
- get()以阻塞形式等待Future中的异步处理结果(call()的返回值).
案例:线程池实现1-100的和
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
/**
* @Classname TestCallable
* @Description TODO:线程池实现1-100的和
* @Created by xx
*/
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//提交任务
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算奇数:");
int oddSum = 0;
for (int i = 0; i <= 100; i++) {
//判断奇数
if (i % 2 != 0) {
oddSum += i;
}
}
return oddSum;
}
});
Future<Integer> future2 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算偶数:");
int evenSum = 0;
for (int i = 0; i <= 100; i++) {
//判断偶数
if (i % 2 == 0) {
evenSum += i;
}
}
return evenSum;
}
});
/* //1.创建Callable对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算奇数:");
int oddSum = 0;
for (int i = 0; i <= 100; i++) {
//判断奇数
if (i % 2 != 0) {
oddSum += i;
}
}
return oddSum;
}
};
Callable<Integer> callable2 = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算偶数:");
int evenSum = 0;
for (int i = 0; i <= 100; i++) {
//判断偶数
if (i % 2 == 0) {
evenSum += i;
}
}
return evenSum;
}
};
//2把Callable对象,转换成可执行任务
FutureTask<Integer> task = new FutureTask<Integer>(callable);
FutureTask<Integer> task1 = new FutureTask<Integer>(callable2);
//3创建线程
Thread odd = new Thread(task);
Thread even = new Thread(task1);
//4.启动线程
odd.start();
even.start();
//5.获取结果--->get方法具有阻塞功能,等待call方法结束后,才会返回
Integer oddSum = task.get();
Integer evenSum = task1.get();
System.out.println("1-100奇数和:"+oddSum);
System.out.println("1-100偶数和:"+evenSum);
System.out.println("1-100总和:"+(oddSum+evenSum));*/
//3获取任务结果,等待任务执行完毕后才会返回
System.out.println("奇数和:" + future.get());
System.out.println("偶数和:" + future2.get());
System.out.println("总和:" + (future.get() + future2.get()));
//关闭线程池
es.shutdown();
}
}
7 Lock接口
7.1 Lock
- JDK5加入,与synchronized比较,显示定义,结果更加灵活.
- 提供更多实用性方法,功能更强大,性能更优越.
常用方法:
方法名 | 描述 |
---|---|
void lock() | 获取锁,如锁被占用,则等待. |
boolean tryLock() | 尝试获取锁(成功返回true。失败返回false,不阻塞)。 |
void unlock() | 释放锁。 |
7.2 重入锁
ReentrantLock:
- Lock接口的实现类,与synchronized一样具有互斥锁功能.
案例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Classname TestReentrantLock
* @Description TODO:测试重入锁
* @Created by xx
*/
public class TestReentrantLock {
public static void main(String[] args) {
SaleTicket st = new SaleTicket();
Thread t = new Thread(st, "1");
Thread t2 = new Thread(st, "2");
Thread t3 = new Thread(st, "3");
t.start();
t2.start();
t3.start();
}
}
class SaleTicket implements Runnable {
private int ticket = 100;
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
//创建一个重入所对象
Lock lr = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//上锁
lr.lock();
if (ticket <= 0) {
break;
}
ticket--;
System.out.println(Thread.currentThread().getName() + "购票,还剩:" + ticket);
} finally {
//释放锁
lr.unlock();
}
}
}
}
7.3 读写锁
ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
- 支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:- 写-写:互斥,阻塞。
- 读-写:互斥,读阻塞写、写阻塞读。
- 读-读:不互斥、不阻塞。
- 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
案例:测试读写锁
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Classname ReadWriteDemo
* @Description TODO:测试读写锁
* @Created by xx
*/
public class ReadWriteDemo {
//创建读写锁
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//获取读锁
private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
//获取写锁
private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
//互斥锁
private ReentrantLock lock = new ReentrantLock();
private String value;
//读取
public String getValue() {
//上读锁
lock.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("读取:" + this.value);
return this.value;
} finally {
//解锁
lock.unlock();
}
}
//写锁
public void setValue(String value) {
lock.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("读取:" + this.value);
this.value = value;
} finally {
//解锁
lock.unlock();
}
}
}
class TestReadWriteLock{
public static void main(String[] args) {
ReadWriteDemo readWriteDemo = new ReadWriteDemo();
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(10);
Runnable read = new Runnable() {
@Override
public void run() {
readWriteDemo.getValue();
}
};
Runnable write = new Runnable() {
@Override
public void run() {
readWriteDemo.setValue("张三"+new Random().nextInt(100));
}
};
//记录任务开始时的毫秒值
long start = System.currentTimeMillis();
//分配两个写任务
for (int i = 0; i < 2; i++) {
es.submit(write);
}
//分配8个读任务
for (int i = 0; i < 8; i++) {
es.submit(read);
}
//关闭资源
es.shutdown();
//等待开始但未完成的`任务完成
while (!es.isTerminated()){}
//任务结束时的毫秒值
long end = System.currentTimeMillis();
System.out.println("用时:"+(end-start));
}
}
8 线程安全的集合
Collection工具类中提供了多个可以获得线程安全集合的方法.
方法名 |
---|
public static <T> Collection<T> synchronizedCollection(Collection<T> c) |
public static <T> List<T> synchronizedList(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提供,接口统一,维护性高,但性能没有提升,均以synchonized实现.
8.1 CopyOnWriteArrayList
- 线程安全的ArrayList,加强版读写分离.
- 写有锁,读无锁,读写之间不阻塞,优于读写锁.
- 写入时,现copy一个容器副本,再添加元素,最后替代引用.
- 使用方式与ArrayList无异.
案例:测试安全的ArrayList集合
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Classname TestCopyOnWriteArrayList
* @Description TODO:测试线程安全的ArrayList集合
* @Created by xx
*/
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
//1.创建集合
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
//2.使用多线程操作
ExecutorService executorService = Executors.newFixedThreadPool(5);
//3.提交任务
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
list.add(Thread.currentThread().getName()+"==="+new Random().nextInt(1000));
}
}
});
}
//关闭线程池
executorService.shutdown();while (!executorService.isTerminated()){}
//5.打印结果
System.out.println("元素个数:"+list.size());
for (String str:list) {
System.out.println(str);
}
}
}
8.2 CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现.
- 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组。
- 如存在元素,则不添加(扔掉副本)。
public class TestCopyOnWriteArraySet {
public static void main(String[] args) {
//1创建集合
CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>();
//2添加元素
set.add("pingguo");
set.add("huawei");
set.add("xiaomi");
set.add("lianxiang");
set.add("pingguo");
//3打印
System.out.println("元素个数:"+set.size());
System.out.println(set.toString());
}
}
8.3 ConcurrentHshMap
- 初始容量默认为16段(Segment),使用分段锁设计.
- 不对整个Map集合加锁,而是为每个Segment加锁.
- 当多个对象存入同一个Segment是,才需要互斥.
- 最理想状态为16个对象分别存入16个Segment,并行数量16.
- 使用方法与HashMap无异.
public class TestConcurrentHashMap {
public static void main(String[] args) {
//1创建集合
ConcurrentHashMap<String, String> hashMap=new ConcurrentHashMap<String, String>();
//2使用多线程添加数据
for(int i=0;i<5;i++) {
new Thread(new Runnable() {
@Override
public void run() {
for(int k=0;k<10;k++) {
hashMap.put(Thread.currentThread().getName()+"--"+k, k+"");
System.out.println(hashMap);
}
}
}).start();
}
}
}
8.4 Queue
- Collection的子接口,表示队列FIFO(First In First Out).
常用方法:
方法名 | 描述 |
---|---|
boolean offer(E e) | 顺序添加一个元素 (到达上限后,再添加则会返回false)。 |
E poll() | 获得第一个元素并移除 (如果队列没有元素时,则返回null)。 |
E keep() | 获得第一个元素但不移除 (如果队列没有元素时,则返回null)。 |
public class TestQueue {
public static void main(String[] args) {
//1创建队列
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());
}
}
8.5 ConcurrentLinkedQueue
- 线程安全,可高效读写的队列,高并发下性能最好的队列.
- 无锁,采用CAS比较交换算法,修改的方法包含三个核心参数(V,E,N).
- V:要更新的变量,E:预期值,N:新值.
- 只有当V==E时,V=N;否则表示已被更新过,取消当前操作.
public class TestConcsurrentLinkedQueue {
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());
}
}
}
8.6 BlockingQueue
- Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法.
- 可用于解决生产消费者问题.
常用方法:
方法名 | 描述 |
---|---|
void put(E e) | 将指定元素插入此队列中,如果没有可用空间,则等待。 |
E take() | 获取并移除此队列头部元素,如果没有可用元素,则等待。 |
8.6.1 ArrayBlockingQueue
- 数组结构实现,有界队列.
- 手工固定上限.
8.6.2 LinkedBlockingQueue
- 链表结构实现,无界队列.
- 默认上限:Integer.MAX_VALUE.
- 使用方法和ArrayBlockingQueue一致.
案例:测试阻塞队列完成生产者消费者问题
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* @Classname TestBolckQueue
* @Description TODO:测试阻塞队列完成生产者消费者问题
* @Created by xx
*/
public class TestBolckQueue {
public static void main(String[] args) {
//创建阻塞队列
BlockingQueue<Doll> bq = new ArrayBlockingQueue<Doll>(5);
//创建任务
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Doll D = new Doll(i, "娃娃" + i);
try {
//队列添加元素
bq.put(D);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "生产" + D);
}
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Doll d = new Doll(i, "娃娃" + i);
try {
//删除队列元素
bq.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "消费" + d);
}
}
};
//创建线程
Thread t = new Thread(r, "生产者");
Thread t2 = new Thread(r2, "消费者");
//启动线程
t.start();
t2.start();
}
}
class Doll {
private int count;
private String name;
@Override
public String toString() {
return "Doll{" +
"count=" + count +
", name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Doll(int count, String name) {
this.count = count;
this.name = name;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Doll() {
}
public Doll(int count) {
this.count = count;
}
/**
* 生产娃娃
*/
public synchronized void produceDoll(int count) throws InterruptedException {
while (this.getCount() > 25) {
//有 25个娃娃不生产
this.wait();
}
this.count += count;
System.out.println(Thread.currentThread().getName() + "生产娃娃" + ",共有" + this.getCount() + "件娃娃");
this.notifyAll();
}
/**
* 购买娃娃
*/
public synchronized void buyDoll(int count) throws InterruptedException {
while (this.getCount() <= 0) {
//没有娃娃不购买
this.wait();
}
this.count -= count;
System.out.println(Thread.currentThread().getName() + "购买娃娃" + ",还剩" + this.getCount() + "件娃娃");
this.notifyAll();
}
}
死锁的四个必要条件
- 互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
- 不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
- 请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
- 循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
以上给出了导致死锁的四个必要条件,只要系统发生死锁则以上四个条件至少有一个成立。事实上循环等待的成立蕴含了前三个条件的成立,似乎没有必要列出然而考虑这些条件对死锁的预防是有利的,因为可以通过破坏四个条件中的任何一个来预防死锁的发生。