JUC
JUC指的是:Java里的一个包
java.util.concurrent 并发
java.util.concurrent.atomic:原子性
java.util.concurrent.locks:lock锁
业务:普通的线程代码Thread
Runnable没有返回值、效率相比Callable 较低!
所以企业中一般更多使用Callable 去执行一些任务
1.概述
进程和线程
进程是操作系统分配资源的最小单元,而线程是cpu调度的最小单元。
程序执行的一次过程,一个进程包含一个或多个线程。进程是资源分配的单位。
可以指程序执行过程中,负责实现某个功能的单位。线程是CPU调度和执行的单位。
一个进程至少包含一个线程
java默认有两个线程:主线程和gc守护线程
java 真的可以开启线程吗?
不能!!!
在Thread.start中调用了 本地方法,底层的C++。Java无法直接操作硬件。
从源码来看, 一个线程如果已经调用过 start 方法,那么再次调用 start 方法的时候会抛出 IllegalThreadStateException 异常, 涉及的技术点为线程的状态切换。
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ 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 */ } } } private native void start0();
并发和并行
并发:同一时刻,多个线程交替执行。(一个CPU交替执行线程)
并行:同一时刻,多个线程同时执行。(多个CPU同时执行多个线程)
public class TestProcessors {
public static void main(String[] args) {
//获取cpu核数
//cpu密集型 io密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
多核 CPU 内部集成了多个计算核心(Core),每个核心相当于一个简单的 CPU,多核 CPU 的每个核心都可以独立地执行一个任务,而且多个核心之间不会相互干扰。在不同核心上执行的多个任务,是真正地同时运行,这种状态就叫做并行。
多核心技术是将多个一样的CPU放置于一个封装内(或直接将两个CPU做成一个芯片),而英特尔的HT技术(超线程技术)是在CPU内部仅复制必要的资源、让一个核模拟成两个线程;也就是一个实体核心,两个逻辑线程,在一单位时间内处理两个线程的工作,模拟实体双核心、双线程运作。
多核CPU,可以并行执行多进程、多线程
所以说 并发编程可以充分利用cpu的资源
wait/sleep的区别
Thread.sleep(long millis),线程休眠。一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
1、来自不同的类
wait => Object
sleep => Thread
一般情况企业中使用休眠是:
import java.util.concurrent.TimeUnit;
TimeUnit.DAYS.sleep(1); //休眠1天
TimeUnit.SECONDS.sleep(1); //休眠1s
2、关于锁的释放
wait:会释放锁;
sleep:睡觉了,不会释放锁;
3、使用的范围是不同的
wait 必须在同步代码块(synchronized)中;如果不放在同步代码块或方法中,运行时会报错
sleep 可以在任何地方睡;
Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
5、是否需要捕获异常
wait不需要捕获异常;(目前也需要了)
sleep必须要捕获异常;
2.传统的synchronized
在开发中,我们在javaThread中的new Thread() 放一个rannable 已经不用了。
我们应该把线程看作一个单独的资源类,没有任何的附属操作!
// 资源类 OOP 属性、方法
class Ticket {
//属性
private int number = 30;
//卖票的方式
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");
}
}
}
public static void main(String[] args) {
//并发 多线程操作同一个资源类
final Ticket ticket = new Ticket();
//把资源类丢入线程
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();
}
}
synchronized 方法控制 “对象” 的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放,后面被阻塞的线程才能获得这个锁,继续执行。
synchronized的方法加上static变成静态方法 那么锁的就是这个类
缺陷:若将一个大的方法申明为 synchronized 将会影响效率。
3.LOCK锁
new ReentrantLock
无参构造是非公平锁,传入参数,如果是true 是公平锁。
源码:
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁是指多个线程按照申请锁的顺序来获取锁。
优点:等待锁的线程不会饿死。
缺点:整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
作用:严格按照线程启动的顺序来执行的,不允许其他线程插队执行
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。非公平锁是按照cup调度的
优点:可以减少唤起线程的开销,吞吐量比公平锁大。,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。
缺点:处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
作用:线程启动允许插队的。
默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
使用方法:
- new
- 加锁
- 业务代码
- 解锁
{
private int number = 30;
Lock lock = new ReentrantLock();
//卖票的方式
public void sale() {
//加锁
lock.lock();
try {
//业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//解锁
lock.unlock();
}
}
}
Synchronized 与Lock 的区别
手动挡和自动挡的区别
1、Synchronized 内置的Java关键字,Lock是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock可以判断
3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁
4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
什么是可重入锁
就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。(简单来说:A线程在某上下文中或得了某锁,当A线程想要在次获取该锁时,不会应为锁已经被自己占用,而需要先等到锁的释放)假使A线程即获得了锁,又在等待锁的释放,就会造成死锁。
指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized 和 ReentrantLock 都是可重入锁。
案例:Synchronzied 版本生产者和消费者的问题
/*
线程之间的通信问题:生产者和消费者问题
线程交替执行 A B操作同一个变量 num=0 A让num+1,B让num-1;
**/
public class ConsumeAndProduct {
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 num = 0;
// +1
public synchronized void increment() throws InterruptedException {
// 判断等待
if (num != 0) {//while
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
// 判断等待
if (num == 0) { //while
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 -1 执行完毕
this.notifyAll();
}
}
存在的问题(虚假唤醒)
如果上面的代码是4个线程,if判断时 可能进去2个,其他线程通知时,两个又同时唤醒,可能原本是0,+1后 通知了两-1,最后变成了负一。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CoNSrrWe-1664277114135)(…/…/tools/Typora/upload/image-20220926092234091.png)]
public synchronized void increment() throws InterruptedException {
// 判断等待
while (num != 0) {//while
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
// 判断等待
while (num == 0) { //while
this.wait();
//把if改成while后 被唤醒不会马上去num--; 会再进行一次判断,防止了一下子唤醒了多个 多个线程的-1或者+1
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 -1 执行完毕
this.notifyAll();
}
案例:LOCK版本生产者和消费者的问题
public class LockCAP {
public static void main(String[] args) {
Data2 data = new Data2();
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 Data2 {
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
// 判断等待
while (num != 0) {
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
condition.signalAll();
}finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
// 判断等待
while (num == 0) {
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
// 通知其他线程 +1 执行完毕
condition.signalAll();
}finally {
lock.unlock();
}
}
}
精准的通知和唤醒的线程
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充
这个例子不太好,这个是通过状态控制的,不知道为啥要用三个condition 一个也是可以的,因为这里while条件已经限制了顺序
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3 {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num = 1; // 1A 2B 3C
public void printA() {
lock.lock();
try {
// 业务代码 判断 -> 执行 -> 通知
while (num != 1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "==> AAAA" );
num = 2;
condition2.signal();
}catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
// 业务代码 判断 -> 执行 -> 通知
while (num != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "==> BBBB" );
num = 3;
condition3.signal();
}catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
// 业务代码 判断 -> 执行 -> 通知
while (num != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "==> CCCC" );
num = 1;
condition1.signal();
}catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
/*
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
...
*/