准备工作
新建一个Maven项目,引入一个lombok依赖.
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
java Compiler改成8
什么是JUC
Java.util.concurrent
进程:一个程序,QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程? 2 个 mian、GC
线程:开了一个进程 Typora,写字,自动保存(线程负责的)
Java 真的可以开启线程吗?
java是开不了线程的
new Thread().start();
我们点进start()
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();
首先。start()是一个synchronized方法,同步方法,安全,这个方法会把当前线程加入一个线程组,调用了start0()方法,这个start0(),是用native修饰的,也就是本地方法。所以最后是调用了本地方法,JAV是没有权限开启线程的。start()调用了本地的C++方法,因为java是运行在虚拟机之上的,无法直接操作硬件
并发、并行
并发:cpu只有一个核:多线程操作同一个资源(cpu通过线程间的快速交替,模拟出来多条线程,看似并行,实际串行)
并行:多个人一起行走,cpu有多个核,多个线程可以同时执行,可以通过线程池完成
并发编程的本质:充分利用CPU的资源
查看处理器的方法
①任务管理器
如图:最大可以同时处理12条线程
②设备管理器也可以看有多少个处理器
③通过代码
public static void main(String[] args) {
//new Thread().start();
//获取CPU核数 CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
线程的状态
Thread.State;
咱们点进state就可以看到了
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
wait()和sleep()的区别
1:来自不同的类
wait()来自Object类,sleep()来自Thread类
2 关于锁的释放
wait()会释放锁,sleep()不会释放锁,可以理解为抱着锁睡觉
3使用范围不同
sleep()可以在任何地方使用,wait()只能在同步代码块中使用
4是否需要捕获异常
wait()不需要捕获异常
sleep()必须要捕获异常
Lock锁
传统的synchronized锁
在公司真正的多线程开发中,线程就是一个单独的资源类,没有任何附属的操作(类中只有属性和方法),为了降低耦合性,不会用类去实现接口,因为实现类接口就不是OOP编程了,而且实现了接口的话耦合性变高,如果让类实现了Runnable,这个类就只是一个线程类了
如下代码,只是把Ticket作为了资源类,并没有让它实现Runnable接口
package com.kuang.demo01;
public class SaleTicketDemo01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
// ticket.sale();
//并发,多线程操作同一个资源类,把资源类丢进线程
// new Thread(new Runnable() { //这种方式教匿名内部类,但是太繁琐了,于是我们向导用lambda表达式
// public void run() {
//
// }
// }).start();
//函数式接口,jdk1.8 lambda表达式(参数)->{代码}
new Thread(()->{//3个线程操作同一个资源,这种方式对于Ticket类来说实现类解耦,因为Ticket类是一个纯类,没有实现Runnable接口
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
class Ticket{
public int number=50;
//synchronized本质是队列,锁
public synchronized void sale(){
if(number>0)
{
System.out.println(Thread.currentThread().getName()+"获得了第"+number--+"票");
}
}
}
Lock接口
所有已知实现类:
ReentrantLock(可重入锁) , ReentrantReadWriteLock.ReadLock (读锁), ReentrantReadWriteLock.WriteLock (写锁)
公平锁:十分公平,先来后到,排队.
非公平锁:不公平,可以插队
默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗
ReentrantLock() 无参时默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
用之前加锁,用完解锁
Lock三部曲
new 锁
加锁
解锁(在finally中,lock.unlock())
package com.kuang.demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SalwTicketDemo02 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{for (int i = 0; i < 60; i++) ticket.sale(); },"A").start();
new Thread(()->{
for (int i = 0; i < 60; i++) ticket.sale(); },"B").start();
new Thread(()->{
for (int i = 0; i < 60; i++) ticket.sale(); },"C").start();
}
}
class Ticket2{
Lock lock=new ReentrantLock();
public int number=50;
//synchronized本质是队列,锁
public synchronized void sale() {
lock.lock();
try {
//业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "获得了第" + 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会等待,线程1阻塞了,线程2也会死等。使用Lock锁时,Lock锁不一定会等待,Lock有一个方法,Lock.tryLock();会去尝试获取锁
5 Synchronized 可重入锁,不看可以在中断,非公平。 Lock:可重入,可以判断锁,可以设置公平或者非公平
6 Synchronized适合锁少量的代码同步问题。Lock锁适合锁大量的同步代码
生产者消费者问题
面试高频: 单例模式, 八大排序,生产者消费者,死锁
①synchronized版本,用wait()和notify()
生产者消费者骨架:等待、执行业务、通知
package com.kuang.demo01;
/*
线程之间的通信问题:生产者和消费者问题
线程交替执行 A B操作同一个变量 num=0 A让num+1,B让num-1;
**/
public class ProductAndConsumer {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//生产者消费者骨架:等待、执行业务、通知
class Date{//数字,资源类
private int num=0;
public synchronized void increment() throws InterruptedException {
if(num!=0)
{
//等待操作
this.wait();
}
num++;
//执行完++,通知其他线程我已经完成++操作
System.out.println(Thread.currentThread().getName()+"=>"+num);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(num==0)
{
//等待操作
this.wait();
}
num--;
//执行完--,通知其他线程我已经完成--操作
System.out.println(Thread.currentThread().getName()+"=>"+num);
this.notifyAll();
}
}
synchronized版本存在的问题
如果增加两个线程,即两个线程加,两个线程减,得到如下结果,并不能1,0交替,而且出现了2,3
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
问题原因分析
咱们看jdk1.8的官方文档,找到Object类的wait()方法
导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:while (<condition does not hold>) obj.wait(timeout); ... // Perform action appropriate to condition } ```
结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
跟cpu调度有关,运气好没问题也是可能的.主要原因还是因为wait会释放锁,会导致两个num++/num–的线程同时进入wait队列。当线程被唤醒后,A先获得锁++,这时侯如果cpu调度到B,B就会继续往下执行++.如果调度是有序的(++,–,++,–),也不会有这种问题
解决方法
用if会出现,判断过了,但是拿到的是之前的值
把if判断改成while判断等待,因为if判断进if之后不会停,用while判断的话,变量一旦被修改,另外一个线程拿到锁之后,就会等待,防止虚假唤醒
② 用Lock锁完成生产者消费者问题
package com.kuang.demo01;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class prodectAndConsumerLock {
public static void main(String[] args) {
Date2 date = new Date2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Date2{
private int num=0;
Lock lock=new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
try {
lock.lock();//锁
//业务代码
while(num!=0)
{
//等待操作
condition.await();
}
num++;
//执行完++,通知其他线程我已经完成++操作
System.out.println(Thread.currentThread().getName()+"=>"+num);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
public void decrement() throws InterruptedException {
try {
lock.lock();
while(num==0)
{
//等待操作
condition.await();
}
num--;
//执行完--,通知其他线程我已经完成--操作
System.out.println(Thread.currentThread().getName()+"=>"+num);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
用Lock锁完成生产者消费者问题改进,Condition 精准的通知和唤醒线程、
让ABCD四个线程交替执行
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充!
Condition 精准的通知和唤醒线程
package com.kuang.demo01;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//A执行完通知 B,B执行完通知C
public class ProductAndConsumerLockJiaoti {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data3.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data3.printB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data3.printC();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
class Data3{
Lock lock=new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3= lock.newCondition();
private int state=1;
int num=1;
public void printA() throws InterruptedException {
lock.lock();
try {
while(state!=1)
{
condition1.await();
}
System.out.println((num++)+"\t"+Thread.currentThread().getName()+"=>AAAAA");
condition2.signal();//A执行完通知B
state=2;//改变state的状态
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() throws InterruptedException {
lock.lock();
try {
while(state!=2)
{
condition2.await();
}
System.out.println((num++)+"\t"+Thread.currentThread().getName()+"=>BBBBB");
condition3.signal();//A执行完通知B
state=3;//改变state的状态
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() throws InterruptedException {
lock.lock();
try {
while(state!=3)
{
condition3.await();
}
System.out.println((num++)+"\t"+Thread.currentThread().getName()+"=>CCCCC");
condition1.signal();//A执行完通知B
state=1;//改变state的状态
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象(锁是什么,如何判断锁的是谁)
锁只会锁两个东西,一个是new出来的对象,一个是class模板
8锁,就是关于锁的8个问题
问题1:
package com.kuang.lock8;
/*
8锁,就是关于所得8个问题
问题1:标准情况下,先打印发短信,还是先打印打电话?结果是先打印发短信,后打印打电话
原因:不能回答先调用A线程,这是错误的,不是先调用先执行,这是锁的问题,因为被Synchronized修饰的
方法,锁的对象是方法的调用者,所以盗用两个方法的对象都是phone,但是现在phone只有一个,也就是说着两个方
法现在用的是同一把锁,谁先拿到,谁就先执行
*/
import java.util.concurrent.TimeUnit;
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 (Exception e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class phone{
public synchronized void sendSms()
{
System.out.println("sendSms");
}
public synchronized void call()
{
System.out.println("call");
}
}
问题2:给发短信方法加延时4s,程序的执行情况
public synchronized void sendSms()
{
try {
TimeUnit.SECONDS.sleep(4);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("sendSms");
}
结果:经过4s之后打印发短信,然后立马打印打电话,如果把主函数中延时改为5s,那么陈恒徐运行情况是经过4s打印发送短信,然后经过1s打印打电话(主程序的延时是同时进行的?)
问题3:当调用普通方法,而不是synchronized方法时,先输出什么
答:先执行普通方法,因为普通方法没有锁,不受锁的影响
public class Test3{
public static void main(String[] args) {
phone2 phone = new phone2();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{
phone.sayHello();
},"B").start();
}
}
class phone2{
public synchronized void sendSms()
{
try {
TimeUnit.SECONDS.sleep(4);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("sendSms");
}
public void sayHello()
{
System.out.println("hello");
}
}
问题4:两个对象分别调用synchronized方法时,先输出什么
因为锁不一样,所以耗时短的先输出
问题5:一个对象,把两个synchronized方法改成静态synchronized方法,即static synchronized方法,先输出哪个?
答:发短信:static方法在类加载时就有了,锁的对象是class模板,因为class对象是唯一的 phone2.class全局唯一,因为两个方法都被static修饰了,所以两个方法用的是同一个锁,且锁的是类模板
问题6:2个对象,把两个synchronized方法改成静态synchronized方法,即static synchronized方法,先输出哪个?
答:发短信,phone2.class全局唯一(两个对象的class类模板只有一个),因为两个方法都被static修饰了,所以两个方法用的是同一个锁,且锁的是类模板
问题7:同一个对象,把两个synchronized方法中的一个改成静态synchronized方法,即static synchronized方法,另一个为普通synchronized方法,先输出哪个?
答:打电话,锁的对象不一样,一个锁的是类模板,一个锁的是对象,后面调用的方法不需要去等待锁
问题8:两个对象,把两个synchronized方法中的一个改成静态synchronized方法,即static synchronized方法,另一个为普通synchronized方法,先输出哪个?
答:打电话,还是锁的对象不一样
小结
当同步方法不用static修饰的时候:锁的是对象,是一个具体的手机
当同步方法用static修饰的时候:锁的是类模板,是唯一的
附件(包括代码和笔记、jdk文档)
链接:https://pan.baidu.com/s/1-0MCWtBGxxljpcqY0EwR5Q
提取码:1nc7