学习笔记:juc并发编程总结
目录
1. java线程的状态
有6种,new、runnable、blocked、waiting、time_waiting、terminated。同一个时刻一个线程只可能处在其中的一个状态。
java线程状态变迁可以参考《java并发编程的艺术》中的图或者查看源码Thread.State的注释。
可以通过jstack和线程id查看运行的java线程状态
2.Daemon线程
守护线程是一种支持型线程,主要用于程序中后台调度等工作。
java虚拟机中如果没有用户线程就会退出,此时所有的守护线程都会立即终止,所以守护线程中的finally块不一定会执行。
在线程没有运行之前可以通过setDaemon()
方法把线程设置为守护线程。
3.线程的创建方式
- 继承Thread类:Thread类实现了Runable接口
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"run");
}
}
class MyThreadTest{
public static void main(String[] args) {
Thread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
- 实现Runnable接口
class MyThread2{
public static void main(String[] args) {
//新建线程是Thread类的匿名对象,通过构造器创建,这一个Thread构造器中的前一个参数是Runnable接口的匿名实现类对象,后一个参数是线程名不能省略。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" run");
}
},"线程A").start();
//lambda表达式
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" run");
},"线程B").start();
}
}
- 实现callable接口:
FutureTask
是Runable
接口的实现类,同时也有属性依赖callable
接口,可以用来方便的创建线程。
//线程的创建方式:实现callable接口
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//这里FutureTask构造器传入的是callable接口的匿名实现类对象
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " come in callable");
return 1024;
});
FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " come in callable");
return 200;
});
new Thread(futureTask,"线程A").start();
new Thread(futureTask2,"线程B").start();
while (!futureTask.isDone()){
System.out.println("wait...");
}
System.out.println(futureTask.get());
System.out.println(futureTask.get());
System.out.println(futureTask2.get());
System.out.println(Thread.currentThread().getName()+" come over");
}
}
- 线程池创建:
execute()方法
4.synchronized关键字(实现线程同步)
- synchronized锁的实现基础:java中的每一个对象都可以作为锁。
当一个线程试图访问同步代码块,它必须先得到锁,退出或者抛出异常时必须释放锁。
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0 ; i < 30; i++) {
ticket.sale();
}
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0 ; i < 30; i++) {
ticket.sale();
}
}
},"线程2").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0 ; i < 30; i++) {
ticket.sale();
}
}
},"线程3").start();
}
}
class Ticket{
private int number = 30;
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"票,剩下"+number+"张票");
}
}
- synchronized(八锁问题):
反映synchronized的特点:- 对于同步方法,锁就是当前对象。
- 对于静态同步方法,锁是当前类的class对象
- 对于同步代码块,锁是synchronized括号里配置的对象。
//1.标准情况 syn通用一把锁
//2.加入延时 syn 锁的对象是方法的调用者 ,两个方法用的是同一把锁,谁先拿到谁先执行
//3.增加普通方法
//4.两个对象
//5.增加两个静态同步方法 静态方法锁的是class模板
//6.两个对象两个静态方法
//7. 一个静态同步方法 一个普通同步方法
//8. 两个对象 一个静态同步方法 一个普通同步方法
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(()->{
phone.send();
}).start();
new Thread(()->{
phone1.call();
}).start();
new Thread(()->{
phone.sayHello();
}).start();
}
}
class Phone {
public static synchronized void send() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
public void sayHello() {
System.out.println("Hello");
}
}
5.sleep()方法和wait()方法的区别
wait()
定义在Object类中,使用在同步方法中,用于线程通信,线程调用wait
方法,会释放掉锁。
sleep()
定义在Thread类中,是个静态方法,用于暂停线程,线程调用sleep
方法,不会释放锁
yield()
定义在Thread类中,也是静态方法,用于暂停线程,让其他处于相同优先级的线程来竞争执行,始终处于runable状态
join()
定义在Thread类中,是个同步方法,如果在A线程中调用了B.join()
方法,A线程会取得同步方法的锁,如果B线程存活,A线程会调用wait()
释放掉锁进入等待状态,直到方法完成B线程释放锁会自动唤醒A线程。这就完成了线程让步。
6.线程通信:
- 虚假唤醒
出现虚假唤醒问题的原因:wait()
方法让线程进入waiting状态,被notify()
方法唤醒后会继续执行,如果使用if
仅判断一次,notifyAll()
会让所有之前通过if
的线程执行,哪怕现在检测条件已经不满足。所以为了避免出现,需要使用while
来判断。 - 等待/通知的经典范式:
- 获取对象的锁
- 判断检测,不满足就调用对象的
wait()
方法,被通知后仍要检查条件。 - 条件满足则执行对应的逻辑。
//虚假唤醒问题:if / while
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程1").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程2").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程3").start();
}
}
class Share{
private int number = 0;
public synchronized void incr() throws InterruptedException {
while (number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"执行incr,number: "+number);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
while (number == 0) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"执行decr,number: "+number);
this.notifyAll();
}
}
7.lock
- 实现多线程同步
public class LSaleTicket {
public static void main(String[] args) {
LTicket lTicket = new LTicket();
//lambda表达式
new Thread(()-> {
for (int i = 0; i < 30; i++) {
lTicket.sale();
}
},"线程1").start();
new Thread(()-> {
for (int i = 0; i < 30; i++) {
lTicket.sale();
}
},"线程1").start();
new Thread(()-> {
for (int i = 0; i < 30; i++) {
lTicket.sale();
}
},"线程1").start();
}
}
class LTicket{
private int number = 40;
public void sale () {
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"票,剩下"+number+"张票");
}
} finally {
lock.unlock();
}
}
}
- lock实现线程通信:
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
share.incr();
}
},"线程A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
share.decr();
}
},"线程B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
share.incr();
}
},"线程C").start();
}
}
class Share{
private int number = 0;
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void incr() {
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"执行incr,number: "+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decr() {
lock.lock();
try {
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"执行decr,number: "+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- 实现线程间定制通信:通过改变标志位和等待/通知机制来实现
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 0; i < 10; i++) {
shareResource.print5(i);
}
},"线程A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print10(i);
}
}, "线程B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareResource.print15(i);
}
}, "线程C").start();
}
}
class ShareResource{
private int flag = 1;
private Lock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void print5(long loop) {
lock.lock();
try{
while (flag != 1) {
conditionA.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"打印"+i+"轮次"+loop);
}
//改变标志位
flag = 2;
//唤醒其它线程
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(long loop) {
lock.lock();
try{
while (flag != 2) {
conditionB.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"打印"+i+"轮次"+loop);
}
//改变标志位
flag = 3;
//唤醒其它线程
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(long loop) {
lock.lock();
try{
while (flag != 3) {
conditionC.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"打印"+i+"轮次"+loop);
}
//改变标志位
flag = 1;
//唤醒其它线程
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8、线程不安全的集合类
- List
类ArrayList
是线程不安全的,多个线程调用put()
方法时,会抛出异常。
常用的解决方法是写时复制机制,添加集合元素时,先复制一个副本,在副本上添加元素,再让变量指针指向副本。这个过程需要持有lock锁,完成后再释放锁。
//解决list不安全
//1.List<Integer> list = new Vector<>();
//2.List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
//3.List<Integer> list = new CopyOnWriteArrayList<>();
public class ListTest {
public static void main(String[] args) {
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
int temp = i;
new Thread(()-> {
list.add(temp);
System.out.println(Thread.currentThread().getName()+list);
}, String.valueOf(i)).start();
}
}
}
- Set
//set不安全
//1.Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
//2.Set<Integer> set = new CopyOnWriteArraySet<>();
public class SetTest {
public static void main(String[] args) {
Set<Integer> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 100; i++) {
int temp = i;
new Thread(()->{
set.add(temp);
System.out.println(Thread.currentThread().getName()+set);
},String.valueOf(i)).start();
}
}
}
- Map
//map不安全
//1.Map<Integer,String> map = Collections.synchronizedMap(new HashMap<>());
//2.Map<Integer,String> map = new ConcurrentHashMap<>();
public class MapTest {
public static void main(String[] args) {
Map<Integer,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 100; i++) {
int temp = i;
new Thread(()->{
map.put(temp,"hello");
System.out.println(Thread.currentThread().getName()+map);
},String.valueOf(i)).start();
}
}
}
9.死锁问题
出现原因
- 竞争资源
- 持有资源不释放
public class DeadLock {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(()->{
synchronized (a) {
System.out.println(Thread.currentThread().getName()+"持有锁a,想获取锁b");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println("获取到了锁b");
}
}
},"线程A").start();
new Thread(()->{
synchronized (b) {
System.out.println(Thread.currentThread().getName()+"持有锁b,想获取锁a");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println("获取到了锁a");
}
}
},"线程B").start();
}
}
10. 并发工具类
简单的记录一下怎么使用。
- CountDownLatch
让一个线程或多个线程等待其它线程完成工作,如果其它线程没有完成工作,当前线程会一直处于阻塞状态,其它线程都完成工作后,才执行await()
后面的程序。
public class CountDownLatch {
public static void main(String[] args) throws InterruptedException {
java.util.concurrent.CountDownLatch countDownLatch = new java.util.concurrent.CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 同学离开了!");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("班长锁门了!");
}
}
- 同步屏障CyclicBarrier
每一个线程到达一个屏障时被阻塞,直到最后一个线程到达屏障,屏障才会打开,所有被屏障拦截的线程才会继续执行。每个线程调用await()
方法时表明已经到达屏障,然后当前线程被阻塞。
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("集齐七龙珠召唤神龙!");
});
for (int i = 0; i < 7; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 收集到龙珠!");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
- 信号灯 SemaPhore
控制同时访问特定资源的线程数量。
release()
会动态的增加许可数量,构造器参数只是说明了一个许可证的初始值。
public class SemaPhoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到了车位!");
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(Thread.currentThread().getName()+"离开了!");
}
},String.valueOf(i)).start();
}
}
}
11、读写锁
- 读写锁的区别
其他锁基本是排他锁,同一时刻只允许一个线程访问,读写锁维护类一对锁,一个读锁,一个写锁,在同一时刻可以允许多个读线程访问,在写线程访问时,所有的读线程和其它写线程被阻塞 - 锁获取
写锁获取:如果存在读锁,则不能获取,并且写锁是排他锁,只能由一个线程持有。如果允许读锁在已经获取的情况下获取写锁,那么持有读锁的线程时感觉不到写锁线程的操作的。
读锁获取:只有在当前线程获取写锁或者没有任何一个线程获得写锁的情况下,才能获取读锁。 - 锁降级
过程:当前线程获得写锁,再获取读锁,最后释放写锁。
为什么不支持锁升级?可能有多个线程获取了读锁,在获取写锁,其它持有读锁的线程感觉不到写锁线程的操作。
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写操作
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(()->{
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
//读操作
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<Object,Object> map = new HashMap<>();
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(Object k,Object v) {
//写锁
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+" 正在写");
try {
TimeUnit.SECONDS.sleep(3);
map.put(k,v);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
System.out.println(Thread.currentThread().getName()+" 写完了!");
}
public Object get(Object k) {
//读锁
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName()+" 正在读");
Object value = null;
try {
TimeUnit.SECONDS.sleep(3);
value = map.get(k);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
System.out.println(Thread.currentThread().getName()+" 读完了!");
return value;
}
}
12.阻塞队列
- 定义:支持两个阻塞操作的队列
阻塞的插入方法:当队列满的的时候,队列会阻塞插入元素的线程,直到队列不满的时候
阻塞的移除方法:在队列为空的时候,队列会阻塞获取元素的线程,等待队列变为非空。 - 常见的阻塞队列:无界的阻塞队列默认的长度是Integer.MAX_VALUE
ArrayBlockingQueue 有界阻塞队列; LinkedBlockingQueue 无界阻塞队列; DelayQueue 优先级队列实现的无界阻塞队列。 - 4中处理方式
//阻塞队列
public class BlockQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<Integer>(3);
// 第一组方法
System.out.println(blockingQueue.add(1));
System.out.println(blockingQueue.add(2));
System.out.println(blockingQueue.add(3));
System.out.println(blockingQueue.add(4));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// 第二组方法
System.out.println(blockingQueue.offer(1));
System.out.println(blockingQueue.offer(2));
System.out.println(blockingQueue.offer(3));
System.out.println(blockingQueue.offer(4));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 第三组方法
blockingQueue.put(1);
blockingQueue.put(1);
blockingQueue.put(1);
blockingQueue.put(1);
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
//第四组方法
blockingQueue.offer(1,3, TimeUnit.SECONDS);
blockingQueue.offer(1,3, TimeUnit.SECONDS);
blockingQueue.offer(1,3, TimeUnit.SECONDS);
blockingQueue.offer(1,4, TimeUnit.SECONDS);
blockingQueue.poll(4,TimeUnit.SECONDS);
blockingQueue.poll(4,TimeUnit.SECONDS);
blockingQueue.poll(4,TimeUnit.SECONDS);
blockingQueue.poll(2,TimeUnit.SECONDS);
System.out.println(blockingQueue);
}
}
13.线程池
有三种常见的线程池:FixedThreadPool
、SingleThreadExecutor
、CachedThreadPool
- 线程池的核心参数:
corePoolSize
:核心线程数
maximumPoolSize
:线程池最大数量
runnableTaskQueue
:任务阻塞队列
keepAliveTime
:线程活动保持时间
TimeUnit
: 线程活动保持时间的单位
RejectedExcecutionHandler
:饱和拒绝策略 - 线程池工作流程
执行execute()
,判断核心线程池满没满(是否都在执行任务),没满就创建一个工作线程;满了就下一步判断阻塞队列满没满,没满就把任务存在队列里,满了就下一步判断线程池最大线程,没满就创建一个新的工作线程,满了就下一步执行拒绝策略。
核心线程池->工作队列->线程池最大线程->拒绝策略 - 自定义线程池:经常使用到
public class ThreadPollDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(3, 5, 2L, TimeUnit.MICROSECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"办理业务");
});
}
}
}