1. 什么是JUC
java.util 工具包、包、分类
业务:普通线程代码 Thread
Runnable 没有返回值、效率相比于Callable相对较低!
2. 线程和进程
线程、进程,如果不能使用一句话说出来的的基数,不扎实!
进程:一个程序,QQ.exe Music.exe 程序的集合 ;
一个进程往往可以包含多个线程,至少包含一个!
Java默认有两个线程:main线程 和 GC线程。
线程: 开了一个进程 Typora,写字,自动保存(线程负责)
对于Java而言:Thread、Runnable、Callable
Java真的可以开启线程嘛? 不可以,只能通过本地方法(native),通过底层c++调用。
并发、并行
并发编程:并发(多线程操作同一个资源)、并行(多个人一起行走)。
- CPU 一核,模拟出来多条线程。
- CPU 多核,多个线程可以同时执行。线程池
public static void main(String[] args) {
// 获取CPU的核数
// CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
并发编程的本质:充分利用CPU的资源
2.1 线程有几个状态
六个状态:
public enum State {
// 新手
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死的等待
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
2.2 wait和sleep的区别
1、来自不同的类。
wait =》Object。
sleep =》Thread。
2、关于锁的释放
sleep 不会释放锁。
wait 会释放锁。
3、使用的范围是不同的。
wait 必须在同步代码块中使用。
sleep 可以在任何地方睡。
3. Lock锁(重点)
Synchronized 本质就是排队,锁
真正的多线程开发,公司中的开发
线程就是一个单独的资源类,没有任何附属的操作!
- 属性
- 方法
package com.mango.demo01;
// 买票
public class Test01 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类
final Ticket ticket = new Ticket();
new Thread(()->{
for(int i = 1; i<60;i++){
ticket.sale();
}
},"a").start();
new Thread(()->{
for(int i = 1; i<60;i++){
ticket.sale();
}
},"b").start();
new Thread(()->{
for(int i = 1; i<60;i++){
ticket.sale();
}
},"c").start();
}
}
// 资源类
class Ticket {
// 属性、方法
private int number = 50;
// 卖票的方式
public synchronized void sale(){
if(number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第"+(number--)+"票,剩余"+number);
}
}
}
Lock
公平锁:十分公平:可以先来后到
非公平锁:十分不公平:可以插队(默认非公平)
Lock三部曲
- new ReentrantLock()
- lock.lock() 加锁
- lock.unlock() 解锁
package com.mango.demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test02 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类
Ticket2 ticket = new Ticket2();
new Thread(()->{for(int i = 1; i<60;i++)ticket.sale();},"a").start();
new Thread(()->{for(int i = 1; i<60;i++)ticket.sale();},"b").start();
new Thread(()->{for(int i = 1; i<60;i++)ticket.sale();},"c").start();
}
}
// Lock
class Ticket2 {
// 属性、方法
private int number = 50;
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();
}
}
}
区别
- Synchornized 内置的java关键字,Lock 是一个Java类
- Synchornized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchornized 会自动释放锁,Lock 必须要手动解锁。如果不释放锁,死锁
- Synchronized 线程1(获得锁、阻塞)、线程2(等待,一直等待);Lock锁不一定会一直等待下去。
- Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,可设置公平性。
- Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。
锁是什么,如何判断锁的是谁!
生产者喝消费者问题
(1)Synchronized 版
package com.mango.pc;
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 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(()->{
try {
for(int i = 0;i<10;i++) data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for(int i = 0;i<10;i++) data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
// 判断等待、业务、通知
class Data{ // 数字 资源类
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
if(number != 0){
// 等待
this.wait();
}
// 通知其他线程,我+1完毕了
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if(number == 0){
this.wait();
}
// 通知其他线程,我-1完毕了
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
问题:现在只有两个线程,如果有四个线程或者四个以上怎么办?
if 改为 while,防止虚假唤醒。
package com.mango.pc;
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 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(()->{
try {
for(int i = 0;i<10;i++) data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for(int i = 0;i<10;i++) data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
new Thread(()->{
try {
for(int i = 0;i<10;i++) data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"C").start();
new Thread(()->{
try {
for(int i = 0;i<10;i++) data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"D").start();
}
}
// 判断等待、业务、通知
class Data{ // 数字 资源类
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
while(number != 0){
// 等待
this.wait();
}
// 通知其他线程,我+1完毕了
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
while(number == 0){
this.wait();
}
// 通知其他线程,我-1完毕了
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
(1)JUC 版
任何一个新的奇数,绝对不是仅仅只是覆盖了原来的技术,肯定会有优势和补充!
Condition 可以精准的通知和唤醒线程
代码实现:
package com.mango.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num + 1
* B num - 1
*/
public class C{
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
try {
for(int i = 0;i<10;i++) data.printA();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for(int i = 0;i<10;i++) data.printB();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for(int i = 0;i<10;i++) data.printC();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
}
}
// 判断等待、业务、通知
class Data3{ // 数字 资源类
private int number = 1;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void printA(){
lock.lock();
try{
while (number != 1){
condition.await();
}
number = 2;
System.out.println(Thread.currentThread().getName() + "=>"+"A");
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
while (number != 2){
condition.await();
}
number = 3;
System.out.println(Thread.currentThread().getName() + "=>"+"B");
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
while (number != 3){
condition.await();
}
number = 1;
System.out.println(Thread.currentThread().getName() + "=>"+"C");
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
// 生产线:下单->支付->交易完成->物流
}
4. 8锁状态
八锁,就是关于锁的八个问题:
- 1、标准情况下,两个线程先打印哪个。 发短信还是打电话 ? 1.发短信 2.打电话 答:1
- 2、sendSms延迟4s情况下,两个线程先打印哪个。 发短信还是打电话 ? 1.发短信 2.打电话 答:1
- 原因: 锁的存在!被 synchronized 锁的对象是方法的调用者!两个方法用的是同一个锁,谁先拿到谁执行!
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
- 3、增加了一个普通方法,两个线程执行完,先发短信还是hello ? hello
- 原因:hello() 没有锁,所以不受锁的影响。
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone2{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
- 4、两个对象,两个同步方法?先执行什么?
- 原因:两个对象、两个调用者,两把锁!
public class Test2 {
public static void main(String[] args) {
// 两个对象
Phone2 phone = new Phone2();
Phone2 phone1 = new Phone2();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
- 5、增加两个静态的同步方法,只有一个对象,发短信、还是打电话?发短信
- 6、两个对象!增加两个静态的同步方法,发短信还是打电话? 发短信
- 原因:static静态方法,Class类一加载就有了,所以他锁的是Class对象,即便是不同的对象,但是他锁的是Class。
public class Test3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone1 = new Phone3();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
},"B").start();
}
}
class Phone3{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
- 7、一个静态同步方法、一个普通同步方法,先执行哪个?打电话
- 8、一个静态同步方法、一个普通同步方法,两个对象先执行哪个?打电话
- 原因:静态同步方法锁的是Class类,普通同步方法,锁的是对象,两个锁的不一样,看谁先执行完。
public class Test4 {
public static void main(String[] args) {
Phone4 phone = new Phone4();
Phone4 phone1 = new Phone4();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
},"B").start();
}
}
class Phone4{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}