02 JUC并发编程1
JUC
java.util.concurrent 工具包
业务:普通的线程代码 Thread
Runnable:没有返回值,效率相比 Callable 相对较低
1、进程和线程
进程:一个程序,QQ.exe、Music.exe
一个进程往往可以包含多个线程,至少包含一个
java 默认 有两个线程:main线程 gc线程(垃圾回收)
线程:开了一个进程 world ,写字,自动保存(线程负责的)
对于java:Thread、Runnable、Callable
java真的可以开启线程吗? 不能 java无法直接操作硬件
本地方法,底层的c++,java无法直接操作硬件
native 本地方法,底层的c++,java无法直接操作硬件
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//native 本地方法,底层的c++,java无法直接操作硬件
private native void start0();
线程有几个状态?
新生、运行、阻塞、等待、超时等待、终止
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
一般用timeutil
TimeUnit.SECONDS.sleep(2);
TimeUnit.DAYS.sleep(2);
wait/sleep 区别
-
来自不同的类
wait => Object
sleep => Threat
-
关于锁的释放
wait会释放锁
sleep 不会释放,睡觉了,抱着不放
-
使用的范围是不同的
wait:要在同步代码块中
sleep 可以在任意地方睡
-
是否需要捕获异常
wait 不需要捕获异常
sleep 必须要捕获异常
2、并发 并行
并发:多线程操作同一个资源,交替进行、
- cpu一核,模拟出来多条线程 快速交替
并行:多个人一起行走,同时进行
- cpu多核,多个线程可以同时执行,最高性能:线程池
//获取cpu的核数
//CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
并发编程的本质:充分利用cpu的资源
3、Lock锁(重点)
传统 synchronized
/**
* 真正的多线程开发,公司中的开发,降低低耦合
* 线程就是一个单独的资源类,没有任何附属的操作
* **/
public class SaleTicketDemo1 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源丢入线程
Ticket1 ticket = new Ticket1();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.saleTicket();
}
},"C").start();
}
}
//资源类
class Ticket1{
private int number = 30;
public synchronized void saleTicket(){
if (number > 0){
System.out.println(Thread.currentThread().getName() + "买了第" + number-- + "票" + "还有" + number + "张票");
}
}
}
Lock锁
公平锁:十分公平;可以先来后到
非公平锁:十分不公平,可以插队(默认 )
Lock三部曲:
Lock lock = new ReentrantLock();
lock.lock();//加锁
finally => lock.unlock();//解锁
public class SaleTicketDemo2 {
public static void main(String[] args) {
Ticket1 ticket1 = new Ticket1();
//并发:多线程操作同一个资源类,把资源丢入线程
new Thread(()->{for (int i = 0; i < 40; i++) ticket1.saleTicket();},"A").start();
new Thread(()->{ for (int i = 0; i < 40; i++) ticket1.saleTicket(); },"B").start();
new Thread(()->{ for (int i = 0; i < 40; i++) ticket1.saleTicket(); },"C").start();
}
}
//资源类
class Ticket2 {
private int number = 30;
Lock lock = new ReentrantLock();
public void saleTicket() {
lock.lock();//加锁
try {
//业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "买了第" + number-- + "票" + "还有" + number + "张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
Synchronized 和 Lock 区别
- Synchronized 是内置的java关键字 Lock是一个java类
- Synchronized 无法获取锁的状态,Lock可以判断是否获取到了锁
- Synchronized 会自动释放锁,Lock必须要手动释放锁!如果不释放锁 ,死锁
- Synchronized 线程1(获得锁,如果阻塞 ) 线程2(等待,一直等) ,Lock锁就不一定会等待下去
- Synchronized 可重入锁,不可以中断的,非公平;Lock 可重入锁,可以判断,非公平锁(可设置)
- Synchronized 适合锁少量的代码同步问题;Lock 适合锁大量的同步代码
4、消费者和生产者
线程之间的通信问题! 等待唤醒,通知唤醒
线程交替执行 A B 操作同一个变量 num = 0
A num + 1
B num - 1
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待、业务、通知
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
if (number != 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--;
//通知其他线程,我-1完毕了
System.out.println(Thread.currentThread().getName() + "=>"+number);
this.notifyAll();
}
}
问题存在,A B C D 4个线程! 虚假唤醒
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒。等待应该总是出现在循环中
当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用的唤醒;
if改为while判断
- if判断流水线状态为空时,线程被阻塞,这时if判断就完成了,线程被唤醒后直接执行线程剩余操作
- while判断流水线状态为空时,线程被阻塞,这时的while循环没有完成,线程被唤醒后会先进行while判断
理解:两个加法线程A、C来说,比如A先执行,执行时调用了wait方法,此时会释放锁,接着如果线程C获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的B线程执行完毕并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行。如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行。
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待、业务、通知
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
while (number != 0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>"+number);
//通知其他线程,我+1完毕了
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (number == 0){
//等待
this.wait();
}
number--;
//通知其他线程,我-1完毕了
System.out.println(Thread.currentThread().getName() + "=>"+number);
this.notifyAll();
}
}
juc版的生产者和消费者问题
代码实现
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待、业务、通知
class Data{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await(); 等待
//condition.signalAll(); 唤醒全部
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (number != 0){
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>"+number);
//通知其他线程,我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>"+number);
//通知其他线程,我-1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
Condition 优势 精准的通知和唤醒线程
public class C {
public static void main(String[] args) {
Data1 data1 = new Data1();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data1.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data1.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data1.printC();
}
},"C").start();
}
}
class Data1{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 1) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAA");
//唤醒指定的人:B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBBB");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>cccccc");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、8锁
8锁就是关于锁的8个问题
-
标准情况下,两个线程先打印 发短信还是打电话? 1/发短信 2/达电话
-
send方法延迟2秒 ,两个线程先打印 发短信还是打电话? 1/发短信 2/打电话
synchronized 锁的对象是方法的调用者 phone
两个方法用的同一个锁,谁先拿到谁执行
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.send();
},"AA");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"AA");
}
}
class Phone{
public synchronized void send(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
- 增加了一个普通方法hello()?先执行hello再执行发短信
普通方法没有锁,不是同步方法,不受锁的影响
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.seed();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone2{
public synchronized void seed(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
- 两个对象,两个同步方法,发短信还是打电话? 先打电话 发短信有延迟
public static void main(String[] args) {
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone1.seed();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
- 增加两个静态的同步方法,只有一个对象, 发短信还是打电话?发短信,打电话
static 静态方法, 类一加载就有了,锁的是Class。
phone3只有唯一的一个class对象
//将两个方法设置成static
public class Test3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
new Thread(()->{
phone.seed();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone3{
public static synchronized void seed(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
- 两个对象,增加两个静态的同步方法,发短信还是打电话?发短信,打电话
public static void main(String[] args) {
//两个对象的Class类模板只有一个
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.seed();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
- 1个静态的同步方法,1个普通的同步方法,一个对象,发短信还是打电话?打电话 发短信 两个锁,打电话不用等发短信的延迟
//将两个方法设置成static
public class Test4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
new Thread(()->{
phone.seed();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone4{
//静态的同步方法 锁的是Class类模板
public static synchronized void seed(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法 锁的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
- 1个静态的同步方法,1个普通的同步方法,2个对象,发短信还是打电话?打电话 发短信 两个锁,打电话不用等发短信的延迟
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.seed();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
6、集合类不安全
list 不安全
public class ListTest {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().subSequence(0,5));
},String.valueOf(i));
}
}
}
这样会报错
java.util.ConcurrentModificationException 并发修改异常
解决方案:
-
List<String> list = new Vector<>();
-
List<String> list = Collections.synchronizedList(new ArrayList<>());
-
List<String> list = new CopyOnWriteArrayList();
CopyOnWrite 写入时复制, COW 计算机程序设计领域的一种优化策略
多个线程调用的时候 list ,读取的时候是固定的,写入的时候,可能会覆盖,CopyOnWrite 写入时复制,避免覆盖,造成数据问题
CopyOnWrite 比 Vector 厉害在哪儿
Vector 的add是synchronized 效率不高
CopyOnWrite 是写入时复制
set 不安全
Set<String> set = new HashSet<>();
for (int i = 1; i <= 100; i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
}).start();
}
同理 java.util.ConcurrentModificationException 并发修改异常
解决方案:
-
Set<String> set = Collections.synchronizedSet(new HashSet<>());
-
Set<String> set = new CopyOnWriteArraySet<>();
HashSet 本质是 Map
add() 本质是 map.put key是无序的 不可重复的
HashMap 不安全
//Map<String,String> map = new HashMap<>(16,0.75);
Map<String,String> map = new HashMap<>();
for (int i = 1; i <= 100; i++){
new Thread(()->{ map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
java.util.ConcurrentModificationException 并发修改异常
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
位运算
初始容量 1 << 4 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
加载因子 0.75
解决方案:
Map<String,String> map = new ConcurrentHashMap<>();
7、callable
- 可以有返回值
- 可以抛出异常
- 方法不同 call()
get() 方法可能会产生阻塞! 把他放到最后
或者使用异步通信来处理
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new Runnable()).start();
//new Thread(newFutureTask<V>()).start();
//new Thread(newFutureTask<V>(Callable)).start();
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask,'A').start();//call
new Thread(futureTask,'B').start();//只会打印一个call,结果会被缓存,效率高
String str = (String) futureTask.get();//获取callable的返回值 可能会产生阻塞
System.out.println(str);//1024
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("call");
return "1024 ";
}
}
细节:
- 有缓存
- 结果可能需要等待,会阻塞
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)