狂神说es笔记_狂神说JUC学习笔记+补充(⼀)
1、什么是JUC?
指的是java.util包下的三个⼯具类:
1. urrent
2. urrent.atomic
3. urrent.locks
实现多线程的三种⽅式:
1. 继承Thread类
2. 实现Runnable接⼝
3. 实现Callable接⼝
Runnable没有返回值、效率相⽐于Callable相对较低!
Runnable
业务
业务:普通的线程代码 Thread
本篇笔记篇幅较长,若想⼀⽓呵成地看完就看本篇就⾏。如果想⼀点点地看,把其中的部分抽取出来做成了4个⼦笔记,点击 本篇笔记篇幅较长,若想⼀⽓呵成地看完就看本篇就⾏。如果想⼀点点地看,把其中的部分抽取出来做成了4个⼦笔记,点击链接看对应⼦笔记就⾏。两种⽅式的内容相同。
1.什么是JUC?(内容较少,就未抽取)
2.java线程和进程演绎法5919:java线程和进程学习⼦笔记
2.java线程和进程
3.Lock锁中的synchonized部分演绎法5919:synchronized学习笔记
3.Lock锁中的synchonized部分
4.8锁现象演绎法5919:8锁现象学习⼦笔记
4.8锁现象
5.不安全的集合类演绎法5919:不安全的集合类学习⼦笔记
5.不安全的集合类
2、线程和进程
进程:是⼀个程序,⼀个进程包含多个线程,且⾄少包含⼀个。
进程
不能开启线程的,底层是调⽤start0()是⼀个native⽅法,由底层的C++⽅法编写。java⽆法Java默认有两个线程:main 和 GC。Java是不能开启线程的
直接操作硬件。
并发:
并发:CPU单核,多个线程共⽤⼀个资源,快速交替⽅式达到并⾏的假象。本质->充分利⽤cpu资源。
并发:CPU多核,多个线程同时执⾏
并发:
System.out.Runtime().availabelProcessors());//获取cpu核数
线程有⼏个状态:
public enum State{
NEW,//新⽣
RUNNABLE,//运⾏
BLOCKED,//阻塞
BLOCKED,//等待
TIMED_WAITING,//超时等待
TERMINATED;//终⽌
}
wait和sleep的区别
wait:来⾃Object类,会会释放锁,必须必须在同步代码块中使⽤,不需要不需要捕获异常
让当前线程进⼊等待状态,当别的其他线程调⽤notify()或者notifyAll()⽅法时,当前线程进⼊就绪状态。wait⽅法必须在同步上下⽂中调⽤。也就是说,如果想要调⽤wait⽅法,前提是必须获取对象上的锁资源。当wait⽅法调⽤时,当前线程会释放已获取的对象锁资如果想要调⽤wait⽅法,前提是必须获取对象上的锁资源。当wait⽅法调⽤时,当前线程会释放已获取的对象锁资源,并进⼊等待队列
源,并进⼊等待队列,其他线程就可以尝试获取对象上的锁资源。sleep:来⾃Thread类是⼀个静态⽅法,不会不会释放锁!(睡着了),可以在任何地⽅任何地⽅使⽤,必须必须要捕获异常
让当前线程休眠指定时间。休眠时间的准确性依赖于系统时钟和CPU调度机制。不释放以获取的锁资源,如果sleep⽅法在同步上下⽂中如果sleep⽅法在同步上下⽂中调⽤,那么其他线程是⽆法进⼊当前同步快或者同步⽅法中的
调⽤,那么其他线程是⽆法进⼊当前同步快或者同步⽅法中的。可通过interrupt()⽅法来唤醒休眠线程。3.Lock 锁
3.1传统的synchronized锁:队列锁
1.sychronized的作⽤:
在并发编程中会存在线程安全问题,主要原因是存在共享数据和多线程共同操作共享数据。关键字sychronized可以保证在同⼀时刻只有⼀个线程可以执⾏某个⽅法或某个代码块(临界区),同时synchronized可以保证⼀个线程的变化可见。
只有共享资源读写访问才需要同步化,如果不是共享资源就没有必要同步。
2.三种应⽤⽅式
修饰实例实例⽅法,对当前实例进⾏加锁,进⼊同步代码前需获得当前实例的锁实例的锁
修饰静态静态⽅法,对类对象加锁,要先获得当前类对象的锁类对象的锁
修饰代码块代码块,指定加锁对象,对给定对象加锁,要先获得给定对象的锁对象的锁
2.1同步⽅法(静态⽅法、实例⽅法)
⽤sychronized修饰的⽅法就叫做同步⽅法,保证A线程执⾏该⽅法的时候,其他线程只能在⽅法外等着。
synchronized public void doWorlk(){
...
}
对于⾮静态类⽅法,同步锁就是this(实例对象)
对于静态类⽅法,同步锁是当前⽅法所在类的字节码对象(类对象)
2.2同步代码块
syschronized(同步锁){
....
}
同步锁:
同步锁:在任何时候最多只能有⼀个线程有同步锁,其他线程只能在代码块外等着。为保证每个线程
都能正常执⾏原⼦操作,Java引⼊了线程同步机制。java程序运⾏使⽤任何对象作为同步监听对象,但⼀般的,我们把当前当前并发访问的共同资源作为同步监听对象。
3.举个栗⼦:
package demo01;
public class test01 {
public static void main(String[] args) throws InterruptedException {
// 并发:多线程操作同⼀个资源类,
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接⼝
new Thread(() -> {
for (int i = 1; i < 20; i++) {
//把资源类丢⼊线程
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i < 20; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i < 20; i++) {
ticket.sale();
}
}, "C").start();
}
}
class Ticket {
//共卖40张票
private int number = 30;
// 卖票的⽅式
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "票,剩余:" + number);
}
}
}
当多个线程同时对⼀个对象的⼀个⽅法进⾏操作,只有⼀个线程能够抢到锁。因为⼀个对象只有⼀把锁,⼀个线程获取了该对象的锁之后,其他线程⽆法获取该对象的锁,就不能访问该对象的其他synchronized实例⽅法,但是可以访问⾮synchronized修饰的⽅法。
3.2Lock接⼝
Lock所是⼀个接⼝,其所有的实现类为
ReentrantLock(可重⼊锁)
ReentrantReadWriteLock.ReadLock(可重⼊读写锁的读锁)
ReentrantReadWriteLock.WriteLock(可重⼊读写锁的写锁)
Lock实现提供了⽐使⽤synchronized⽅法和语句可获得的更⼴泛的锁定操作。Lock接⼝的实现允许锁
在不同的作⽤范围内获取和释放,并允许以任何顺序获取和释放多个所。随着灵活的增加,也带来了更多的责任。不使⽤块结构锁就失去了使⽤synchronized⽅法和语句时会出现的锁⾃动释放功能。在⼤多数情况下,应使⽤以下语句:
Lock l = ...;
l.lock();//加锁
try {
// access the resource protected by this lock
} finally {
l.unlock();//解锁
}
加锁和解锁出现在不同作⽤范围中是,需谨慎确保锁定是所执⾏的所有代码⽤try-finally或try-catch保护,以确保在必要时释放锁。
公平锁:⼗分公平,遵循先来后到
公平锁:
⾮公平锁:⼗分不公平,出现插队现象
⾮公平锁:
可重⼊锁:如果锁具备可重⼊性,则称为可(可以)重(再次)⼊(进⼊同步域,即同步代码块/⽅法)锁(同步锁)。可重⼊就是指某个线程可重⼊锁:
已经获得某个锁,可以再次获取相同的锁⽽不会出现死锁。
3.3synchronized和Lock锁的区别
synchronized:
是java内置的关键字
⽆法获取锁的状态
会⾃动释放锁
线程⼀在获得锁的情况下阻塞了,第⼆个线程就只能傻傻的等着
可重⼊锁
是不可中断的、⾮公平的、可重⼊锁
适合锁少量的同步代码
有代码块锁和⽅法锁
Lock:
是java的⼀个类
可判断是否获取了锁
需⼿动释放锁,如果不释放会造成死锁
线程⼀在获得锁的情况下阻塞了,可以使⽤tryLock()尝试获取锁
⾮公平的、可判断的、可重⼊锁
可重⼊锁
适合锁⼤量的同步代码
只有代码块锁
使⽤Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(拥有更多的⼦类)
4.8锁现象
掌握8中锁的现象永远知道锁的是谁!
package lock8;
import urrent.TimeUnit;
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.call();
}, "A").start();
new Thread(() -> {
phone.send();
}, "B").start();
}
}
class Phone {
public synchronized void send() {
System.out.println("发短信");
}
public synchronized void call() {
//现象⼆时添加,让线程先睡4秒种一个线程可以包含多个进程
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("打电话");
}
}
现象⼀:两个⽅法都使⽤synchronized关键字,先执⾏打电话
现象⼆:让线程先睡4秒,结果依然是先打电话
synchromized锁的是⽅法的调⽤者,并且开启的两个线程⽅法使⽤的是同⼀把锁,那么就会出现谁先拿到谁先执⾏的现原因:synchromized锁的是⽅法的调⽤者,并且开启的两个线程⽅法使⽤的是同⼀把锁,那么就会出现谁先拿到谁先执⾏的现象。及时我们让call⽅法sleep了4秒,依然是call⽅法先执⾏。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论