Java多线程题目 不同解决办法
题目 两个线程轮换打印foo 和bar
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程将会共用一个 FooBar 实例:
线程 A 将会调用 foo() 方法,而
线程 B 将会调用 bar() 方法
请设计修改程序,以确保 "foobar" 被输出 n 次
思考:本质上两个函数对应两个线程 线程实际就是函数的执行过程,两个不同线程对应两个函数
一个函数打印foo
一个函数打印bar
中间怎么同步,需要共享变量,变量无非三种 静态 实例变量 局部变量
函数无非是对变量操作
静态变量 所有对象所有方法都共享
范围:
不同对象的同一方法,
不同对象的不同方法
同一对象的不同方法
实例变量 单一对象的所有方法都共享
范围:
同一对象的不同方法
此题用实例变量就可以解决
方法1:自旋锁+出让CPU
自旋锁的目的是为了解决同步问题而不是互斥问题
单核cpu需要 线程让出 yield() 目的是不让出 会一直空转 有可能 超时 。不让出 就是等时间片用完 再线程调度 也需要线程切换 结果:线程切换会进入内核态
多核cpu不需要 线程让出 不存在线程切换 大大提高效率 线程空转 也不需要切换线程 全部在用户态,没有进入内核态
操作系统指挥线程切换, 操作系统在内核态指挥线程切换
class FooBar5 {
private int n;
public FooBar5(int n) {
this.n = n;
}
volatile boolean permitFoo = true;
public void foo() throws InterruptedException {
for (int i = 0; i < n; ) {
if(permitFoo) {
printFoo();
i++;
permitFoo = false;
}else{
Thread.yield();
}
}
}
public void bar() throws InterruptedException {
for (int i = 0; i < n; ) {
if(!permitFoo) {
printBar();
i++;
permitFoo = true;
}else{
Thread.yield();
}
}
}
}
方法2 synchronized + 标志位 + 唤醒
通常先互斥 再通过循环同步
线程阻塞时的特点:
该线程放弃CPU的使用权,暂停运行,只有当阻塞的原因消除后才回到就绪状态进行运行
synchronized的作用:互斥
线程a获得锁执行了一段同步代码,线程b竞争同一把锁由于无法获得相关的同步锁,只能进入阻塞状态,等获取了同步锁,才能恢复运行
wait作用:同步
wait必须要在synchronized中使用
wait的前提是有锁
1.先释放锁
2.进行阻塞等待
3.收到通知之后,重新尝试获取锁,并且在获取锁后,继续往下执行.
class FooBar3 {
private int n;
// 标志位,控制执行顺序,true执行printFoo,false执行printBar
private volatile boolean type = true;
private final Object foo= new Object(); // 锁标志
public FooBar3(int n) {
this.n = n;
}
public void foo() throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (foo) {
while(!type){
foo.wait();
}
printFoo();
type = false;
foo.notifyAll();
}
}
}
public void bar() throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (foo) {
while(type){
foo.wait();
}
printBar();
type = true;
foo.notifyAll();
}
}
}
}
给出方法2的多线程具体实现-内部类
public class FooBar3Main {
public static void main(String[] args) {
// 假设n的值为10
int n = 10;
FooBar3 fooBar3 = new FooBar3(n);
// 创建两个线程分别调用foo和bar方法lamda表达式简化
Thread threadFoo = new Thread(() -> {
try {
fooBar3.foo();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread threadBar = new Thread(() -> {
try {
fooBar3.bar();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 启动线程
threadFoo.start();
threadBar.start();
}
}
外部类 写两个
两个 thread类 都用 foobar类做对象 run方法执行不同的 函数 foo或bar函数
方法3 信号量 适合控制顺序
class FooBar2 {
private int n;
private Semaphore foo = new Semaphore(1);
private Semaphore bar = new Semaphore(0);
public FooBar2(int n) {
this.n = n;
}
public void foo() throws InterruptedException {
for (int i = 0; i < n; i++) {
foo.acquire();
printFoo();
bar.release();
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
bar.acquire();
printBar();
foo.release();
}
}
}