java多线程学习(个人学习仅供参考)
进程与线程
进程就是程序执行的过程,它是一个动态的概念,进程中有多个线程,一个进程中至少有一个线程。
线程就是独立执行的路径。
main称之为主线程,用于执行整个程序。
在一个进程中如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统密切相关的,先后顺序人为不能干预。
对同一份资源进行操作时,会存在资源抢夺的问题,需要加入并发控制。
线程会带来额外的开销,如cpu调度时间,并发控制开销。
每个线程在工作内存交互,内存控制不当会造成数据不一致。
线程的创建
Thread
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
/**
* 创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
*
* @author Administrator
*/
public class TestThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程" + i);
}
}
public static void main(String[] args) {
//创建线程
TestThread testThread = new TestThread();
//调用start开启线程
testThread.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程" + i);
}
}
}
Runnable
自定义线程类实现Runnable接口
重写run()方法,编写线程执行体
执行线程需要丢入Thread接口的实现类,调用start方法
package com.demo.demo.demo01;
/**
* 实现runnable接口,重写run方法,执行线程需要丢入Thread接口的实现类,调用start方法
*
* @author Administrator
*/
public class TestThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程" + i);
}
}
public static void main(String[] args) {
//创建线程
TestThread2 testThread = new TestThread2();
//调用start开启线程
new Thread(testThread).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程" + i);
}
}
}
callable
实现Callable接口,需要返回值类型
重写call方法,需要抛出异常
创建目标对象
创建执行服务
提交结果
关闭服务
/**
* 实现Callable可以设置返回值类型
*
* @author Administrator
*/
public class TestCallable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("第" + i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable testCallable = new TestCallable();
//创建执行服务
ExecutorService es = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = es.submit(testCallable);
//获取结果
Boolean b = r1.get();
//关闭
es.shutdownNow();
}
}
Lambda表达式
避免匿名内部类定义过多
其实属于函数式编程的概念
去掉了一堆没有意义的代码只留下核心的逻辑
函数式接口的定义:
任何接口如果只包含了唯一一个抽象方法,那么它就是一个函数式接口。
package com.demo.demo.demo01;
/**
* @author Administrator
*/
public class TestLambda {
//3 使用静态内部类去简化代码
static class TestClass2 implements TestInterface {
@Override
public void test() {
System.out.println("test2");
}
}
public static void main(String[] args) {
//实例化类并调用方法
TestInterface testInterface = new TestClass();
testInterface.test();
//实例化静态内部类并调用方法
testInterface = new TestClass2();
testInterface.test();
//4 使用局部内部类来简化代码
class TestClass3 implements TestInterface {
@Override
public void test() {
System.out.println("test3");
}
}
//实例化局部内部类并调用方法
testInterface = new TestClass3();
testInterface.test();
//5 匿名内部类来简化代码
testInterface = new TestInterface() {
@Override
public void test() {
System.out.println("test4");
}
};
//调用方法
testInterface.test();
//6.用lambda简化
testInterface = () -> System.out.println("test5");
//调用方法
testInterface.test();
}
}
//1.定义一个函数接口
interface TestInterface {
void test();
}
//2.用一个类实现接口
class TestClass implements TestInterface {
@Override
public void test() {
System.out.println("test");
}
}
线程状态
创建状态-启动线程变为->就绪状态->阻塞状态->运行状态->死亡状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAPDiiwV-1597249687183)(C:\Users\Administrator\Desktop\线程状态.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UKayLr4c-1597249687187)(C:\Users\Administrator\Desktop\线程详细.png)]
线程方法
方 法 | 说 明 |
---|---|
setPriority(int newPriority) | 更改线程优先级 |
static void sleep(long millis) | 在指定毫秒数内瓤当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
不推荐使用JDK提供的stop(),destroy()方法。【已废弃】
推荐线程自己停止下来
建议使用一个标志位进行终止变量当flag=false,则线程停止。
package com.demo.demo.demo01;
/**
* 1.建议线程正常停止-->利用次数,不建议死循环
* 2.建议使用标志位来停止
* 3.不要使用过时的方法
*
* @author Administrator
*/
public class TestStop implements Runnable {
//设置标志位
private Boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("运行中" + i++);
}
}
//设置一个公开的线程停止方法
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
TestStop t = new TestStop();
new Thread(t).start();
for (int i = 0; i < 500; i++) {
System.out.println("main" + i);
if (i == 400) {
//调用自己的stop方法让线程停止
t.stop();
System.out.println("线程停止");
}
}
}
}
线程休眠
sleep(时间)指定当前线程阻塞的毫秒数。
sleep存在异常InterruptedExection。
sleep时间达到后线程进入就绪状态。
sleep可以模拟网络延迟时,倒计时等。
每一个对象都有一个锁,sleep不会释放锁。
package com.demo.demo.demo01;
/**
* 模拟网络延迟放大问题的发生性
*
* @author Administrator
*/
public class TestSleep implements Runnable {
//总票数
private Integer number = 10;
@Override
public void run() {
while (number > 0) {
try {
//模拟延迟
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + number-- + "票");
}
}
public static void main(String[] args) {
TestSleep testThread2 = new TestSleep();
new Thread(testThread2, "1").start();
new Thread(testThread2, "2").start();
new Thread(testThread2, "3").start();
}
}
/**
* 模拟倒计时
*
* @author Administrator
*/
public class TestSleepTwo {
public static void main(String[] args) {
try {
countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 模拟倒计时方法
*/
public static void countDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
线程礼让
礼让线程,让当前正在执行的线程暂停,但不阻塞。
将线程从运行状态转为就绪状态。
让cpu重新调度,礼让不一定成功,看cpu心情。
package com.demo.demo.demo01;
/**
* 测试礼让线程,礼让不一定成功
*
* @author Administrator
*/
public class TestYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始------");
//礼让
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程结束------");
}
public static void main(String[] args) {
TestYield t = new TestYield();
new Thread(t,"a").start();
new Thread(t,"b").start();
}
}
线程强制执行
Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞,可以想象成插队
package com.demo.demo.demo01;
/**
* 测试join方法
*
* @author Administrator
*/
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("插队线程" + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin join = new TestJoin();
Thread thread = new Thread(join);
//主线程
for (int i = 0; i < 1000; i++) {
if (i==200){
thread.start();
//线程插队
thread.join();
}
System.out.println("main"+i);
}
}
}
线程状态观测
线程可以处于一下的任意状态中:
NEW:线程尚未启动处于此状态。
Runnable:在java虚拟机中执行的线程处于此状态。
BLOCKED:被阻塞等监视器锁定的线程处于此状态。
WAITING: 正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING: 正在等待另一个线程执行动作达到指定的等待时间的线程处于此状态。
TERMINATED: 已退出的线程处于此状态。
一个线程可以在给定的时间点处于一个状态。这些状态是不反应任何操作系统线程状态的虚拟机状态。
package com.demo.demo.demo01;
/**
* 管擦线程状态
*
* @author Administrator
*/
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("//");
});
//观察状态
//new状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动后
thread.start();
//run状态
state = thread.getState();
System.out.println(state);
//只有线程不终止就一直输出
while (!state.equals(Thread.State.TERMINATED)) {
Thread.sleep(100);
//更新状态
state = thread.getState();
System.out.println(state);
}
}
}
线程的优先级
java提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程优先级用数字表示,范围从1-10
Thread.MIN_PRIORITY=1;
Thread.MAX_PRIORITY=10;
Thread.NORM_PRIORITY=5;
使用一下方式改变或获取线程优先级
getPriority(), setPriority(int xxx);
线程优先级大小只意味着获得的调度概率,并不是优先级高就会百分之百的每次调度优先级高的。
package com.demo.demo.demo01;
/**
* 测试线程优先级
*
* @author Administrator
*/
public class TestPriority {
public static void main(String[] args) {
System.out.println("主线程名字:" + Thread.currentThread().getName() + "主线程默认优先级:" + Thread.currentThread().getPriority());
Priority p = new Priority();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
Thread t4 = new Thread(p);
Thread t5 = new Thread(p);
Thread t6 = new Thread(p);
//设置线程优先级
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
//10优先级
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
t5.setPriority(Thread.MIN_PRIORITY);
t5.start();
t6.setPriority(Thread.NORM_PRIORITY);
t6.start();
}
}
class Priority implements Runnable {
@Override
public void run() {
System.out.println("线程名字:" + Thread.currentThread().getName() + "线程优先级:" + Thread.currentThread().getPriority());
}
}
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如后台记录操作日志,监控内存,垃圾回收等。
package com.demo.demo.demo01;
/**
* 测试守护线程
*
* @author Administrator
*/
public class TestDaemon {
public static void main(String[] args) {
Test2 test2=new Test2();
Test test=new Test();
Thread thread=new Thread(test2);
//默认是false表示是用户线程
thread.setDaemon(true);
thread.start();
//用户线程
new Thread(test).start();
}
}
class Test implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("用户线程执行中");
}
System.out.println("用户线程死亡");
}
}
class Test2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("守护线程一直执行");
}
}
}
线程同步机制
并发:同一个对象被多个线程同时操作
线程同步其实就是一个等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
线程同步需要队列加锁才能保持线程同步的安全性。
由于同一进程多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在问题:
一个线程持有锁会导致其他所需要此锁的线程挂起;
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
package com.demo.syn;
/**
* 不安全的银行
*
* @author Administrator
*/
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100, "账户");
Drawing user1=new Drawing(account,50,"用户1");
Drawing user2=new Drawing(account,100,"用户2");
user1.start();
user2.start();
}
}
/**
* 账户
*/
class Account {
//余额
Integer money;
//卡号
String name;
public Account(Integer money, String name) {
this.money = money;
this.name = name;
}
}
/**
* 银行取款不安全
*/
class Drawing extends Thread {
//账户
Account account;
//取了多少钱
Integer drawingMoney;
//现在多少钱
Integer nowMoney=0;
public Drawing(Account account, Integer drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//判断是否有钱
if (account.money-drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足,取钱失败");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
nowMoney = drawingMoney + nowMoney;
System.out.println("账户:" + account.name + "还剩余余额:" + account.money);
System.out.println(this.getName() + "手里的钱:" + nowMoney);
super.run();
}
}
package com.demo.syn;
/**
* 不安全的买票
* 线程不安全有负数
*
* @author Administrator
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
Thread thread = new Thread(buyTicket, "小红");
Thread thread2 = new Thread(buyTicket, "小明");
Thread thread3 = new Thread(buyTicket, "黄牛");
thread.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable {
//总票数
private Integer number = 10;
//外部停止方法
private Boolean flag = true;
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 买票方法
*/
private void buy() throws InterruptedException {
if (number <= 0) {
flag = false;
return;
}
//模拟延迟
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "拿到了第" + number--);
}
}
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法去访问,所以我们需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括2两种用法:synchronized方法和synchronized块。
方法 public synchronized method(int args){}
synchronized方法控制对“对象”访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该对象的锁才能执行,否者线程阻塞,方法一旦执行就会独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法申明未synchronized将会影响效率。
方法种需要修改的资源才需要锁锁的太多浪费资源。
同步块:synchronized(Obj){}
Obj称之为同步监视器
Obj可以是任意对象,但是推荐使用共享资源作为同步监视器。
同步方法种无须同步监视器,同步方法的同步监视器结束this,就是对象本身,或者是class
同步监视器的执行过程:
第一个线程访问,锁定同步监视器,执行其中代码。
第二个线程访问,发现同步监视器锁定,无法访问。
第一个线程访问完毕,解锁同步监视器。
第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
同步方法:
package com.demo.syn;
/**
* 安全的买票
*
*
* @author Administrator
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
Thread thread = new Thread(buyTicket, "小红");
Thread thread2 = new Thread(buyTicket, "小明");
Thread thread3 = new Thread(buyTicket, "黄牛");
thread.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable {
//总票数
private Integer number = 10;
//外部停止方法
private Boolean flag = true;
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 买票同步方法(synchronized)
*/
private synchronized void buy() throws InterruptedException {
if (number <= 0) {
flag = false;
return;
}
//模拟延迟
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "拿到了第" + number--);
}
}
同步块
package com.demo.syn;
/**
* 安全的银行
*
* @author Administrator
*/
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(400, "账户");
Drawing user1 = new Drawing(account, 100, "用户1");
Drawing user2 = new Drawing(account, 200, "用户2");
user1.start();
user2.start();
}
}
/**
* 账户
*/
class Account {
//余额
Integer money;
//卡号
String name;
public Account(Integer money, String name) {
this.money = money;
this.name = name;
}
}
/**
* 银行取款安全同步块
*/
class Drawing extends Thread {
//账户
Account account;
//取了多少钱
Integer drawingMoney;
//现在多少钱
Integer nowMoney = 0;
public Drawing(Account account, Integer drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//锁的对象是变化的量
synchronized (account) {
//判断是否有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "余额不足,取钱失败");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money - drawingMoney;
nowMoney = drawingMoney + nowMoney;
System.out.println("账户:" + account.name + "还剩余余额:" + account.money);
System.out.println(this.getName() + "手里的钱:" + nowMoney);
}
}
}
死锁
多个线程各自占有一些共享资源,并且相互等待其他线程占有占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有两个以上对象的锁时,就可能发生死锁问题
产生死锁的四个必要条件:
1:互斥条件:一个资源每次只能被一个线程访问
2:请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不妨。
3:不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。
4:循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
package com.demo.syn;
/**
* 死锁测试类
*
* @author Administrator
*/
public class TestDeadLock {
public static void main(String[] args) {
//会导致死锁程序卡死
Test3 test = new Test3(0, "第一个");
Test3 test2 = new Test3(1, "第二个");
test.start();
test2.start();
}
}
//测试类1
class Test {
}
//测试类2
class Test2 {
}
class Test3 extends Thread {
//需要的资源只有一份所以用static来保证
static Test test = new Test();
static Test2 test2 = new Test2();
//选择
int choice;
//名称
String name;
public Test3(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//互相持有对方的锁
private void test() throws InterruptedException {
if (choice == 0) {
//获得test的锁
synchronized (test) {
System.out.println(this.name + "获得了test的锁");
Thread.sleep(1000);
//一秒钟后获得test2的锁
synchronized (test2) {
System.out.println("获得test2的锁");
}
}
} else {
synchronized (test2) {
System.out.println(this.name + "获得了test2的锁");
Thread.sleep(2000);
//两秒钟后获得test的锁
synchronized (test) {
System.out.println("获得test的锁");
}
}
}
}
}
Lock锁
从jdk5.0开始,java提供了更强大的线程同步机制-通过显式定义同步锁对象来实现同步,同步锁使用Lock对象来充当
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能由一个线程多Lock对象进行加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁。
package com.demo.syn;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试lock锁
*
* @author Administrator
*/
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock = new TestLock2();
new Thread(testLock,"1").start();
new Thread(testLock,"2").start();
new Thread(testLock,"3").start();
}
}
class TestLock2 implements Runnable {
int number = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//加锁
lock.lock();
try {
if (number <= 0) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+number--);
} finally {
//解锁
lock.unlock();
}
}
}
}
synchronized和Lock的对比
Lock是显式锁(手动开启和关闭,一定要关闭)synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized具有代码锁和方法锁。
使用Lock锁,jvm将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性。
优先使用顺序
Lock>同步代码块>同步方法
线程协作
生产者消费者模式:
假设仓库中只能存放一件产品,生产者将产品生产出来放入仓库,消费者将仓库中产品取走消费。
如果仓库中没有产品,则生产者将产品放入仓库,否者就停止生产并等待,直到仓库中的产品被消费者取走为止。
如果仓库中放有产品,则消费者可以将产品取走消费,否者停止消费并等待,直到仓库中再次放入产品为止
这是一个线程同步的问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件:
对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费。
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
在生产者消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同步一个共享资源,实现同步
synchronized不能实现不同线程之间的消息传递(通信)
线程通信
方法名 | 作用 |
---|---|
wait() | 表示线程会一直等待,直到其他线程通知,与sleep不同,会释放锁。 |
wait(long timeout) | 指定等待毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程先调度 |
注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否者会抛出异常lllegalMonitorStateException
并发协作模型:“生产者/消费者模式”–>管程法
生产者:负责生产数据的模块(方法,对象,线程等)
消费者:负责处理数据的模块(方法,对象,线程等)
缓冲区:消费者不能直接使用生产者的数据,它们之间有个缓冲区,生产者吧生产好的数据放入缓存区,消费者从缓冲区拿出数据。
package com.demo.syn;
/**
* 生产者消费者模式->管程法
*
* @author Administrator
*/
public class TestPc {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Productor(synContainer).start();
new Consumcr(synContainer).start();
}
}
/**
* 生产者类
*
* @author Administrator
*/
class Productor extends Thread {
SynContainer synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
//生产
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synContainer.push(new Chicken(i));
System.out.println("生产了" + i + "只鸡");
}
}
}
/**
* 消费者类
*
* @author Administrator
*/
class Consumcr extends Thread {
SynContainer synContainer;
public Consumcr(SynContainer synContainer) {
this.synContainer = synContainer;
}
//消费
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("消费了-->" + synContainer.pop().id + "只鸡");
}
}
}
/**
* 产品
*
* @author Administrator
*/
class Chicken {
//产品编号
int id;
public Chicken(int id) {
this.id = id;
}
}
/**
* 缓冲区
*/
class SynContainer {
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果容器满了就需要等待消费者消费如果没满就丢入产品
while (count == chickens.length) {
//通知消费者消费,生产等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
chickens[count] = chicken;
count++;
//可以通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop() {
while (count == 0) {
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费
count--;
Chicken chicken = chickens[count];
//通知生产者生产
this.notifyAll();
return chicken;
}
}
并发协作模型:“生产者/消费者模式”–>信号灯法
使用标识符来通信
package com.demo.syn;
/**
* 生产者消费者模式->信号灯法,标志位解决
*
* @author Administrator
*/
public class TestPc2 {
public static void main(String[] args) {
Tv tv=new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
/**
* 生产者->演员
*
* @author Administrator
*/
class Player extends Thread {
Tv tv;
public Player(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
this.tv.play("猫和老鼠");
} else {
this.tv.play("广告-------------");
}
}
}
}
/**
* 消费者-》观众
*
* @author Administrator
*/
class Watcher extends Thread {
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
/**
* 产品->节目
*
* @author Administrator
*/
class Tv {
//节目名称
String voice;
//演员表演,观众等待为true,演员等待观众观看为false
boolean flag = true;
//表演
public synchronized void play(String voice) {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了->" + voice);
this.voice = voice;
//通知观众观看
this.notifyAll();
this.flag = !this.flag;
}
//观看
public synchronized void watch() {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了" + voice);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
JUC并发编程
JUC是java.util包下的concurrent工具类提供了并发编程的很多方法。
wait和sleep的区别
1.来自不同的类
wait来自Object类,sleep来自Thread类
2.关于锁的释放
wait会释放锁,sleep不会释放锁
3.使用范围不同
wati只能在同步代码块中使用
sleep可以在任何地方使用
深入Lock锁
ReentrantLock是可重入锁,ReentrantReadWriteLock.ReadLock是读锁,ReentrantReadWriteLock.WriteLock是写锁
公平锁:公平先来后到
非公平锁:可以插队
ReenTrantLock默认是非公平锁
Synchronized 和Lock 区别
1.Synchronized是内置的java关键字Lock是一个java接口
2.Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.Synchronized会自动释放锁,Lock必须手动释放锁,如果不释放锁会导致死锁
4.Synchronized 一个线程获得了锁另一个线程必须等待下去;Lock不会一直等待下去因为Lock中有tryLock()方法会尝试获取锁;
5.Synchronized 可重入锁,并且不可中断的,非公平锁;Lock是可重入,可以判断锁,可以自己设置是否为公平锁
6.Synchronized 适合锁少量的同步代码,Lock可以锁大量的同步代码。
7.Synchronized 可以是同步方法也可以是同步代码块,Lock锁只能是同步代码块。
生产者消费者问题
Synchronized版本
使用Synchronized锁可以使用 wait notify来实现生产者消费者模式
package com.juc.demo.juc.pc;
/**
* 线程通信问题 1:等待唤醒2:通知唤醒
*
* @author Administrator
*/
public class Test {
public static void main(String[] args) {
Number number = new Number();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
number.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
number.decrement();
}
}, "B").start();
}
}
/**
* 数字资源类
*/
class Number {
private int number = 0;
/**
* +1操作
*/
public synchronized void increment() {
//当前的线程必须拥有该对象的显示器。 该线程释放此监视器的所有权,并等待另一个线程通知等待该对象监视器的线程通过调用notify方法或notifyAll方法notifyAll 。 然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。 像在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在循环中使用:
while (number != 0) {
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
this.notifyAll();
}
/**
* -1操作
*/
public synchronized void decrement() {
//当前的线程必须拥有该对象的显示器。 该线程释放此监视器的所有权,并等待另一个线程通知等待该对象监视器的线程通过调用notify方法或notifyAll方法notifyAll 。 然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。 像在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在循环中使用:
while (number == 0) {
//等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知
this.notifyAll();
}
}
juc版本
Condition
因素出Object
监视器方法(wait
,notify
和notifyAll
)成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock
个实现。Lock
替换synchronized
方法和语句的使用,Condition
取代了对象监视器方法的使用。
package com.juc.demo.juc.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程通信问题 1:等待唤醒2:通知唤醒
*
* @author Administrator
*/
public class Test {
public static void main(String[] args) {
Number number = new Number();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
number.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
number.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
number.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
number.decrement();
}
}, "D").start();
}
}
/**
* 数字资源类
*/
class Number {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
/**
* +1操作
*/
public void increment() {
lock.lock();
try {
while (number != 0) {
try {
//等待
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* -1操作
*/
public void decrement() {
lock.lock();
try {
while (number == 0) {
try {
//等待
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition的指定唤醒
package com.juc.demo.juc.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition精准通知 A执行完调用B,B执行完调用C,C执行完调用A
*
* @author Administrator
*/
public class Test2 {
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();
//nb 为0执行a,nb为1执行b,nb为2执行c
private int number = 1;
public void printA() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "->AAAAAAAAAA");
//唤醒指定的B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "->BBBBBBBBB");
//唤醒指定的C
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "->CCCCCCCCCCCC");
//唤醒指定的A
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
集合类不安全
CopyOnWriteArrayList
在多线程下List是不安全所以我们得解决多线程下集合不安全的问题;
package com.juc.demo.juc.list;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Administrator
*/
public class TestList {
public static void main(String[] args) {
/*将list转换为线程安全
1.List<String> list=new Vector();
2.List<String> list =Collections.synchronizedList(new ArrayList<>());
3.List<String> list = new CopyOnWriteArrayList<>();
CopyOnWrite 写入时复制 cow 计算机程序设计领域的一种优化策略
CopyOnWriteArrayList底层是使用的lock锁实现而Vector是使用synchronized实现的所以vector的效率比从CopyOnWriteArrayList慢
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString());
System.out.println(list);
}).start();
}
}
}
CopyOnWriteArraySet
在多线程下Set是不安全所以我们得解决多线程下集合不安全的问题;
package com.juc.demo.juc.list;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author Administrator
*/
public class TestSet {
public static void main(String[] args) {
/*将Set转换为线程安全
1.List<String> list =Collections.synchronizedList(new HashSet<>());
2.List<String> list = new CopyOnWriteArraySet<>();
*/
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
HashSet的底层是什么
在源代码中HashSet底层就是HashMap
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
//add方法本质是map的key因为map的key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
ConcurrentHashMap
在jdk官方文档中解释了什么是ConcurrentHashMap
-
支持检索的完全并发性和更新的高预期并发性的哈希表。这个类服从相同功能规范如
Hashtable
,并且包括对应于每个方法的方法版本Hashtable
。不过,尽管所有操作都是线程安全的,检索操作并不意味着锁定,并没有为防止所有访问的方式锁定整个表的任何支持。这个类可以在依赖于线程安全性的程序中与Hashtable
完全互Hashtable
,但不依赖于其同步细节。检索操作(包括
get
)通常不阻止,因此可能与更新操作重叠(包括put
和remove
)。 检索反映了最近完成的更新操作的结果。 (更正式地,对于给定密钥的更新操作熊之前发生与任何(非空关系)检索该键报告经更新的值。)对于聚合操作,比如putAll
和clear
,并发检索可能反映插入或移除只有一些条目。 类似地,迭代器,分割器和枚举返回在反映迭代器/枚举创建过程中或之后反映哈希表状态的元素。 他们不抛出ConcurrentModificationException
。 然而,迭代器被设计为一次只能由一个线程使用。 请记住,骨料状态方法的结果,包括size
,isEmpty
和containsValue
通常是有用的,只有当一个地图没有发生在其他线程并发更新。 否则,这些方法的结果反映了可能足以用于监视或估计目的的瞬态状态,但不适用于程序控制。当存在太多的冲突(即,具有不同的哈希码但是以表的大小为模数落入相同的时隙的密钥)时,该表被动态扩展,并且每个映射保持大致两个bin的预期平均效果(对应于0.75负载因素阈值调整大小)。 由于映射被添加和删除,这个平均值可能会有很大差异,但是总的来说,这为哈希表保留了普遍接受的时间/空间权衡。 然而,调整这个或任何其他类型的散列表可能是相对较慢的操作。 如果可能,最好提供一个尺寸估计作为可选的
initialCapacity
构造函数参数。 附加的可选的loadFactor
构造函数参数提供了另外的手段,通过指定在计算给定数量的元素时要分配的空间量时使用的表密度来定制初始表容量。 此外,为了与此类的先前版本兼容,构造函数可以可选地指定预期的concurrencyLevel
作为内部大小调整的附加提示。 请注意,使用完全相同的许多键hashCode()
是降低任何哈希表的hashCode()
的一种可靠的方法。 为了改善影响,当按键为Comparable
时,该类可以使用键之间的比较顺序来帮助打破关系。甲
Set
投影一个的ConcurrentHashMap可以(使用被创建newKeySet()
或newKeySet(int)
),或观察(使用keySet(Object)
时仅键是感兴趣的,并且被映射的值是(可能瞬时)不使用或全部取相同的映射值。
HashMap在多线程下也是不安全的
package com.juc.demo.juc.list;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Administrator
*/
public class TestMap {
public static void main(String[] args) {
/**
* map变为线程安全
* map初始有加载因子,和初始化容量
* 默认等价是什么 new HashMap<String, String>(16,0.75)
* 1.Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
*2.Map<String, String> map = new ConcurrentHashMap<>();
*/
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString());
System.out.println(map);
}).start();
}
}
}
map底层默认参数
callable
-
Callable
接口类似于Runnable
,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,ARunnable
不返回结果,也不能抛出被检查的异常。该
Executors
类包含的实用方法,从其他普通形式转换为Callable
类。callable可已有返回值,callable可以抛出异常,callable使用的是call()方法.
package com.juc.demo.juc.list; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestCallable { public static void main(String[] args) throws ExecutionException, InterruptedException { MyThread myThread = new MyThread(); //适配类 FutureTask futureTask = new FutureTask(myThread); //只会打印一个call结果会被缓存 new Thread(futureTask).start(); new Thread(futureTask).start(); //get方法可能会会产生阻塞,吧方法 放到最后或者通过异步通信 String a= (String) futureTask.get(); System.out.println(a); } } class MyThread implements Callable<String> { @Override public String call() throws Exception { System.out.println("call"); return "123"; } }
常用的辅助类
CountDownLatch(减法计数器)
-
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
A
CountDownLatch
用给定的计数初始化。await
方法阻塞,直到由于countDown()
方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await
调用立即返回。 这是一个一次性的现象 - 计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier
。A
CountDownLatch
是一种通用的同步工具,可用于多种用途。 一个CountDownLatch
为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await
在门口等待,直到被调用countDown()
的线程打开。 一个CountDownLatch
初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。CountDownLatch
一个有用的属性是,它不要求调用countDown
线程等待计数到达零之前继续,它只是阻止任何线程通过await
,直到所有线程可以通过。package com.juc.demo.juc; import java.util.concurrent.CountDownLatch; /** * 计数器 * * @author Administrator */ public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { //倒计时总数是6必须要执行任务时才使用 CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i < 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "走了"); //减一操作 countDownLatch.countDown(); }, String.valueOf(i)).start(); } //等待计数器归0,然后在向下执行 countDownLatch.await(); System.out.println("关门"); } }
CyclicBarrier(加法计数器)
-
允许一组线程全部等待彼此达到共同屏障点的同步辅助。循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
A
CyclicBarrier
支持一个可选的Runnable
命令,每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程释放之前。 在任何一方继续进行之前,此屏障操作对更新共享状态很有用。package com.juc.demo.juc; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * 加法计数器 * * @author Administrator */ public class TestCyclicBarrier { public static void main(String[] args) { //7条线程执行完毕后执行一个新的线程 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { System.out.println("成功"); }); for (int i = 0; i < 7; i++) { int finalI = i; new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "收集了" + finalI); //等待 cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
Semaphore(信号量)
- 一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个
acquire()
都会阻塞,直到许可证可用,然后才能使用它。 每个release()
添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象;Semaphore
只保留可用数量的计数,并相应地执行。
-
package com.juc.demo.juc;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author Administrator
*/
public class TestSemaphore {
public static void main(String[] args) {
//线程数量
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
//得到 semaphore.acquire();
//释放 semaphore.release();
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
}).start();
}
}
}
读写锁
ReadWriteLock读的时候可以被多个线程读取写入时只能被一个线程写入
package com.juc.demo.juc;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁
* 独占锁(写锁)一次只能被一个线程占有
* 共享锁(读锁)多个线程可以同时占有
* @author Administrator
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
//写入
myCache.put(finalI + "", finalI + "");
}, String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
//读取
myCache.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache {
//读写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private volatile Map<String, Object> map = new HashMap<>();
//存 写入的时候只希望只有一个线程写
public void put(String key, String value) {
//加锁
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入-》" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
//取 所有线程都可以读
public void get(String key) {
//读锁
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取-》" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取ok-》");
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
}
阻塞队列
BlockingQueue
写入:如果对列满了,就必须阻塞等待。
读取:如果队列是空的,必须阻塞等待生产。
多线程和线程池下使用BlockingQueue较多。
方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer() |
移除 | remove() | poll() | take() | poll() |
判断队列首 | element() | peek() | - | - |
package com.juc.demo.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class TestBq {
public static void main(String[] args) {
test1();
}
/**
* 抛出异常
*/
public static void test1(){
//初始值对列大小
ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("a"));
//java.lang.IllegalStateException: Queue full 抛出异常
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
//java.util.NoSuchElementException没有元素
//System.out.println(arrayBlockingQueue.remove());
}
}
package com.juc.demo.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class TestBq {
public static void main(String[] args) {
test1();
}
/**
* 不抛出异常
*/
public static void test1(){
//初始值对列大小
ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
//返回false 不抛出异常
// System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
//返回null,不抛异常
// System.out.println(arrayBlockingQueue.poll());
}
}
package com.juc.demo.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class TestBq {
public static void main(String[] args) {
test1();
}
/**
* 查看队首元素 element
*/
public static void test1(){
//初始值对列大小
ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.poll());
//查看队首元素
System.out.println("---0+"+arrayBlockingQueue.element());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
}
}
package com.juc.demo.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class TestBq {
public static void main(String[] args) {
test1();
}
/**
* 查看队首元素 peek
*/
public static void test1(){
//初始值对列大小
ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.poll());
//查看队首元素
System.out.println("---0+"+arrayBlockingQueue.peek());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
}
}
package com.juc.demo.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class TestBq {
public static void main(String[] args) throws InterruptedException {
test1();
}
/**
*等待阻塞(一直阻塞)
*/
public static void test1() throws InterruptedException {
//初始值对列大小
ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);
//一直阻塞
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
//对列没位置了会一直阻塞
//arrayBlockingQueue.put("d");
//取出值
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
//没有元素会一直阻塞
//System.out.println(arrayBlockingQueue.take());
}
}
package com.juc.demo.juc.bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class TestBq {
public static void main(String[] args) throws InterruptedException {
test1();
}
/**
* 等待阻塞(一直阻塞)
*/
public static void test1() throws InterruptedException {
//初始值对列大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3);
//一直阻塞
arrayBlockingQueue.offer("a");
arrayBlockingQueue.offer("b");
arrayBlockingQueue.offer("c");
//如果前面是满的就等待2s否者就退出
// arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS);
arrayBlockingQueue.poll();
arrayBlockingQueue.poll();
arrayBlockingQueue.poll();
arrayBlockingQueue.poll(2, TimeUnit.SECONDS);
}
}
同步队列
没有容量,进去一个元素必须等待取出来之后,才能再放一个元素。
package com.juc.demo.juc.bq;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步对列 SynchronousQueue不存储多个元素只要put了元素就必须take取出来否者就不能put进值
* @author Administrator
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<String>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "put1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "get1"+ synchronousQueue.take()); TimeUnit.SECONDS.sleep(2);
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "get2"+ synchronousQueue.take()); TimeUnit.SECONDS.sleep(2);
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "get3"+ synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"2").start();
}
}
线程池
线程池:3大方法。7大参数,4种拒绝策略。
进程创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
可以创建好多个线程,放入线程池中使用时直接获取,使用完毕后放回池中,可以避免频繁创建销毁,实现重复利用。
好处:提高响应速度,降低资源消耗,便于管理,corePoolSize:核心池的大小,maximumPoolSize:最大线程数,keepAliveTime:线程没有任务时最多保持多长时间终止。
jdk5.0提供了线程池相关的api:ExecutorService和Executors
ExecutorService:
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Futuresubmit(Callabletask):执行任务,有返回值,一般用来执行Callable
void shutdown():关闭连接池
Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
在阿里巴巴代码规范中禁止使用Executors直接创建线程池,而是通过ThreadPoolExecutor的方式创建,因为Executors有弊端如FixedThreadPool和SingThreadPoll允许请求的对列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致oom。
CachedThreadPool和ScheduledThreadPool允许创建线程数量为Integer.MAX_VALUE,可能会导致大量的线程,从而导致oom。
3大方法:
package com.juc.demo.juc.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Executors 工具类,3大方法
*
* @author Administrator
*/
public class DemoExecutor {
public static void main(String[] args) {
//单个线程
// ExecutorService pool = Executors.newSingleThreadExecutor();
//创建固定的线程池大小
//ExecutorService pool =Executors.newFixedThreadPool(5);
//可伸缩的
ExecutorService pool= Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
//使用线程池来创建
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
} finally {
pool.shutdown();
}
}
}
7大参数
newSingleThreadExecutor源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newFixedThreadPool源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
源码看出创建线程池都是使用的ThreadPoolExecutor。
ThreadPoolExecutor源码:
//7大参数
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大的线程池大小
long keepAliveTime,//超时时间没人调用就释放
TimeUnit unit,//超时时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂创建线程一般不用动
RejectedExecutionHandler handler//拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
4种拒绝策略
package com.juc.demo.juc.pool;
import java.util.concurrent.*;
/**
* Executors 工具类,3大方法
* ThreadPoolExecutor.AbortPolicy() 当线程超过最大线程加阻塞队列后将抛出异常 java.util.concurrent.RejectedExecutionException
* ThreadPoolExecutor.CallerRunsPolicy() 当线程超过最大线程加阻塞队列后将用main线程执行
* ThreadPoolExecutor.DiscardPolicy() 当线程超过最大线程加阻塞队列后不会抛出异常并且不会执行
* ThreadPoolExecutor.DiscardOldestPolicy() 当线程超过最大线程加阻塞队列后不会抛出异常并且会和第一个线程竞争如果竞争成功就会执行否者不会执行
* @author Administrator
*/
public class DemoExecutor {
public static void main(String[] args) {
ExecutorService service=new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
for (int i = 0; i < 9; i++) {
//使用线程池来创建
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
} finally {
service.shutdown();
}
}
}
最大线程数到底该如何定义
cpu密集型和IO密集型(调优)
cpu密集型可以使用cpu有几核就是几,可以保持cpu效率 Runtime.getRuntime().availableProcessors()获取cpu的核数
Io密集型 判断程序中十分耗Io的线程设置为大于IO线程数。
函数式接口
函数式接口是只有一个方法的接口。
/**
*函数时接口FunctionalInterface
foreach(消费者类型的函数式接口)
*/
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Function函数式接口
package com.juc.demo.juc.function;
import java.util.function.Function;
/**
* 函数式接口
* 只要是函数式接口就可以使用lambda表达式简化
*/
public class Test {
public static void main(String[] args) {
//工具类
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String s) {
return s;
}
};
Function<String, String> function1 = (str) -> { return str; };
System.out.println(function1.apply("dasd"));
}
}
Predicate断定型接口
package com.juc.demo.juc.function;
import java.util.function.Predicate;
/**
* 断定型接口 Predicate 有一个输入值返回值只能是布尔值。
*
* @author Administrator
*/
public class Test2 {
public static void main(String[] args) {
//判断字符串是否为空
Predicate<String> predicate = String::isEmpty;
System.out.println(predicate.test(""));
}
}
Consumer消费型接口
package com.juc.demo.juc.function;
import java.util.function.Consumer;
/**
* 消费型接口只有输入没有返回值
*
* @author Administrator
*/
public class Test3 {
public static void main(String[] args) {
Consumer<String> consumer = System.out::println;
consumer.accept("a");
}
}
Supplier供给型接口
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020081300401450.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDEwMDc0NA==,size_16,color_FFFFFF,t_70#pic_center)
package com.juc.demo.juc.function;
import java.util.function.Supplier;
/**
* 供给型接口 没有参数只有返回值
* @author Administrator
*/
public class Test4 {
public static void main(String[] args) {
Supplier<String> supplier=()-> "a";
System.out.println(supplier.get());
}
}
Stream流计算
package com.juc.demo.juc.stream;
import java.util.Arrays;
import java.util.List;
/**
*1.id必须是偶数
* 2.年龄必须大于23岁
* 3.用户名转换为大写
* 4.用户字母到这排序
* 5。只输入一个用户
* @author Administrator
*/
public class Test {
public static void main(String[] args) {
User user=new User(1,"a",21);
User user2=new User(2,"b",22);
User user3=new User(3,"c",23);
User user4=new User(4,"d",24);
User user5=new User(6,"e",25);
//储存
List<User> list= Arrays.asList(user,user2,user3,user4,user5);
//计算交给Stream流 链式编程
list.stream().filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
}
}
ForkJoin
分支合并,ForkJoin是jdk1.7出来的,主要是并行执行任务,提高效率,大量数据下使用,大数据:Map Reduce(把大任务拆分 多个子任务)
ForkJoin特点:工作窃取,维护的是个双端队列。
package com.juc.demo.juc.stream;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* @author Administrator
*/
public class Test2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test();//3563
//test2();//6597
test3();//144
}
public static void test() {
long start = System.currentTimeMillis();
long sum = 0L;
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum:" + sum + "时间:" + (end - start));
}
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoin forkJoin = new ForkJoin(1L, 10_0000_0000L);
//执行任务没结果
forkJoinPool.execute(forkJoin);
//提交任务有结果
ForkJoinTask<Long> forkJoinTask = forkJoinPool.submit(forkJoin);
Long sum = forkJoinTask.get();
long end = System.currentTimeMillis();
System.out.println("sum:" + sum + "时间:" + (end - start));
}
public static void test3() {
long start = System.currentTimeMillis();
//stream并行流
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum:" + sum + "时间:" + (end - start));
}
}
异步回调
Future 设计的初衷是对将来的某个事件的结果进行建模
package com.juc.demo.juc.fture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 异步 CompletableFuture
*
* @author Administrator
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// //没有返回值的异步回调
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("a");
// });
// System.out.println("-------------------");
// //获取阻塞执行结果
// completableFuture.get();
// 有返回值的异步回调
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a");
return 1024;
});
System.out.println("-------------------");
//获取阻塞执行结果
Integer i=completableFuture.get();
System.out.println(i);
}
}
JMM
JMM:java内存模型,是一个不存在的东西,概念或者约定。
关于jmm的一些同步的约定
1.线程在解锁前,必须吧共享变量立刻刷新回主存
2.线程加锁前,必须读取主存的最新值到工作内存中。
3.加锁和解锁是同一把锁。
线程分为工作内存和主内存
jmm中有8种操作
lock(锁定)
unlock(解锁)
read(读取)
load(加载)
use(使用)
assign(赋值)
store(存储)
write(写入)
jmm对八种指令的使用规则:
不允许read和Load,store和write操作之一单独出现,必须一起使用。
不允许线程丢弃他最近的assing操作,即工作变量的数据改变之后必须告知主内存。
不允许一个线程将没有assing的数据从工作内存同步到主内存
一个新的变量必须要主内存中产生,不允许工作内存直接使用一个未初始化的变量
一个变量同一时间只有一条线程能进行lock,多次lock后必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作操作初始化变量的值。
如果一个变量没被lock,就不能进行unlock操作,也不能unlock一个被其他线程锁住的变量
对一个变量进行unlok操作之前,就必须吧此变量同步回主内存/
Volatile
Volatile是java虚拟机提供的一个轻量级的同步机制
1.保证可见性
package com.juc.demo.juc.v;
import java.util.concurrent.*;
/**
* jmm
*
* @author Administrator
*/
public class Test {
//不加volatile程序就会死循环,加了volatile保持可见性
private static volatile int num = 0;
public static void main(String[] args) {
ExecutorService service = new ThreadPoolExecutor(1, 2, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
service.execute(() -> {
while (num == 0) {
}
});
service.shutdown();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2.不保证原子性
package com.juc.demo.juc.v;
import java.util.concurrent.*;
/**
* jmm
*
* @author Administrator
*/
public class Test2 {
//volatile不保证原子性
private volatile static int num = 0;
public static void main(String[] args) {
ExecutorService service = new ThreadPoolExecutor(5, 22,2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= 20; i++) {
service.execute(() -> {
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
});
}
service.shutdown();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
public static void add() {
num++;
}
}
使用原子类来保证原子性
package com.juc.demo.juc.v;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* jmm
*
* @author Administrator
*/
public class Test2 {
//volatile不保证原子性
private volatile static AtomicInteger num = new AtomicInteger();
public static void main(String[] args) {
ExecutorService service = new ThreadPoolExecutor(5, 22,2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= 20; i++) {
service.execute(() -> {
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
});
}
service.shutdown();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
public static void add() {
//num++;
//+1方法 使用的是cas
num.getAndIncrement();
}
}
3.禁止指令重排
指令重排:写的程序计算机并不是按照自己写的那样执行的。
源代码->编译器优化的重排->指令并行也可能会重排->内存系统也会重排->执行
内存屏障,cpu指令
1.保证特定的操作执行顺序
2.可以保证某些变量的内存可见性
CAS
CAS是cpu的并发原语。
package com.juc.demo.juc.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Administrator
*/
public class Test {
public static void main(String[] args) {
AtomicInteger a = new AtomicInteger(2020);
//比较并交换 public final boolean compareAndSet(int expect//期望, int update//更新) {
a.compareAndSet(2020,2021);
}
}
unsafe底层源码:
CAS:比较当前工作内存中的值和主内存中的值如果当前值是期望的,那么就执行操作,如果不是就一直循环。
缺点:
1.循环会耗时。
2.一次性只能保证一个共享变量的原子性
3.会存在ABA问题
ABA问题(狸猫换太子)
package com.juc.demo.juc.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Administrator
*/
public class Test {
public static void main(String[] args) {
AtomicInteger a = new AtomicInteger(2020);
//狸猫换太子的线程
System.out.println(a.compareAndSet(2020, 2021));
System.out.println(a.compareAndSet(2021, 2020));
//比较并交换 public final boolean compareAndSet(int expect//期望, int update//更新) {
System.out.println(a.compareAndSet(2020, 2021));
}
}
原子引用
解决aba问题只需要引入原子引用对应的思想:乐观锁。带版本号的原子操作
package com.juc.demo.juc.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author Administrator
*/
public class Test {
public static void main(String[] args) {
// AtomicInteger a = new AtomicInteger(2020);
//
AtomicStampedReference<Integer> a = new AtomicStampedReference<>(1, 1);
new Thread(()->{
//获取版本号
int stamp=a.getStamp();
System.out.println("a->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.compareAndSet(1, 2, a.getStamp(), a.getStamp() + 1));
System.out.println("a2->"+a.getStamp());
System.out.println(a.compareAndSet(2, 1, a.getStamp(), a.getStamp() + 1));
System.out.println("a3->"+a.getStamp());
},"a").start();
new Thread(()->{
//获取版本号
int stamp=a.getStamp();
System.out.println("b=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("b===="+a.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("b1->"+a.getStamp());
},"b").start();
}
}
锁的理解
1.公平锁,非公平锁
公平锁:不能够插队,先来后到
非公平锁:可以插队,默认都是非公平锁
2.可重入锁(递归锁)
所有的锁都是可重入锁
拿一把锁时会吧里面的锁拿到
package com.juc.demo.juc.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Administrator
*/
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sms();
}, "A").start();
new Thread(() -> {
phone.sms();
}, "B").start();
}
}
class Phone {
Lock lock = new ReentrantLock();
public void sms() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "sms");
//这里也有锁
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.自旋锁
package com.juc.demo.juc.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁
*
* @author Administrator
*/
public class TestSpinlock {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock() {
System.out.println(Thread.currentThread().getName()+"------------------"+atomicReference);
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "===myLock");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
//解锁
public void myUnLock() {
System.out.println(Thread.currentThread().getName()+"myUnLock------------------"+atomicReference);
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "===myUnLock");
System.out.println(Thread.currentThread().getName()+"---------------------------------------------------------"+atomicReference.compareAndSet(thread, null));
System.out.println(Thread.currentThread().getName()+"------------------"+atomicReference);
}
}
class Demo {
public static void main(String[] args) throws InterruptedException {
//底层使用自旋锁cas来实现
TestSpinlock lock = new TestSpinlock();
new Thread(() -> {
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"B").start();
}
}
死锁排查
1.使用jps-l定位进程号
2.使用jstack 进程号 找到死锁