Java中的多线程你只要看这一篇附学习资料

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

…do something

}

}

}

  • 直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。

public class Thread1 implements Runnable {

public synchronized void run() {

…do something

}

}

  • synchronized, wait, notify结合:典型场景生产者消费者问题

/**

  • 生产者生产出来的产品交给店员

*/

public synchronized void produce()

{

if(this.product >= MAX_PRODUCT)

{

try

{

wait();

System.out.println(“产品已满,请稍候再生产”);

}

catch(InterruptedException e)

{

e.printStackTrace();

}

return;

}

this.product++;

System.out.println(“生产者生产第” + this.product + “个产品.”);

notifyAll(); //通知等待区的消费者可以取出产品了

}

/**

  • 消费者从店员取产品

*/

public synchronized void consume()

{

if(this.product <= MIN_PRODUCT)

{

try

{

wait();

System.out.println(“缺货,稍候再取”);

}

catch (InterruptedException e)

{

e.printStackTrace();

}

return;

}

System.out.println(“消费者取走了第” + this.product + “个产品.”);

this.product–;

notifyAll(); //通知等待去的生产者可以生产产品了

}

volatile

多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。

volatile

针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。

太祖长拳:基本线程类


基本线程类指的是Thread类,Runnable接口,Callable接口 Thread 类实现了Runnable接口,启动一个线程的方法:

MyThread my = new MyThread();

my.start();

Thread类相关方法:

//当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)

public static Thread.yield()

//暂停一段时间

public static Thread.sleep()

//在一个线程中调用other.join(),将等待other执行完后才继续本线程。

public join()

//后两个函数皆可以被打断

public interrupte()

关于中断:它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。 Thread.interrupted()检查当前线程是否发生中断,返回boolean synchronized在获锁的过程中是不能被中断的。

中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体

Thread类最佳实践: 写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。

如何获取线程中的异常

不能用try,catch来获取线程中的异常

Runnable

与Thread类似

Callable

future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态

ExecutorService e = Executors.newFixedThreadPool(3);

//submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.

Future future = e.submit(new myCallable());

future.isDone() //return true,false 无阻塞

future.get() // return 返回值,阻塞直到该线程运行结束

九阴真经:高级多线程控制类


以上都属于内功心法,接下来是实际项目中常用到的工具了,Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent, 提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。

1.ThreadLocal类

用处:保存线程的独立变量。对一个线程类(继承自Thread) 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。

实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。 主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。

2.原子类(AtomicInteger、AtomicBoolean……)

如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized

//返回值为boolean

AtomicInteger.compareAndSet(int expect,int update)

该方法可用于实现乐观锁,考虑文中最初提到的如下场景:a给b付款10元,a扣了10元,b要加10元。此时c给b2元,但是b的加十元代码约为:

if(b.value.compareAndSet(old, value)){

return ;

}else{

//try again

// if that fails, rollback and log

}

AtomicReference 对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。 这时候,AtomicStampedReference就派上用场了。这也是一个很常用的思路,即加上版本号

3.Lock类

lock: 在java.util.concurrent包内。共有三个实现:

  • ReentrantLock

  • ReentrantReadWriteLock.ReadLock

  • ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。

区别如下:

  1. lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)

  2. 提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。

  3. 本质上和监视器锁(即synchronized是一样的)

  4. 能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。

  5. 和Condition类的结合。

  6. 性能更高,对比如下图:

synchronized和Lock性能对比

ReentrantLock     可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。 使用方法是:

1.先new一个实例

static ReentrantLock r=new ReentrantLock();

2.加锁

r.lock()或r.lockInterruptibly();

此处也是个不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争抢,进入catch块。(如果使用后者,必须throw interruptable exception 或catch)

3.释放锁

r.unlock()

必须做!何为必须做呢,要放在finally里面。以防止异常跳出了正常流程,导致灾难。这里补充一个小知识点,finally是可以信任的:经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。

ReentrantReadWriteLock

可重入读写锁(读写锁的一个实现)

ReentrantReadWriteLock lock = new ReentrantReadWriteLock()

ReadLock r = lock.readLock();

WriteLock w = lock.writeLock();

两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码

4.容器类

这里就讨论比较常用的两个:

  • BlockingQueue

  • ConcurrentHashMap

BlockingQueue 阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管  道,特别适用于先进先出策略的一些应用场景。普通的queue接口主要实现有PriorityQueue(优先队列),有兴趣可以研究

BlockingQueue在队列的基础上添加了多线程协作的功能:

BlockingQueue

除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。

常见的阻塞队列有:

  • ArrayListBlockingQueue

  • LinkedListBlockingQueue

  • DelayQueue

2021年Java中高级面试必备知识点总结

在这个部分总结了2019年到目前为止Java常见面试问题,取其面试核心编写成这份文档笔记,从中分析面试官的心理,摸清面试官的“套路”,可以说搞定90%以上的Java中高级面试没一点难度。

本节总结的内容涵盖了:消息队列、Redis缓存、分库分表、读写分离、设计高并发系统、分布式系统、高可用系统、SpringCloud微服务架构等一系列互联网主流高级技术的知识点。

目录:

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

部分内容:

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
)]

(上述只是一个整体目录大纲,每个点里面都有如下所示的详细内容,从面试问题——分析面试官心理——剖析面试题——完美解答的一个过程)

[外链图片转存中…(img-DD7B2YPG-1714677190365)]

部分内容:

[外链图片转存中…(img-S4qVLnwp-1714677190365)]

[外链图片转存中…(img-UNn6apQF-1714677190365)]

[外链图片转存中…(img-3XskWAMA-1714677190366)]

对于每一个做技术的来说,学习是不能停止的,小编把2019年到目前为止Java的核心知识提炼出来了,无论你现在是处于什么阶段,如你所见,这份文档的内容无论是对于你找面试工作还是提升技术广度深度都是完美的。

不想被后浪淘汰的话,赶紧搞起来吧,高清完整版一共是888页,需要的话可以点赞+关注
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当然可以!下面是一篇关于Java多线程的"锁"的博客: ## Java多线程的锁 在Java多线程编程,锁是一种重要的同步机制,用于保护共享资源的访问。使用锁可以防止多个线程同时对共享资源进行修改,从而避免数据不一致的问题。 ### 1. 什么是锁? 锁是一种同步机制,它可以让多个线程按照一定的顺序访问共享资源。在Java,锁可以是隐式的,也可以是显式的。 - 隐式锁:通过关键字`synchronized`来实现,它可以用于修饰方法或代码块。当一个线程进入被`synchronized`修饰的方法或代码块时,它会自动获取锁,并在执行完毕后释放锁。 - 显式锁:通过`java.util.concurrent.locks`包的Lock接口及其实现类来实现,如ReentrantLock。显式锁需要手动获取和释放,在使用上更加灵活。 ### 2. 锁的作用 锁的主要作用是保护共享资源的访问,它可以解决多线程并发访问时可能引发的以下问题: - 竞态条件(Race Condition):当多个线程同时访问共享资源,并且对其进行写操作时,可能会导致数据的不一致。 - 临界区(Critical Section):当多个线程同时访问共享资源,并且对其进行读写操作时,可能会导致数据的不一致。 - 死锁(Deadlock):当多个线程相互等待对方释放锁时,可能会导致程序无法继续执行。 ### 3. 锁的类型 Java常用的锁类型包括: - 内置锁(Intrinsic Lock):也称为监视器锁(Monitor Lock),是由关键字`synchronized`来实现的。内置锁是基于对象的,每个对象都有一个用于同步的内置锁,当一个线程获取了该锁后,其他线程必须等待。 - 重入锁(Reentrant Lock):是`java.util.concurrent.locks`包的一个显式锁实现类,它具有与内置锁类似的功能,但提供了更高级的特性,如可重入、公平和超时等。 - 读写锁(Read-Write Lock):也是`java.util.concurrent.locks`包的一个显式锁实现类,它区分了读操作和写操作,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。 ### 4. 锁的使用示例 下面是一个使用内置锁`synchronized`来实现线程安全的示例: ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` 在上述示例,`increment()`和`getCount()`方法都被`synchronized`修饰,这意味着同一时间只能有一个线程执行这些方法,从而保证了`count`变量的访问安全。 ### 5. 锁的注意事项 在使用锁时,需要注意以下事项: - 避免死锁:在获取锁的时候,要确保能够及时释放锁,避免多个线程相互等待对方释放锁而导致死锁。 - 避免饥饿:要确保所有线程都有公平获取锁的机会,避免某个线程一直无法获得锁而导致饥饿。 - 锁的粒度:要选择合适的锁粒度,尽量减小锁的范围,以提高程序的并发性能。 - 锁的性能:显式锁相对于内置锁,通常具有更高的性能,但使用不当可能导致性能问题。要根据实际情况选择合适的锁。 ### 总结 锁是Java多线程编程重要的同步机制,用于保护共享资源的访问。它可以解决竞态条件、临界区和死锁等问题。在使用锁时,需要注意避免死锁和饥饿,选择合适的锁粒度和锁类型,以及权衡锁的性能。 希望本篇博客对你有所帮助!如有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值