1、概述
信号量,多线程多任务同步,一个线程完成某个动作就通过信号量告诉别的线程,别的线程在进行某些动作。
2、上锁
信号量大于0,其他线程就等待,成功后信号量的值-1。值不大于0,则线程阻塞,直到信号量的值+1。
常用的两个主要方法:
acquire()
release()
3、实际中的一个例子:abc三个线程交替打印147、258、369
package com.test.abc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Semaphore;
@Slf4j
public class ThreadTestSemaphoreA {
public static void main(String[] args) {
Semaphore a = new Semaphore(1);
Semaphore b = new Semaphore(0);
Semaphore c = new Semaphore(0);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 10; i = i + 3) {
try {
a.acquire();
System.out.println("a线程:" + i);
} catch (Exception e) {
log.error("a线程异常", e);
} finally {
b.release();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 2; i < 10; i = i + 3) {
try {
b.acquire();
System.out.println("b线程:" + i);
} catch (Exception e) {
log.error("b线程异常", e);
} finally {
c.release();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 3; i < 10; i = i + 3) {
try {
c.acquire();
System.out.println("c线程:" + i);
} catch (Exception e) {
log.error("c线程异常", e);
} finally {
a.release();
}
}
}
}).start();
}
}
打印结果如下:
a线程:1
b线程:2
c线程:3
a线程:4
b线程:5
c线程:6
a线程:7
b线程:8
c线程:9
4、思考:
信号量固然能实现三个线程的交替打印,从实现上讲,代码段并无问题。然而信号量是用来做这个的么?
我们来改变下信号量的值,进行测试:
package com.test.abc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Semaphore;
@Slf4j
public class ThreadTestSemaphoreA {
public static void main(String[] args) {
Semaphore a = new Semaphore(2);
Semaphore b = new Semaphore(0);
Semaphore c = new Semaphore(0);
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 10; i = i + 3) {
try {
a.acquire();
System.out.println("a线程:" + i);
} catch (Exception e) {
log.error("a线程异常", e);
} finally {
b.release();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 2; i < 10; i = i + 3) {
try {
b.acquire();
System.out.println("b线程:" + i);
} catch (Exception e) {
log.error("b线程异常", e);
} finally {
c.release();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 3; i < 10; i = i + 3) {
try {
c.acquire();
System.out.println("c线程:" + i);
} catch (Exception e) {
log.error("c线程异常", e);
} finally {
a.release();
}
}
}
}).start();
}
}
执行结果:
a线程:1
a线程:4
b线程:2
b线程:5
c线程:3
c线程:6
a线程:7
b线程:8
c线程:9
上文可以看出:这是实现了三个线程,分别打印147 258 369,且每个线程打印两次的实例。
如果这样还不能看出问题,那么我们换种方式:
package com.test.abc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Semaphore;
@Slf4j
public class ThreadTestSemaphoreA {
public static void main(String[] args) {
Semaphore a = new Semaphore(3);
for (int i = 1; i < 10; i++) {
new Thread(new MyThread(a, "第" + i + "个")).start();
}
}
static class MyThread implements Runnable {
private Semaphore semaphore;
private String name;
MyThread(Semaphore semaphore, String name) {
this.semaphore = semaphore;
this.name = name;
}
@Override
public void run() {
int permits = semaphore.availablePermits();
if (permits > 0) {
System.out.println(name + "线程。" + "大于0,不可用");
} else {
System.out.println(name + "线程。" + "不大于0,等待");
}
try {
semaphore.acquire();
System.out.println(name + "线程。" + "占用");
Thread.sleep(100);
System.out.println(name + "线程。" + "使用完毕");
} catch (Exception e) {
log.error("a线程异常", e);
} finally {
semaphore.release();
}
}
}
}
执行结果:
第1个线程。大于0,不可用
第1个线程。占用
第2个线程。大于0,不可用
第2个线程。占用
第3个线程。大于0,不可用
第3个线程。占用
第4个线程。不大于0,等待
第7个线程。不大于0,等待
第6个线程。不大于0,等待
第5个线程。不大于0,等待
第8个线程。不大于0,等待
第9个线程。不大于0,等待
第1个线程。使用完毕
第2个线程。使用完毕
第3个线程。使用完毕
第7个线程。占用
第4个线程。占用
第6个线程。占用
第7个线程。使用完毕
第5个线程。占用
第4个线程。使用完毕
第8个线程。占用
第6个线程。使用完毕
第9个线程。占用
第5个线程。使用完毕
第8个线程。使用完毕
第9个线程。使用完毕
这次执行结果,估计都能看出来了,这像不像是上厕所排队的例子?
最后总结:
信号量本身并不是专门为互斥锁设计的,或者说信号量为0 1时为二进制信号量。但更多的是计数信号量,真正的多线程协调。