进程和线程
进程值得是cpu管理一个程序,把程序加载进内存,I/O,加载指令
线程 一个进程里可以有多个线程,一个线程里有多个排队等待cup处理的指令
并发和并行
并发指的是cpu处理对个线程里的指令是轮流的,因为有一个调度器的逐渐,windows的时间片最短是20ms所以感觉不到线程轮流执行,也就是微观串行,宏观并行
并行
并行指的是一个多核cpu,几个核心同时处理多个线程,也就是说同一时间处理线程指令
异步和同步
异步指的是调用方不需要等待指令处理结果返回就可以继续执行其他的指令
同步指必须等待这个线程的那个指令结果返回才可以继续执行
//异步与同步代码
单核cpu和多核cpu在处理多线程的区别
单核的cpu处理多线程并不会使效率提高,而且会由于上下文切换使得时间变长
多核cou在处理多线程的时候效率会明显提高,前提是线程创建合理,彼此可以轮流运行
创建和运行线程代码
这里new Thread用到了继承类的匿名内部类,new runable用到了实现接口的匿名内部类
匿名内部类
第一种
推荐使用的先创建runable任务,其最终也会用到run方法
public class Test2 {
public static void main(String[] args) {
/*创建任务对象,和第一种方法相比更加方便因为他将线程和任务的创建分开了*/
/*由于runable接口里有functionInterface,切接口里只有一个方法所以可以被简化成箭头函数*/
Runnable r = () -> {log.debug("running");};
/*任务对象填入第一个参数,第二个参数填入对应的线程名字*/
Thread t = new Thread(r, "t2");
t.start();
}
}
第二种
@Slf4j(topic = "c.test1")
public class test1 {
public static void main(String[] args) {
Thread thread=new Thread(){
@Override
public void run() {
log.debug("{}","100");
}
};
thread.setName("test1");
thread.start();
}
}
windows查看和杀死线程
- tasklist 查看线程
- jsp 查看java线程
- taskkill /F /P PID 强制杀死线程
jconsole连接远程服务器,监测java进程
线程运行原理
栈和栈帧
IDEA利用debug模式运行
JVM会为每一个线程安排一个栈内存存储全局变量
每一个栈内存里有多个栈帧
当调用某个方法时会入栈,存储局部变量
执行完成就出栈
package cn.itcast.n3;
public class TestFrames {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
method1(20);
}
};
t1.setName("t1");
t1.start();
method1(10);
}
private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}
private static Object method2() {
Object n = new Object();
return n;
}
}
JVM堆创建对象,方法区存放方法,CPU调度器管理线程
Thread.start 和Thread.run的区别
执行Thread.run并不会产生新的线程,也做不了异步操作
不能多次start会出异常
当线程sleep之后的state为TIMED_WAITING,当被其他线程打断,会抛出InterruptedException
增加可读性可以使用TimeUnit
yield (让步的意思)
当线程调用yield之后会让出cpu的使用权,但是这不是一定的,当没有其他工作的线程的时候,cpu的调度器组件还是会把cpu分给你,执行之后线程的状态会切换成为runable(就绪状态),cpu想分时间片的时候不会考虑timewait,而会考虑runable
线程优先级
setpriority 从10-1减少,但是只是java给调度器的建议,可能会不被考虑
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) {
Runnable task1 = () -> {
int count = 0;
for (;;) {
System.out.println("---->1 " + count++);
}
};
Runnable task2 = () -> {
int count = 0;
for (;;) {
// Thread.yield();
System.out.println(" ---->2 " + count++);
}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
防止线程占单核cpu100%
让while(true)里有一个sleep这样就不会占过多的cpu资源
join
等待某个线程结束
下面的代码主线程调用t1.join表示主线程等待t1结束,再继续往下面执行
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test10")
public class Test10 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
}
非常典型的同步
那么如何等待多个线程呢
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
log.debug("join begin");
t2.join();
log.debug("t2 join end");
t1.join();
log.debug("t1 join end");
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
这里t1.join不需要等待,因为t2.join已经结束了
join里面的参数可以为时间
interrupt
打断阻塞状态的线程
会抛出异常,并且打断的异常标记为假
可以使用isInterrupted获取打断标记,正常运行的线程的打断标记为true
然后该线程可以在内部做判断,处理后事之后break
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted) {
log.debug("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
}
两阶段终止模式
stop,会强制杀死线程
System.exit(id)会杀死整个进程
isInterrupted会返回线程当前的打断标记不会清除
interrupted返回后清除打断标记
interrupt,interrupted,isInterrupted
interrupt()是用来设置中断状态的。返回true说明中断状态被设置了而不是被清除了。我们调用sleep、wait等此类可中断(throw InterruptedException)方法时,一旦方法被interrupt打断抛出InterruptedException,当前调用该方法的线程的中断状态就会被jvm自动清除了,就是说我们调用该线程的isInterrupted 方法时是返回false。如果你想保持中断状态,可以再次调用interrupt方法设置中断状态。这样做的原因是,java的中断并不是真正的中断线程,而只设置标志位(中断位)来通知用户。如果你捕获到中断异常,说明当前线程已经被中断,不需要继续保持中断位。
interrupted是静态方法,返回的是当前线程的中断状态。例如,如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调用interrupted方法,第一次会返回true。然后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。如果你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其他操作清除了。
LockSupport.park 仅当访问标记为假的时候有效,这个线程会阻塞当前线程的后续指令
public static void main(String[] args) throws InterruptedException {
// test4();
Thread thread=new Thread(()->{
log.debug("开始thread线程");
LockSupport.park();
log.debug("park方法被打断了");
log.debug("{}",Thread.currentThread().isInterrupted());
LockSupport.park();
log.debug("第二个park方法起作用了吗");
},"t1");
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
thread线程被park方法停下来了,当设置打断标记之后park才被放开,并且之后的park方法会失效,因为打断标记为true,除非利用interrupted设置
守护线程
当非守护线程结束的时候即使守护线程没有结束,也会被强制结束
thread.setDaemon(true)
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
break;
}
}
log.debug("结束");
}, "t1");
t1.setDaemon(true);
t1.start();
Thread.sleep(1000);
log.debug("结束");
}
}
士兵执行完了,将军
相对于操作系统有
五种状态
相对于javaAPI六种状态
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
简单例子
package cn.itcast.n3;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@Slf4j(topic = "c.TestState")
public class TestState {
public static void main(String[] args) throws IOException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.debug("running...");
}
};
Thread t2 = new Thread("t2") {
@Override
public void run() {
while(true) { // runnable
}
}
};
t2.start();
Thread t3 = new Thread("t3") {
@Override
public void run() {
log.debug("running...");
}
};
t3.start();
Thread t4 = new Thread("t4") {
@Override
public void run() {
synchronized (TestState.class) {
try {
Thread.sleep(1000000); // timed_waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t4.start();
Thread t5 = new Thread("t5") {
@Override
public void run() {
try {
t2.join(); // waiting
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread t6 = new Thread("t6") {
@Override
public void run() {
synchronized (TestState.class) { // blocked
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t6.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 state {}", t1.getState());
log.debug("t2 state {}", t2.getState());
log.debug("t3 state {}", t3.getState());
log.debug("t4 state {}", t4.getState());
log.debug("t5 state {}", t5.getState());
log.debug("t6 state {}", t6.getState());
System.in.read();
}
}
泡茶案列
package cn.itcast.mytest;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.mytest2")
public class mytest2 {
public static void main(String[] args) {
Thread t1=new Thread(()->{
try {
log.debug("洗水壶");
Thread.sleep(1000);
log.debug("烧开水");
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
Thread t2=new Thread(()->{
try {
log.debug("西茶壶");
Thread.sleep(1000);
log.debug("洗茶杯");
Thread.sleep(5000);
log.debug("拿茶叶");
Thread.sleep(2000);
log.debug("准备工作完成,等待水开");
t1.join();
log.debug("水开了泡茶开始");
Thread.sleep(10000);
log.debug("泡好了喝茶,结束了");
}catch (InterruptedException e){
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
多线程访问共享资源带来的安全问题,比如说访问主存里的变量。由于分时系统,时间片到了,就切换线程,但是之前的线程可能还来不及改变变量,这样会导致其他线程获得的变量值失真,由于上下文切换和指令交错产生了问题,因此,采用锁的机制,时间片到了但是其他的线程不能去进行相关变量的操作,也就是把相关变量的操作锁住。
案列
@Slf4j(topic = "c.Test17")
public class Test17 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}", room.getCounter());
}
}
解决办法锁
synchronized
原理
synichronized(锁),因为这个锁保证了代码的原子性,对共享变量进行读取的代码片段,叫做代码临界区
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test17")
public class Test17 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}", room.getCounter());
}
}
class Room {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public synchronized void decrement() {
counter--;
}
public synchronized int getCounter() {
return counter;
}
}
synchronized加在成员方法上,相当于锁住了this对象
synchronized加在成员方法上,相当于锁住了类对象
this对象和类对象不是同一对象,不会触发同步锁
成员变量和静态变量在资源共享被多个线程读写时需要考虑线程安全,在只读时不需要考虑线程安全
局部变量不需要考虑线程安全,
因为局部变量不需要共享,每个线程执行相应的方法时创建自己的栈和栈帧,在栈帧里各自有自己的方法和局部变量
但是使用局部变量的对象需要考虑线程安全,可能将父类的局部变量暴露给子类,而在子类里新开了一个线程,那么父类的线程和子类的线程会同时操作父类里面的局部变量这样会使得局部变量被多个线程所读写而引起的线程安全问题
线程安全,使用private或者final防止子类覆盖父类方法
package cn.itcast.n4;
import java.util.ArrayList;
public class TestThreadSafe {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadSafe test = new ThreadSafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + (i+1)).start();
}
}
}
class Threadsafe {
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++) {
method2();
method3();
}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
}
alt+shift点击某一行可以按上下箭头移动该行的位置,ctrl+F12快速查找方法
线程不安全的情况
class ThreadSafeSubClass extends ThreadSafe{
package cn.itcast.n4;
import java.util.ArrayList;
public class TestThreadSafe {
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadSafeSubClass test = new ThreadSafeSubClass();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
test.method1(LOOP_NUMBER);
}, "Thread" + (i+1)).start();
}
}
}
class ThreadSafe {
public final void method1(int loopNumber) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
public void method2(ArrayList<String> list) {
list.add("1");
}
private void method3(ArrayList<String> list) {
System.out.println(1);
list.remove(0);
}
}
class ThreadSafeSubClass extends ThreadSafe{
// @Override
public void method3(ArrayList<String> list) {
System.out.println(2);
new Thread(() -> {
list.remove(0);
}).start();
}
}
常见的线程安全的类
String类里边的方法,不可以改变String的状态他们大多会返回一个new String 因此,他的value没有改变
不会出现线程安全的问题
Hashtable 这是map的线程安全实现,里面的方法大多用sychronized枷锁,而保证了他的线程安全性。
Vector继承自AbtractList,里面的方法也是用sychronized加锁了的
但是当多个方法组合的时候就不一定是线程安全的了,可以使用synchronized在外层上锁
非线程安全的几个需要注意的点
Map不是线程安全的类 String是的
加了final的成员变量不可改变
当成员变量是一个类的时候要观察定义的类对象里有没有成员变量,如果有可更改的成员变量那么是非线程安全的
但是如果定义的对象在每个线程执行时都会创建一个自己的对象,那么还是线程安全的,
这里一直到UserDaoImpl但是它里面的Connection为单资源,并没有每次使用都new一个实例,因此
追踪到最后不是线程安全的,假如把他作为局部变量那么就可以是线程安全的
要注意对各个线程里运用到的成员变量(共享变量)更改的(临界区)的保护,主线程里的代码不需要保护因为没有多个线程竞争,没有多个线程共享
package cn.itcast.n4.exercise;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
@Slf4j(topic = "c.ExerciseSell")
public class ExerciseSell {
public static void main(String[] args) throws InterruptedException {
// 模拟多人买票
TicketWindow window = new TicketWindow(1000);
// 所有线程的集合
List<Thread> threadList = new ArrayList<>();
// 卖出的票数统计
List<Integer> amountList = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread thread = new Thread(() -> {
// 买票
int amount = window.sell(random(5));
// 统计买票数
amountList.add(amount);
});
//主线程里面的方法不需要保护,不会被多个线程使用
threadList.add(thread);
thread.start();
}
//等2000个人买完票,再统计
for (Thread thread : threadList) {
thread.join();
}
// 统计卖出的票数和剩余票数
log.debug("余票:{}",window.getCount());
log.debug("卖出的票数:{}", amountList.stream().mapToInt(i-> i).sum());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int random(int amount) {
return random.nextInt(amount) + 1;
}
}
// 售票窗口
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
// 获取余票数量
public int getCount() {
return count;
}
// 售票
public synchronized int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
Monitor
普通对象会有一个Object header,分为mark word和klass word(指向方法区的class实例,以区别是什么类)
当用sychronizied锁定一个对象的时候,会在object header分配一个指针指向操作系统的Monitor对象,原来的hash码会变成30位的指向monitor的地址,同时lock位会变成10
当上了锁,对象和monitor关联起来之后,拥有该锁的线程就会成为Owner,然后其余线程访问到该代码块的时候就会查看,被锁对象对应的monitor里面有没有值,如果有值,就不能拥有锁,同样会关联起来但是,不是进入owner而是进入entrylist
19-22是当异常出现的时候抛出异常解开锁
得到异常锁,复制一份到slot1,将lock,markword置为关联的monitor地址,进行i++,获得lock引用,将lock,markword重置,唤醒entrylist,然后里面的线程进行非公平竞争
轻量级锁
当线程执行竞态代码的时候有顺序,那么就没必要关联monitor,可以使用轻量级锁
轻量级锁的原理
然后用cas,使lock record地址和markword互换,00表示轻量级锁
交换成功,01才能交换成功,假如是00就交换失败(已经被其他线程锁了),对象头里的lockrecord可以知道是哪个线程锁的
接着代码执行完,退出锁,假如为null,表示重入,退出锁,假如不为null,恢复对象的markword
假如解锁失败说明,发生锁膨胀,那么解锁重量级锁
当一个线程想加轻量级锁,发现对象已经加过了,那么只能申请monitor进入阻塞状态
修改markword为monitor的地址指向操作系统的monitor,然后owner为当前拥有锁的线程,然后这个线程进入entrylist
当拥有锁的线程执行完了,然后想cas想重置markword发生错误,接着,根据markword找到monitor,置owner为null并且唤醒entrylist里面的线程
还有一种叫做自旋优化的功能,当cpu2(单核无意义)处理的线程来到同步代码块时,发现锁没解开,并不会进入阻塞状态,而是自旋几次,看能否获得锁,假如多次失败,则会进入阻塞状态,进入entrylist
偏向锁
假如线程不断对相同的对象加轻量级锁的话,会产生null的锁记录,还会进行cas判断,但是会失败,为了进行这一步的优化
产生了 偏向锁,markword存储线程ID
当访问对象hash的时候会撤销对象的偏向锁,而把hash码替换成之前的线程id
当多个线程不同时间访问同步代码块的时候也会出现偏向锁被撤销的情况
调用 wait和notify也会撤销锁的可偏向状态因为这种机制属于重量级锁
当线程反复给新new的对象枷锁,没有其他线程竞争,会加偏向锁101,而后来另外一个线程对这些对象枷锁,会发生锁膨胀,加轻量锁00,然后解锁之后恢复为普通001,当超过20次之后会发生偏向锁重定向为这个线程,只是线程ID换了,偏向锁过程为重置偏向为T1线程->偏向为T2线程->解锁还是偏向T2线程,解锁之后还是偏向这个进程,然后再来一个线程对这些对象加锁,会锁膨胀成轻量锁,解锁之后变成普通锁这样一直到40次,之后这个类new的对象全是不可偏向的状态
JIT在执行热点代码的时候会考虑到锁消除
/*
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.itcast;
import org.openjdk.jmh.annotations.*;
import java.util.Arrays;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
static int x = 0;
@Benchmark
public void a() throws Exception {
x++;
}
@Benchmark
// JIT 即时编译器
public void b() throws Exception {
Object o = new Object();
synchronized (o) {
x++;
}
}
}
由于Object o对象不会被其他同步锁拿到,所以jit在做即时编译的时候就会把锁取掉,这样执行时间就会相近
而假如没有去掉锁执行时间差了17倍
wait/notify
当一个线程进入同步锁之后,但是由于执行了wait进入了一个休息区,waitset,这个时候就会让出锁。
当某个线程进入锁之后使用notify唤醒正在等待的线程,那么该线程就会退出waitset然后进入entrylist
假如wait(时间)过去了这么久还没人来叫醒的话,会继续执行下面的代码
package cn.itcast.n4;
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("其它代码....");
}
},"t2").start();
// 主线程两秒后执行
sleep(0.5);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
// obj.notify(); // 唤醒obj上一个线程
obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
}
正确使用wait、notify
假如在使用notify的时候没有给while判断条件正确的值那么就会进入虚假唤醒在下一次循环再进入等待
因此这样可以使用notify唤醒多个线程,但是只有你想要唤醒的线程条件满足才是真正的唤醒,这样就可以选择唤醒的线程
package cn.itcast.n4;
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep5 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
sleep(1);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
设计模式一
保护性暂停,当两个线程,一个线程需要另外一个线程的数据的时候,可以使用保护性暂停的设计模式,当对于Join来说,线程可以在等待前做自己的事情,而不需要等待一个线程结束才继续运行,只需要notify唤醒即可,response是局部变量,join需要全局变量。还可以设置等待时间,wait的时间不可以设置成timeout,不然在假唤醒的时候会重置成timeout,需要设置成余下时长
class GuardedObject {
// 标识 Guarded Object
private int id;
public GuardedObject(int id) {
this.id = id;
}
public int getId() {
return id;
}
// 结果
private Object response;
// 获取结果
// timeout 表示要等待多久 2000
public Object get(long timeout) {
synchronized (this) {
// 开始时间 15:00:00
long begin = System.currentTimeMillis();
// 经历的时间
long passedTime = 0;
while (response == null) {
// 这一轮循环应该等待的时间
long waitTime = timeout - passedTime;
// 经历的时间超过了最大等待时间时,退出循环
if (timeout - passedTime <= 0) {
break;
}
try {
this.wait(waitTime); // 虚假唤醒 15:00:01
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
}
return response;
}
}
// 产生结果
public void complete(Object response) {
synchronized (this) {
// 给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
增加暂停时间的测试实例
joIn原理,就是一个加时的同步代码块
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
利用guardedObject制作一个future(guardObject的集合),然后postman不断获得guardObject(id)然后给他送信(notify)
收信人不断创造guardObject然后取信
package cn.itcast.test;
import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
@Slf4j(topic = "c.Test20")
public class Test20 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new People().start();
}
Sleeper.sleep(1);
for (Integer id : Mailboxes.getIds()) {
new Postman(id, "内容" + id).start();
}
}
}
@Slf4j(topic = "c.People")
class People extends Thread{
@Override
public void run() {
// 收信
GuardedObject guardedObject = Mailboxes.createGuardedObject();
log.debug("开始收信 id:{}", guardedObject.getId());
Object mail = guardedObject.get(5000);
log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
}
}
@Slf4j(topic = "c.Postman")
class Postman extends Thread {
private int id;
private String mail;
public Postman(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
log.debug("送信 id:{}, 内容:{}", id, mail);
guardedObject.complete(mail);
}
}
class Mailboxes {
private static Map<Integer, GuardedObject> boxes = new Hashtable<>();
private static int id = 1;
// 产生唯一 id
private static synchronized int generateId() {
return id++;
}
public static GuardedObject getGuardedObject(int id) {
return boxes.remove(id);
}
public static GuardedObject createGuardedObject() {
GuardedObject go = new GuardedObject(generateId());
boxes.put(go.getId(), go);
return go;
}
public static Set<Integer> getIds() {
return boxes.keySet();
}
}
// 增加超时效果
class GuardedObject {
// 标识 Guarded Object
private int id;
public GuardedObject(int id) {
this.id = id;
}
public int getId() {
return id;
}
// 结果
private Object response;
// 获取结果
// timeout 表示要等待多久 2000
public Object get(long timeout) {
synchronized (this) {
// 开始时间 15:00:00
long begin = System.currentTimeMillis();
// 经历的时间
long passedTime = 0;
while (response == null) {
// 这一轮循环应该等待的时间
long waitTime = timeout - passedTime;
// 经历的时间超过了最大等待时间时,退出循环
if (timeout - passedTime <= 0) {
break;
}
try {
this.wait(waitTime); // 虚假唤醒 15:00:01
} catch (InterruptedException e) {
e.printStackTrace();
}
// 求得经历时间
passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
}
return response;
}
}
// 产生结果
public void complete(Object response) {
synchronized (this) {
// 给结果成员变量赋值
this.response = response;
this.notifyAll();
}
}
}
上面的收信人和送信人是一一对应的关系,但是这样有不好的地方。
会导致送信人过多。200收信人但是我只要五个邮递员
解决办法
创造一个消息队列,生产者的消息存入一定容量的消息队列,然后消费者从队列里把消息取出,当队列满生产者需要wait,当队列空消费者需要wait,当生产者生产消息需要notify消费者取,当消费者消费消息需要提醒生产者给消息
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test21")
public class Test21 {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(2);
for (int i = 0; i < 3; i++) {
int id = i;
new Thread(() -> {
queue.put(new Message(id , "值"+id));
}, "生产者" + i).start();
}
new Thread(() -> {
while(true) {
sleep(1);
Message message = queue.take();
}
}, "消费者").start();
}
}
// 消息队列类 , java 线程之间通信
@Slf4j(topic = "c.MessageQueue")
class MessageQueue {
// 消息的队列集合
private LinkedList<Message> list = new LinkedList<>();
// 队列容量
private int capcity;
public MessageQueue(int capcity) {
this.capcity = capcity;
}
// 获取消息
public Message take() {
// 检查队列是否为空
synchronized (list) {
while(list.isEmpty()) {
try {
log.debug("队列为空, 消费者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 从队列头部获取消息并返回
Message message = list.removeFirst();
log.debug("已消费消息 {}", message);
list.notifyAll();
return message;
}
}
// 存入消息
public void put(Message message) {
synchronized (list) {
// 检查对象是否已满
while(list.size() == capcity) {
try {
log.debug("队列已满, 生产者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 将消息加入队列尾部
list.addLast(message);
log.debug("已生产消息 {}", message);
list.notifyAll();
}
}
}
final class Message {
private int id;
private Object value;
public Message(int id, Object value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value=" + value +
'}';
}
}
LockSupport.park (暂停当前线程,状态变为wait)和LockSupport.unpark(恢复当前线程),先执行unpark会使park失去作用
park和wait的对比
park不必与monitor关联,unpark可以写在park之前
unpark可以明确的表示唤醒哪个线程
park底层原理
线程转换
多把锁
什么时候要准备多把锁呢。当需要保护的几个代码块不需要依次执行的时候,并且需要保证代码块的原子性那么这个时候就可以采取多个锁,因为这样可以同时执行代码块
活跃性
当多个线程有多个锁的时候可能会出现死锁,
两个线程持有对方的锁
package cn.itcast.n4.deadlock;
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
public static void main(String[] args) {
test1();
}
private static void test1() {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
sleep(1);
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
sleep(0.5);
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
}
死锁
多个线程互相竞争各自的资源,形成闭环
哲学家问题
活锁
多个线程改变其结束的条件,形成活锁
饥饿
当一个线程优先级太低一直得不到cpu调度器执行
可重入锁相比sychronized
可中断
可以设置超时时间
可以设置成为公平锁
支持多个条件
可中断指的是在没有获得锁阻塞期间,被其他线程打断,会弹出一个interrupt异常,就可以弹出不需要再等待锁了lockInterruptability
设置超时时间,利用trylock,里面可以接时间,假如在指定的时间里得不到锁,那么就可以返回一个false,反之得到锁返回true
,trylock也可以被打断
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test22")
public class Test22 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
if (! lock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("获取不到锁");
return;
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得到锁");
t1.start();
sleep(1);
log.debug("释放了锁");
lock.unlock();
}
}
用trylock解决哲学家就餐问题,原因是哲学家拿起一根筷子就会一直等下一根筷子,而不会尝试放下筷子
trylock可以设置时限,当很长时间拿不到筷子就会放下筷子
package cn.itcast.test;
import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.Test23")
public class Test23 {public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
if(left.tryLock()) {
try {
// 尝试获得右手筷子
if(right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock(); // 释放自己手里的筷子
}
}
}
}
Random random = new Random();
private void eat() {
log.debug("eating...");
Sleeper.sleep(0.5);
}
}
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
可重入锁的公平性
构造方法
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
支持多个条件,以之前送烟和送外卖为例子,其中condition1.await=wait,condition2.signal=notify
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test24")
public class Test24 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock ROOM = new ReentrantLock();
// 等待烟的休息室
static Condition waitCigaretteSet = ROOM.newCondition();
// 等外卖的休息室
static Condition waitTakeoutSet = ROOM.newCondition();
public static void main(String[] args) {
new Thread(() -> {
ROOM.lock();
try {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小南").start();
new Thread(() -> {
ROOM.lock();
try {
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小女").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasTakeout = true;
waitTakeoutSet.signal();
} finally {
ROOM.unlock();
}
}, "送外卖的").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasCigarette = true;
waitCigaretteSet.signal();
} finally {
ROOM.unlock();
}
}, "送烟的").start();
}
}
同步模式,控制线程的执行顺序
第一利用wait和notify
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test25")
public class Test25 {
static final Object lock = new Object();
// 表示 t2 是否运行过
static boolean t2runned = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock) {
while (!t2runned) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock) {
log.debug("2");
t2runned = true;
lock.notify();
}
}, "t2");
t1.start();
t2.start();
}
}
利用reentrantLock的await和signal实现顺序
利用LockSupport的park和unpark方法
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.Test26")
public class Test26 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
log.debug("1");
}, "t1");
t1.start();
new Thread(() -> {
log.debug("2");
LockSupport.unpark(t1);
},"t2").start();
}
}
三个线程顺序打印abc五次,利用wait、notify实现,设置一个打印标记
当满足打印标记就执行打印并且改变打印标记为2,然后唤醒其他线程,其他线程接到访问标记之后,还会循环一次,发现条件满足了也会打印,再重复到第三个线程。。由于要打印五次还需要设置一个打印次数
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test27")
public class Test27 {
public static void main(String[] args) {
WaitNotify wn = new WaitNotify(1, 5);
new Thread(() -> {
wn.print("a", 1, 2);
}).start();
new Thread(() -> {
wn.print("b", 2, 3);
}).start();
new Thread(() -> {
wn.print("c", 3, 1);
}).start();
}
}
/*
输出内容 等待标记 下一个标记
a 1 2
b 2 3
c 3 1
*/
class WaitNotify {
// 打印 a 1 2
public void print(String str, int waitFlag, int nextFlag) {
for (int i = 0; i < loopNumber; i++) {
synchronized (this) {
while(flag != waitFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(str);
flag = nextFlag;
this.notifyAll();
}
}
}
// 等待标记
private int flag; // 2
// 循环次数
private int loopNumber;
public WaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
}
同样的问题,利用await和signal实现
package cn.itcast.test;
import sun.rmi.runtime.Log;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Test30 {
public static void main(String[] args) throws InterruptedException {
AwaitSignal awaitSignal = new AwaitSignal(5);
Condition a = awaitSignal.newCondition();
Condition b = awaitSignal.newCondition();
Condition c = awaitSignal.newCondition();
new Thread(() -> {
awaitSignal.print("a", a, b);
}).start();
new Thread(() -> {
awaitSignal.print("b", b, c);
}).start();
new Thread(() -> {
awaitSignal.print("c", c, a);
}).start();
Thread.sleep(1000);
awaitSignal.lock();
try {
System.out.println("开始...");
a.signal();
} finally {
awaitSignal.unlock();
}
}
}
class AwaitSignal extends ReentrantLock{
private int loopNumber;
public AwaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}
// 参数1 打印内容, 参数2 进入哪一间休息室, 参数3 下一间休息室
public void print(String str, Condition current, Condition next) {
for (int i = 0; i < loopNumber; i++) {
lock();
try {
current.await();
System.out.print(str);
//唤醒下一个线程
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
unlock();
}
}
}
}
利用park和unpark实现
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.LockSupport;
@Slf4j(topic = "c.Test31")
public class Test31 {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) {
ParkUnpark pu = new ParkUnpark(5);
t1 = new Thread(() -> {
pu.print("a", t2);
});
t2 = new Thread(() -> {
pu.print("b", t3);
});
t3 = new Thread(() -> {
pu.print("c", t1);
});
t1.start();
t2.start();
t3.start();
LockSupport.unpark(t1);
}
}
class ParkUnpark {
public void print(String str, Thread next) {
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
System.out.print(str);
LockSupport.unpark(next);
}
}
private int loopNumber;
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
}
共享模型之内存
内存的可见性
问题
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test32")
public class Test32 {
// 易变
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(true){
if(!run) {
break;
}
}
});
t.start();
sleep(1);
run = false; // 线程t不会如预想的停下来
}
}
原因:因为内存里存在某些共享变量,当线程循环的时候,会频繁地去内存里面读取共享变量(比如说静态成员变量)的值,但是循环多次之后,线程会缓存那个变量值,之后就不去内存里去读取了,这个优化使得即使你之后改变了共享变量值也影响不到线程,内存的不可见性的问题就出来了
解决:
利用volatile修饰成员变量实现线程不可以从工作缓存读取该成员变量的值
利用synchronized修饰,能实现同样的效果,但是需要创建monitor,但是volatile不能保证原子性
利用volatile实现两阶段停止模式
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.TwoPhaseTermination")
public class Test13 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tpt = new TwoPhaseTermination();
tpt.start();
tpt.start();
tpt.start();
/*Thread.sleep(3500);
log.debug("停止监控");
tpt.stop();*/
}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
// 监控线程
private Thread monitorThread;
// 停止标记
private volatile boolean stop = false;
// 判断是否执行过 start 方法
private boolean starting = false;
// 启动监控线程
public void start() {
synchronized (this) {
if (starting) { // false
return;
}
starting = true;
}
monitorThread = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
// 是否被打断
if (stop) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("执行监控记录");
} catch (InterruptedException e) {
}
}
}, "monitor");
monitorThread.start();
}
// 停止监控线程
public void stop() {
stop = true;
monitorThread.interrupt();
}
}
主线程改变了变量值,然后其他线程用这个变量判断是否退出,
但是多个线程会创建多个监控线程。于是就有了一种犹豫模式,犹豫模式会创建一个布尔类型的值,每当执行创建监控线程的代码时,会判断布尔类型的值。还要使用sychonized防止多个线程一起创建监控线程。但是还会有一个问题,创建之后每次重复创建还会走sychronized代码块,因此可以进行双重核验dcl(double checking lock),以单例模式举列子
上面的getInstance方法的指令如下,由于getIntance在sychronized外面,因此可能会产生指令的重排序
问题在于24和21发生指令重排之后,给静态变量赋值,但是还没有调用构造方法,假如这个时候另外一个线程来判断
会出现不为空而直接返回Instance实例,但是这个时候可能instance的构造方法还没有执行
sychronized会保护原子性可见性,但是还会发生指令重排,假如把变量完全交给sychronized就不会发生指令重排序问题
解决办法,在Instance定义假如volatile来设置读写屏障,在写屏障之前的代码不会出现在他后面,在对Instance赋值的时候出现了写屏障,写屏障会防止执行构造方法的指令跑到他后面去,从而解决了问题
在web应用里一个请求对应一个线程
单例模式的懒惰初始化
有序性
在对最后的结果不会产生影响的时候,指令重排
问题
解决
在ready加volatile,防止之前的代码出现在他的后面
volatile的读写屏障,保护可见性
读写屏障保证有序性
happen-before 规则
在线程执行之前对共享变量执行了写操作
那么在线程开始的时候能读取到共享变量的值
在线程结束后会把变量的值更新到主存中,所以主线程对于它可读
在线程被打断的时候能看到
几个题
enum里面的都是静态的final变量
在无锁情况下实现的针对共享资源的访问,怎么才能做到线程安全------>乐观锁(不怕别人修改共享变量),无锁并发,无阻塞
里面最主要的是CAS(conpare AND Set),cas是利用不断地进行while(true)循环,然后从当前内存取出值来,并且也有我要改变成的值,假如发现他已经是我要改变的值了这个时候就break,重新进行循环,直到修改成功
package cn.itcast.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class TestAccount {
public static void main(String[] args) {
Account account = new AccountCas(10000);
Account.demo(account);
}
}
class AccountCas implements Account {
private AtomicInteger balance;
public AccountCas(int balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
/*while(true) {
// 获取余额的最新值
int prev = balance.get();
// 要修改的余额
int next = prev - amount;
// 真正修改
if(balance.compareAndSet(prev, next)) {
break;
}
}*/
balance.getAndAdd(-1 * amount);
}
}
class AccountUnsafe implements Account {
private Integer balance;
public AccountUnsafe(Integer balance) {
this.balance = balance;
}
@Override
public Integer getBalance() {
synchronized (this) {
return this.balance;
}
}
@Override
public void withdraw(Integer amount) {
synchronized (this) {
this.balance -= amount;
}
}
}
interface Account {
// 获取余额
Integer getBalance();
// 取款
void withdraw(Integer amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
long start = System.nanoTime();
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance()
+ " cost: " + (end-start)/1000_000 + " ms");
}
}
为什么无锁效率会变高,因为假如线程数小于cpu核心数,这个时候无锁线程会一直告诉跑
而有锁线程在碰到多线程竞争的时候会进入runable的阻塞状态这个时候会发生上下文切换,也就会发生线程状态的保存,这样就会浪费性能
原子整数
java.util.content提供了
AtomicInteger基本使用后,由于cas太复杂,替换成有一些api有原子操作
package cn.itcast.test;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntUnaryOperator;
public class Test34 {
public static void main(String[] args) {
AtomicInteger i = new AtomicInteger(5);
/*System.out.println(i.incrementAndGet()); // ++i 1
System.out.println(i.getAndIncrement()); // i++ 2
System.out.println(i.getAndAdd(5)); // 2 , 7
System.out.println(i.addAndGet(5)); // 12, 12*/
// 读取到 设置值
// i.updateAndGet(value -> value * 10);
System.out.println(updateAndGet(i, p -> p / 2));
// i.getAndUpdate()
System.out.println(i.get());
}
//updateAndGet具体实现
public static int updateAndGet(AtomicInteger i, IntUnaryOperator operator) {
while (true) {
int prev = i.get();
int next = operator.applyAsInt(prev);
if (i.compareAndSet(prev, next)) {
return next;
}
}
}
}
AtomicReference的基本使用
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@Slf4j(topic = "c.Test35")
public class Test35 {
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountCas(new BigDecimal("10000")));
}
}
class DecimalAccountCas implements DecimalAccount {
private AtomicReference<BigDecimal> balance;
public DecimalAccountCas(BigDecimal balance) {
// this.balance = balance;
this.balance = new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withdraw(BigDecimal amount) {
while(true) {
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
}
interface DecimalAccount {
// 获取余额
BigDecimal getBalance();
// 取款
void withdraw(BigDecimal amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(DecimalAccount account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(BigDecimal.TEN);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(account.getBalance());
}
}
ABA问题,主线程不能感知到其他线程对变量的修改,知道expect和当前值一样那么就能够修改成功,返回true
AtomicStampedReference可以给原子引用加上版本号,要想更新成功,需要版本号的期望值和真实值相同,才可以更新成功
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicStampedReference;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test36")
public class Test36 {
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
// 获取值 A
String prev = ref.getReference();
// 获取版本号
int stamp = ref.getStamp();
log.debug("版本 {}", stamp);
// 如果中间有其它线程干扰,发生了 ABA 现象
other();
sleep(1);
// 尝试改为 C
log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}
private static void other() {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本为 {}", ref.getStamp());
}, "t1").start();
sleep(0.5);
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本为 {}", ref.getStamp());
}, "t2").start();
}
}
有时候不需要知道他被更改了多少次。只要他更改过那么我们就可以认定cas更改失败,
想换垃圾袋,前提是装满了辣鸡,假如其中有一个人把垃圾倒了,然后把标记更改,那么换垃圾袋就会操作失败
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicMarkableReference;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test38")
public class Test38 {
public static void main(String[] args) throws InterruptedException {
GarbageBag bag = new GarbageBag("装满了垃圾");
// 参数2 mark 可以看作一个标记,表示垃圾袋满了
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
log.debug("start...");
GarbageBag prev = ref.getReference();
log.debug(prev.toString());
new Thread(() -> {
log.debug("start...");
bag.setDesc("空垃圾袋");
ref.compareAndSet(bag, bag, true, false);
log.debug(bag.toString());
},"保洁阿姨").start();
sleep(1);
log.debug("想换一只新垃圾袋?");
boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
log.debug("换了么?" + success);
log.debug(ref.getReference().toString());
}
}
class GarbageBag {
String desc;
public GarbageBag(String desc) {
this.desc = desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return super.toString() + " " + desc;
}
}
原子数组,
为了保证对数组里面元素的操作的原子性
package cn.itcast.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class Test39 {
public static void main(String[] args) {
demo(
()->new int[10],
(array)->array.length,
(array, index) -> array[index]++,
array-> System.out.println(Arrays.toString(array))
);
demo(
()-> new AtomicIntegerArray(10),
(array) -> array.length(),
(array, index) -> array.getAndIncrement(index),
array -> System.out.println(array)
);
}
/**
参数1,提供数组、可以是线程不安全数组或线程安全数组
参数2,获取数组长度的方法
参数3,自增方法,回传 array, index
参数4,打印数组的方法
*/
// supplier 提供者 无中生有 ()->结果
// function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
// consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->
private static <T> void demo(
Supplier<T> arraySupplier,
Function<T, Integer> lengthFun,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer ) {
List<Thread> ts = new ArrayList<>();
T array = arraySupplier.get();
int length = lengthFun.apply(array);
for (int i = 0; i < length; i++) {
// 每个线程对数组作 10000 次操作
ts.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j%length);
}
}));
}
ts.forEach(t -> t.start()); // 启动所有线程
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}); // 等所有线程结束
printConsumer.accept(array);
}
}
为了保证对对象域的操作, 有了原子更新器
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@Slf4j(topic = "c.Test40")
public class Test40 {
public static void main(String[] args) {
Student stu = new Student();
AtomicReferenceFieldUpdater updater =
AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
System.out.println(updater.compareAndSet(stu, null, "张三"));
System.out.println(stu);
}
}
class Student {
volatile String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
相比atomicLong的高效率累加器
package cn.itcast.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class Test41 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
demo(
() -> new AtomicLong(0),
(adder) -> adder.getAndIncrement()
);
}
for (int i = 0; i < 5; i++) {
demo(
() -> new LongAdder(),
adder -> adder.increment()
);
}
}
/*
() -> 结果 提供累加器对象
(参数) -> 执行累加操作
*/
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
T adder = adderSupplier.get();
List<Thread> ts = new ArrayList<>();
// 4 个线程,每人累加 50 万
for (int i = 0; i < 4; i++) {
ts.add(new Thread(() -> {
for (int j = 0; j < 500000; j++) {
action.accept(adder);
}
}));
}
long start = System.nanoTime();
ts.forEach(t -> t.start());
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(adder + " cost:" + (end - start) / 1000_000);
}
}
casLock,利用cas做的锁,不建议代码使用,底层源码使用
package cn.itcast.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicInteger;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.Test42")
public class LockCas {
// 0 没加锁
// 1 加锁
private AtomicInteger state = new AtomicInteger(0);
public void lock() {
while (true) {
if (state.compareAndSet(0, 1)) {
break;
}
}
}
public void unlock() {
log.debug("unlock...");
state.set(0);
}
public static void main(String[] args) {
LockCas lock = new LockCas();
new Thread(() -> {
log.debug("begin...");
lock.lock();
try {
log.debug("lock...");
sleep(1);
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
log.debug("begin...");
lock.lock();
try {
log.debug("lock...");
} finally {
lock.unlock();
}
}).start();
}
}
CPU缓存行伪共享
CPU的缓存结构
因为一个cell占24个字节,一个缓存行占64个字节,
因此假如一个cell被改变,会导致另外一个核心整个缓存行失效,因此出现了伪缓存行共享的注解
这个会使一个cell占一个缓存行,这样修改了某个cell之后使一个缓存行失效
底层unsafe方法
在一些底层api比如说increment或者locksupport方法里用到了unsafe
由于源码里的Unsafe是私有对象,因此只能通过反射来获得,
package cn.itcast.test;
import lombok.Data;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class TestUnsafe {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
System.out.println(unsafe);
// 1. 获取域的偏移地址
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
Teacher t = new Teacher();
// 2. 执行 cas 操作
unsafe.compareAndSwapInt(t, idOffset, 0, 1);
unsafe.compareAndSwapObject(t, nameOffset, null, "张三");
// 3. 验证
System.out.println(t);
}
}
@Data
class Teacher {
volatile int id;
volatile String name;
}
使用Unsafe创建线程安全的加减,实现银行加减操作
package cn.itcast.test;
import cn.itcast.n4.UnsafeAccessor;
import lombok.extern.slf4j.Slf4j;
import sun.misc.Unsafe;
@Slf4j(topic = "c.Test42")
public class Test42 {
public static void main(String[] args) {
Account.demo(new MyAtomicInteger(10000));
}
}
class MyAtomicInteger implements Account {
private volatile int value;
private static final long valueOffset;
private static final Unsafe UNSAFE;
static {
UNSAFE = UnsafeAccessor.getUnsafe();
try {
valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public int getValue() {
return value;
}
public void decrement(int amount) {
while(true) {
int prev = this.value;
int next = prev - amount;
if (UNSAFE.compareAndSwapInt(this, valueOffset, prev, next)) {
break;
}
}
}
public MyAtomicInteger(int value) {
this.value = value;
}
@Override
public Integer getBalance() {
return getValue();
}
@Override
public void withdraw(Integer amount) {
decrement(amount);
}
}
不可变类
DateFormatter 和SimpleDateFormat对比
package cn.itcast.n7;
import lombok.extern.slf4j.Slf4j;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
DateTimeFormatter stf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
TemporalAccessor parse = stf.parse("1951-04-21");
log.debug("{}", parse);
}).start();
}
}
private static void test() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (sdf) {
try {
log.debug("{}", sdf.parse("1951-04-21"));
} catch (Exception e) {
log.error("{}", e);
}
}
}).start();
}
}
}
不可变类的设计
final,添加在类,方法,变类上,防止继承破坏不可变的性质
保护性拷贝,以String 为例子
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
//this.value=a
//a=2
}
防止给this.value复制的那个变量的改变而破坏不可变性
但是如果不断地创建对象时,会造成对象过多,解决方法,享元模式
包装类
*/
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
会去缓存里拿对象,从而避免重复创建
创建线程安全的连接池
package cn.itcast.n7;
import lombok.extern.slf4j.Slf4j;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class Test3 {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Connection conn = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.free(conn);
}).start();
}
}
}
@Slf4j(topic = "c.Pool")
class Pool {
// 1. 连接池大小
private final int poolSize;
// 2. 连接对象数组
private Connection[]
connections;
// 3. 连接状态数组 0 表示空闲, 1 表示繁忙
private AtomicIntegerArray states;
// 4. 构造方法初始化
public Pool(int poolSize) {
this.poolSize = poolSize;
this.connections = new Connection[poolSize];
this.states = new AtomicIntegerArray(new int[poolSize]);
for (int i = 0; i < poolSize; i++) {
connections[i] = new MockConnection("连接" + (i+1));
}
}
// 5. 借连接
public Connection borrow() {
while(true) {
for (int i = 0; i < poolSize; i++) {
// 获取空闲连接
if(states.get(i) == 0) {
if (states.compareAndSet(i, 0, 1)) {
log.debug("borrow {}", connections[i]);
return connections[i];
}
}
}
// 如果没有空闲连接,当前线程进入等待
synchronized (this) {
try {
log.debug("wait...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 6. 归还连接
public void free(Connection conn) {
for (int i = 0; i < poolSize; i++) {
if (connections[i] == conn) {
states.set(i, 0);
synchronized (this) {
log.debug("free {}", conn);
this.notifyAll();
}
break;
}
}
}
}
class MockConnection implements Connection {
}
final变量写的原理
final变量在写完之后加入了写屏障保证,之前的写入指令在写屏障之前执行
保证了内存的可见性
final变量读的原理
final变量在读的时候,会直接bipush进栈内存
不加final
在堆中访问
自定义线程池的实现
package cn.itcast.n8;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.log.LogDelegateFactory;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j(topic = "c.TestPool")
public class TestPool {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(1,
1000, TimeUnit.MILLISECONDS, 1,
// 策略模式具体执行操作让调用者觉得,我们只需提供相应操作接口,这里拒绝策略自定义
(queue, task)->{
// 1. 死等
// queue.put(task);
// 2) 带超时等待
// queue.offer(task, 1500, TimeUnit.MILLISECONDS);
// 3) 让调用者放弃任务执行
// log.debug("放弃{}", task);
// 4) 让调用者抛出异常
// throw new RuntimeException("任务执行失败 " + task);
// 5) 让调用者自己执行任务
task.run();
});
for (int i = 0; i < 4; i++) {
int j = i;
threadPool.execute(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("{}", j);
});
}
}
}
@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
void reject(BlockingQueue<T> queue, T task);
}
@Slf4j(topic = "c.ThreadPool")
class ThreadPool {
// 任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet<>();
// 核心线程数
private int coreSize;
// 获取任务时的超时时间
private long timeout;
private TimeUnit timeUnit;
private RejectPolicy<Runnable> rejectPolicy;
// 执行任务
public void execute(Runnable task) {
// 当任务数没有超过 coreSize 时,直接交给 worker 对象执行
// 如果任务数超过 coreSize 时,加入任务队列暂存
synchronized (workers) {
if(workers.size() < coreSize) {
Worker worker = new Worker(task);
log.debug("新增 worker{}, {}", worker, task);
workers.add(worker);
worker.start();
} else {
// taskQueue.put(task);
// 1) 死等
// 2) 带超时等待
// 3) 让调用者放弃任务执行
// 4) 让调用者抛出异常
// 5) 让调用者自己执行任务
taskQueue.tryPut(rejectPolicy, task);
}
}
}
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity, RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapcity);
this.rejectPolicy = rejectPolicy;
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
// 执行任务
// 1) 当 task 不为空,执行任务
// 2) 当 task 执行完毕,再接着从任务队列获取任务并执行
// while(task != null || (task = taskQueue.take()) != null) {
while(task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
try {
log.debug("正在执行...{}", task);
task.run();
} catch (Exception e) {
e.printStackTrace();
} finally {
task = null;
}
}
synchronized (workers) {
log.debug("worker 被移除{}", this);
workers.remove(this);
}
}
}
}
@Slf4j(topic = "c.BlockingQueue")
class BlockingQueue<T> {
// 1. 任务队列
private Deque<T> queue = new ArrayDeque<>();
// 2. 锁
private ReentrantLock lock = new ReentrantLock();
// 3. 生产者条件变量
private Condition fullWaitSet = lock.newCondition();
// 4. 消费者条件变量
private Condition emptyWaitSet = lock.newCondition();
// 5. 容量
private int capcity;
public BlockingQueue(int capcity) {
this.capcity = capcity;
}
// 带超时阻塞获取
public T poll(long timeout, TimeUnit unit) {
lock.lock();
try {
// 将 timeout 统一转换为 纳秒
long nanos = unit.toNanos(timeout);
while (queue.isEmpty()) {
try {
// 返回值是剩余时间
if (nanos <= 0) {
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
// 阻塞获取
public T take() {
lock.lock();
try {
while (queue.isEmpty()) {
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
// 阻塞添加
public void put(T task) {
lock.lock();
try {
while (queue.size() == capcity) {
try {
log.debug("等待加入任务队列 {} ...", task);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}", task);
queue.addLast(task);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
// 带超时时间阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit) {
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while (queue.size() == capcity) {
try {
if(nanos <= 0) {
return false;
}
log.debug("等待加入任务队列 {} ...", task);
nanos = fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}", task);
queue.addLast(task);
emptyWaitSet.signal();
return true;
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
// 判断队列是否满
if(queue.size() == capcity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
log.debug("加入任务队列 {}", task);
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
}
JDK的线程池
m-c是救急线程的数目,当救急线程完成任务后会根据设置的时间失去活动
JDK提供的拒绝策略
线程工厂的作用主要是给线程起一个好听的名字
只有救急线程。
特殊的阻塞队列
队列的put方法执行完毕要等到线程把任务取走才可以
package cn.itcast.n8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.SynchronousQueue;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.TestSynchronousQueue")
public class TestSynchronousQueue {
public static void main(String[] args) {
SynchronousQueue<Integer> integers = new SynchronousQueue<>();
new Thread(() -> {
try {
log.debug("putting {} ", 1);
integers.put(1);
log.debug("{} putted...", 1);
log.debug("putting...{} ", 2);
integers.put(2);
log.debug("{} putted...", 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
sleep(1);
new Thread(() -> {
try {
log.debug("taking {}", 1);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
sleep(1);
new Thread(() -> {
try {
log.debug("taking {}", 2);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t3").start();
}
}
与excute对应的还有submit,
不同的是他执行完还可以返回一个结果,
invokeAny 假如有多个任务,只会返回其中最先执行完的那一个任务,并且同时放弃掉其他任务的执行
invokeAll,会执行所有任务,并且返回结果
package cn.itcast.n8;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.TestSubmit")
public class TestSubmit {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(1);
}
private static void method3(ExecutorService pool) throws InterruptedException, ExecutionException {
String result = pool.invokeAny(Arrays.asList(
() -> {
log.debug("begin 1");
Thread.sleep(1000);
log.debug("end 1");
return "1";
},
() -> {
log.debug("begin 2");
Thread.sleep(500);
log.debug("end 2");
return "2";
},
() -> {
log.debug("begin 3");
Thread.sleep(2000);
log.debug("end 3");
return "3";
}
));
log.debug("{}", result);
}
private static void method2(ExecutorService pool) throws InterruptedException {
List<Future<String>> futures = pool.invokeAll(Arrays.asList(
() -> {
log.debug("begin");
Thread.sleep(1000);
return "1";
},
() -> {
log.debug("begin");
Thread.sleep(500);
return "2";
},
() -> {
log.debug("begin");
Thread.sleep(2000);
return "3";
}
));
futures.forEach( f -> {
try {
log.debug("{}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
private static void method1(ExecutorService pool) throws InterruptedException, ExecutionException {
Future<String> future = pool.submit(() -> {
log.debug("running");
Thread.sleep(1000);
return "ok";
});
//get方法要等return ok返回才能执行
log.debug("{}", future.get());
}
}
结束线程shutdown和shutdownNow的区别
设计模式线程的饥饿现象(两个线程都在处理点餐,但是没有线程上菜)
解决办法
设置两个线程池,一个线程池点餐,一个线程池上菜
package cn.itcast.n8;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j(topic = "c.TestDeadLock")
public class TestStarvation {
static final List<String> MENU = Arrays.asList("地三鲜", "宫保鸡丁", "辣子鸡丁", "烤鸡翅");
static Random RANDOM = new Random();
static String cooking() {
return MENU.get(RANDOM.nextInt(MENU.size()));
}
public static void main(String[] args) {
ExecutorService waiterPool = Executors.newFixedThreadPool(1);
ExecutorService cookPool = Executors.newFixedThreadPool(1);
waiterPool.execute(() -> {
log.debug("处理点餐...");
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
log.debug("处理点餐...");
Future<String> f = cookPool.submit(() -> {
log.debug("做菜");
return cooking();
});
try {
log.debug("上菜: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
Timer串行执行,并且会在异常时退出,不会让下一个线程执行