控制线程执行顺序
wait notify
每次进来携带能够执行的标识, 如果标识不对则线程进入等待, 这里是公用一个 TestSyncWaitNotify对象, 所以这个对象的flag属性就是临界值,对对象加锁, 保证对象的flag属性不会同时被多个线程持有, 每一次执行执行结束需要使用notifyAll() 唤醒所有等待的线程, 然后每个线程通过竞争锁进入到 while 循环中 进行 flag 的比较.
@Slf4j(topic = "c.SyncWaitNotify")
public class TestSyncWaitNotify {
private int flag;
private int loopNumber;
public TestSyncWaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
private void print(int waitFlag, int nextFlag, String str) {
for (int i = 0; i < loopNumber; i++) {
synchronized (this) {
while (this.flag != waitFlag) {
try {
this.wait();
} catch (InterruptedException e) {
log.debug("exec wait occur");
}
}
log.debug(str);
this.flag = nextFlag;
this.notifyAll();
}
}
}
public static void main(String[] args) {
TestSyncWaitNotify syncWaitNotify = new TestSyncWaitNotify(1, 5);
new Thread(() -> syncWaitNotify.print(1, 2, "a"), "t1").start();
new Thread(() -> syncWaitNotify.print(2, 3, "b"), "t2").start();
new Thread(() -> syncWaitNotify.print(3, 1, "c"), "t3").start();
}
}
park unpark
park 相对于 wait 的线程来说, 可以通过 LockSupport.unpark(thread)
唤醒指定的线程
@Slf4j(topic = "c.SyncPark")
public class TestSyncPark {
private int loopNumber;
private Thread[] threads;
public TestSyncPark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void setThreads(Thread... threads) {
this.threads = threads;
}
private void print(String str) {
for (int i = 0; i < loopNumber; i++) {
LockSupport.park();
log.debug(str);
LockSupport.unpark(nextThread());
}
}
private Thread nextThread() {
int current = 0;
Thread currentThread = Thread.currentThread();
for (int i = 0; i < threads.length; i++) {
if (threads[i] == currentThread) {
current = i < threads.length - 1 ? i + 1 : 0;
break;
}
}
return threads[current];
}
private void start() {
for (Thread thread : threads) {
thread.start();
}
LockSupport.unpark(threads[0]);
}
public static void main(String[] args) {
TestSyncPark syncPark = new TestSyncPark(5);
Thread t1 = new Thread(() -> syncPark.print("a"));
Thread t2 = new Thread(() -> syncPark.print("b"));
Thread t3 = new Thread(() -> syncPark.print("c"));
syncPark.setThreads(t1, t2, t3);
syncPark.start();
}
}
await signal
await signal的方式和park相似的是都可以唤醒指定的线程, 只不过 await 是通过 condition 来进行控制的, 这里需要注意的是 condition的 await 和 signal 方法都是需要在加锁 lock() 下执行
@Slf4j(topic = "c.AwaitSignal")
public class TestAwaitSignal extends ReentrantLock {
private int loopNumber;
public TestAwaitSignal(int loopNumber) {
this.loopNumber = loopNumber;
}
private void print(String str, Condition current, Condition next) {
for (int i = 0; i < loopNumber; i++) {
this.lock();
try {
current.await();
log.debug(str);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
this.unlock();
}
}
}
private void start(Condition current) {
this.lock();
try {
log.debug("start");
current.signal();
} finally {
this.unlock();
}
}
public static void main(String[] args) {
TestAwaitSignal awaitSignal = new TestAwaitSignal(5);
Condition aWaitSet = awaitSignal.newCondition();
Condition bWaitSet = awaitSignal.newCondition();
Condition cWaitSet = awaitSignal.newCondition();
new Thread(() -> awaitSignal.print("a", aWaitSet, bWaitSet)).start();
new Thread(() -> awaitSignal.print("b", bWaitSet, cWaitSet)).start();
new Thread(() -> awaitSignal.print("c", cWaitSet, aWaitSet)).start();
awaitSignal.start(aWaitSet);
}
}
ReentrantLock
上面的 await, signal 使用到了 reentrantLock 类, 这里就简单记录一下
-
new ReentrantLock(fire: boolean)
参数默认为false: 不公平的, 可以手动设置为 true 则为公平锁 -
锁可以被打断, 锁时使用
lock.lockInterruptibly()
如果没有竞争那么此方法就会获得 lock 锁, 如果有竞争锁就会进入阻塞队列, 但是可以被别的线程通过interrupted
方法打断 -
lock.lock()
普通加锁的方式, 没有啥特点 -
lock.tryLock(timeout): boolean
携带超时时间的获取锁, 返回 boolean 值
锁对象和锁类的情况
锁对象
多个线程操作同一个对象的某个属性, 这个属性也可以被叫做操作的临界区
@Slf4j(topic = "c.ExerciseSell")
public class ExerciseSell {
// random 是线程安全类
static Random random = new Random();
private static int randomAmount() {
return random.nextInt(5) + 1;
}
public static void main(String[] args) throws InterruptedException {
TicketWindow ticketWindow = new TicketWindow(1000);
List<Thread> threadList = new ArrayList<>();
List<Integer> amountList = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread thread = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(randomAmount());
int amount = ticketWindow.sellTicket(randomAmount());
amountList.add(amount);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadList.add(thread);
}
threadList.forEach(Thread::start);
for (Thread thread : threadList) {
thread.join();
}
// 21:10:21.595 [main] DEBUG c.ExerciseSell - 余票: 1
log.debug("余票: {}", ticketWindow.getCount());
// 21:10:21.599 [main] DEBUG c.ExerciseSell - 卖出的票: 999
log.debug("卖出的票: {}", amountList.stream().mapToInt(Integer::intValue).sum());
// 21:10:21.599 [main] DEBUG c.ExerciseSell - 总票数: 1000
log.debug("总票数: {}", ticketWindow.getCount() + amountList.stream().mapToInt(Integer::intValue).sum());
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
/**
* sell ticket
* @param amount 买的票数
* @return 成功购买的票数
*/
public synchronized int sellTicket(int amount) {
if (this.count > amount) {
this.count -= amount;
return amount;
}
return 0;
}
}
锁类
多个线程操作类的属性也就是静态, 或则多个类(相同的类)的相同属性, 怎么理解这句话呢? 其实就是假设有个Account类它有一个money的属性, 这时有个成员方法参数也是Account类的对象, 这时在多线程中调用时, 共享的变量就变成了this 和 参数Account, 所以需要锁this和参数Account的共享变量也就是Account类
@Slf4j(topic = "c.ExerciseTransfer")
public class ExerciseTransfer {
// random 是线程安全类
static Random random = new Random();
private static int randomAmount() {
return random.nextInt(5) + 1;
}
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomAmount());
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomAmount());
}
});
t1.start();
t2.start();
t1.join();
t2.join();
// 21:32:06.119 [main] DEBUG c.ExerciseTransfer - total: 2000
log.debug("total: {}", a.getMoney() + b.getMoney());
}
}
class Account {
private int money; // 被多个线程所共享
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void transfer(Account target, int money) {
// 因为同时操作了 this.money 和 target.money 两个对象的 money 属性所以单独锁对象是锁不住的
// 当然如果你想锁两个对象, 这个也是不行的很容易出现死锁
synchronized (Account.class) {
if (this.money > money) {
this.setMoney(this.getMoney() - money);
target.setMoney(target.getMoney() + money);
}
}
}
}
生产者消费者模型-消息队列
生产者消费者模型就是一个线程负责生产, 一个线程负责消费, 而消息队列不过是多了一个容器存放消息, 容器中没有了消息就通知生产者生产消息, 满了就生产者就wait, 消费者相反, 当然这里线程间的通信方式就可以用到上面的三个api
@Slf4j(topic = "c.MessageQueue")
public class MessageQueue {
// 消息队列集合
private LinkedList<Message> messageLinkedList = new LinkedList<>();
// 消息的容量
private int capacity;
public MessageQueue(int capacity) {
this.capacity = capacity;
}
// 获取消息
public Message task() {
synchronized (messageLinkedList) {
while (messageLinkedList.isEmpty()) {
try {
log.debug("队列为空, 消费者线程进入等待...");
messageLinkedList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = messageLinkedList.poll();
log.debug("消费了一条消息: {}", message);
messageLinkedList.notifyAll();
return message;
}
}
// 存入消息
public void put(Message message) {
synchronized (messageLinkedList) {
while (messageLinkedList.size() == capacity) {
try {
log.debug("队列已满, 生产者线程进入等待...");
messageLinkedList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.messageLinkedList.addLast(message);
log.debug("已生产一条消息: {}", message);
messageLinkedList.notifyAll();
}
}
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, "value: " + id));
}, "生产者" + id).start();
}
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
queue.task();
}
}, "消费者").start();
}
}
class Message<T> {
private int id;
private T value;
public Message(int id, T value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value=" + value +
'}';
}
}