JUC01
java 线程的高级篇
1.什么是JUC?
java.util工具包,包,分类
2.线程与进程
进程:一个程序,QQ.exe,Music.exe程序的集合
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程?2个,main GC
线程:开了一个进程Typora, 里面有写字,自动保存等是线程负责的
Java真的可以开线程吗?不能,开不了
public class Test1 {
public static void main(String[] args) {
new Thread().start();
}
}
看源码!只能通过本地方法去调
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();//只能通过本地方法去调
并行 并发
并发(多线程同时操作同一个资源)
- CPU一核,模拟出来多条线程,唯快不破,快速交替
并行(多个人一起行走)
- CPU多核,多个线程可以同时执行 线程池
package com.mjh;
public class Test1 {
public static void main(String[] args) {
//获取CPU的核数
//cpu密集型 IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发的本质:充分利用CPU资源
线程有几个状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待 死死的等
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
3.Lock锁(重要)
为了解决多线程的并发问题
传统的 synchronized 本质就是队列 锁
package com.mjh;
public class Test1 {
public static void main(String[] args) {
//并发 多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口, JDK1.8 lambda表达式(参数)——>{代码}
/* new Thread(new Runnable() {
@Override
public void run() {
}
}).start();*/
new Thread(()->{
for (int i = 0; i <40 ; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <40 ; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{for (int i = 0; i <40 ; i++) {
ticket.sale();
}
},"C").start();
}
}
class Ticket{
//属性,方法
private int number = 30;
//卖票的方式
// synchronized 同步 解决多个人同时抢而造成票数混杂问题 等一个线程执行完了再执行下一个线程
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
}
}
lock 接口
公平锁:十分公平 先来后到
非公平锁:十分不公平 可以插队
package com.mjh;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
public static void main(String[] args) {
//并发 多线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
//@FunctionalInterface 函数式接口, JDK1.8 lambda表达式(参数)——>{代码}
new Thread(()->{ for (int i = 0; i <40 ; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 0; i <40 ; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 0; i <40 ; i++) ticket.sale(); },"C").start();
}
}
//1.new ReentrantLock();
//2. lock.lock();//加锁
// finally ----> lock.unlock();//解锁
class Ticket2 {
//属性,方法
private int number = 30;
Lock lock = new ReentrantLock();
//卖票的方式
public void sale() {
try {
lock.lock();//加锁
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:" + number);
}
} finally {
lock.unlock();//解锁
}
}
}
synchronized 和lock的区别
1.synchronized 是内置的java关键字,lock是一个类
2.synchronize无法判断获取锁的状态,lock 可以判断是否获取到了锁
3.synchronize会自动释放锁,lock必须要手动释放锁!如果不释放锁,就变成了死锁
4.synchronize 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);lock 就不一定会等待下去,它可以等不了就下了
5.synchronize 可重入锁,不可以中断,非公平 ;lock 可重入锁,可以判断锁,非公平(可以自设置);
6.synchronize 适合少量的代码同步问题,lock 适合大量的同步代码
锁是什么,如何判断锁的是谁
4.生产者和消费者
传统的synchronized
package com.mjh;
public class pc {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Date{
private int number= 0;
public synchronized void increment() throws InterruptedException {
if(number!=0){
//等待其他线程使用为0为止
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,我+1结束了
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(number==0){
//等待其他线程生产在使用
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,我-1结束了
this.notifyAll();
}
}
如果有多个线程呢?如果用以上代码,运行出来就会出现如下情况,if就判断了一次,这样多个线程同时去访问同一个资源他也不知道,导致同时都加了1,导致了虚假唤醒
按照官网所说如下图。使用while循环会让一个线程进去,而其他线程等待,因此稍改变代码如下
JUC版的生产者和消费者
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,一定会有优势和补充
Condition实现精准通知和唤醒线程
使用多个监视器就可以实现顺序执行线程
package com.mjh.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PC3 {
public static void main(String[] args) {
Date3 date=new Date3();
new Thread(()->{
for (int i = 0; i <10; i++) {
date.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
date.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10; i++) {
date.printC();
}
},"C").start();
}
}
class Date3{
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
private Condition condition2=lock.newCondition();
private Condition condition3=lock.newCondition();
private int number=1;//1A 2B 3C
public void printA(){
lock.lock();
try {
//业务代码
while(number!=1){
//等待
condition.await();
}
number=2;
System.out.println(Thread.currentThread().getName()+"=>AAAAAAAAA");
condition2.signal();//唤醒当前线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number!=2){
condition2.await();
}
number=3;
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBBBB");
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务代码
while(number!=3){
//等待
condition3.await();
}
number=1;
System.out.println(Thread.currentThread().getName()+"=>CCCCCCCCCCC");
condition.signal();//唤醒当前线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5.8锁现象彻底理解锁
我们回到“锁是什么,如何判断锁是谁”-------->永远知道什么是锁,锁谁
对象,class
package com.mjh.lock8;
import java.util.concurrent.TimeUnit;
/**
*8锁,就是关于锁的8个问题
* 1.标准情况下,两个线程先打印发短信还是打电话? 1/发短信 2/打电话
* 2.sendSms延迟4秒,两个线程先打印发短信还是打电话? 1/发短信 2/打电话
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
// phone.sendSms();这不是先调用的问题,是锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者
//两个方法用的是同一个锁,谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
package com.mjh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 3.增加了一个普通方法下,两个线程先打印你好还是发短信? 1/你好 2/发短信
* 4.两个线程两个对象,一个线程调用一个对象先打印打电话还是发短信? 1/打电话 2/发短信
*/
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
// phone.sendSms();这不是先调用的问题,是锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者
//两个方法用的是同一个锁,谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁
public void hello(){
System.out.println("你好");
}
}
package com.mjh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 5.两个静态方法,一个对象,先打印打电话还是发短信? 1/打电话 2/发短信
* 6.两个静态方法,两个线程两个对象,一个线程调用一个对象先打印打电话还是发短信? 1/打电话 2/发短信
*
*/
public class Test3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone3 = new Phone3();
// phone.sendSms();这不是先调用的问题,是锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone3.call();
},"B").start();
}
}
//Phone3唯一的一个Class对象
class Phone3{
//synchronized 锁的对象是方法的调用者
//static 静态方法
//类一加载就有了,锁的是class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
package com.mjh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 7.1个静态同步方法,1个普通同步方法,一个对象,先打印发短信?还是打电话 1/打电话 2/发短信
* 8.1个静态同步方法,1个普通同步方法,两个对象,先打印发短信?还是打电话 1/打电话 2/发短信
*/
public class Test4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
// phone.sendSms();这不是先调用的问题,是锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
//Phone3唯一的一个Class对象
class Phone4{
//静态同步方法,锁的是class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法,锁的是调用者
public synchronized void call(){
System.out.println("打电话");
}
}
6.集合类不安全
list不安全
package com.mjh.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
/*
并发下ArrayList不安全
解决方法
1. List<String> list = new Vector<>();
2. List<String> list = Collections.synchronizedList(new ArrayList<>());
3. List<String> list = new CopyOnWriteArrayList<>();//JUC的解决方案
CopyOnWrite 写入时复制 简称COW 计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候是固定的,写入(覆盖)
在写入的时候避免覆盖 造成数据问题
Vector 和 CopyOnWriteArrayList<>
Vector 的底层源码的add()用的是synchronized 效率相对较低
CopyOnWriteArrayList<>底层源码的add()用的是Lock锁 效率高
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
set 不安全
package com.mjh.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
//同理可证 java.util.ConcurrentModificationException
public class SetTest {
public static void main(String[] args) {
/*多线程下的 HashSet 不安全
1.工具类
Set<String> set= Collections.synchronizedSet(new HashSet<>());
2. Set<String> set= new CopyOnWriteArraySet<>();
*/
Set<String> set= new CopyOnWriteArraySet<>();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
map不安全
package com.mjh.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MapTest {
// 同理可证:java.util.ConcurrentModificationException
public static void main(String[] args) {
// Map<String,String> map = new HashMap<>();
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
7.常用的辅助类(必会)
CountDownLatch
package com.mjh.add;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
//减法计数器
public static void main(String[] args) throws InterruptedException {
//总数是6,使用场景:必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" go out");
countDownLatch.countDown();//数量-1
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,然后再向下执行
System.out.println("close door");
}
}
原理:
countDownLatch.countDown();//数量-1
countDownLatch.await();//等待计数器归零,然后再向下执行
每次有线程调用countDown()数量-1,假设计数器变为0, countDownLatch.await();就会被唤醒,继续执行!
CyclicBarrier
package com.mjh.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
//加法计数器
public static void main(String[] args) {
/*
集齐7颗龙珠召唤神龙
*/
//召唤龙珠的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("神龙召唤成功!");
});
for (int i = 1; i <=7 ; i++) {
final int temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
try {
cyclicBarrier.await();//等待召集7颗龙珠
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
Semaphore
package com.mjh.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量:停车位!这个Semaphore一般用于限流
Semaphore semaphore = new Semaphore(3);//信号量就只有三个,超过三个线程进来其余就只有等待
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
//acquire() 得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 找到停车位");
TimeUnit.SECONDS.sleep(3);//找到之后让它停一会儿
System.out.println(Thread.currentThread().getName()+" 离开停车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//release() 释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire();获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release();释放,会将当前的信号量释放,然后唤醒等待的线程!
作用:多个线程资源互斥的使用!并发限流,控制最大的线程数!