- 多线程编程步骤:
- 创建资源类,在资源类中封装属性和操作方法
- 如果是生产者消费者场景:在资源类的操作方法中,要完成 判断, 干活, 通知 操作
- 创建多个线程,调用资源类的操作方法
- 防止虚假唤醒,线程间通信场景下,资源类操作方法中的判断要使用while循环来判断,而不是if判断
- 场景1: 假设有一个初始变量0,同时有两个用户线程,一个线程对变量进行加1,另一个对变量减1,两个线程交替执行(甚至更多线程)
(1)使用Object对象的wait和notify来实现
package com.lchtest.juc.resource;
public class Share {
private int number = 0;
public synchronized void incr() throws InterruptedException {
while (number != 0){ // 注意,这里用if会造成虚假唤醒,导致不正确的结果
this.wait(); // !=0 等待,==0 执行加1操作,然后唤醒其他线程
}
// 操作
number ++;
System.out.println(Thread.currentThread().getName() + " do incr, number:" + number);
// 通知
notifyAll();
}
public synchronized void decr() throws InterruptedException {
while (number != 1){
this.wait();
}
number --;
System.out.println(Thread.currentThread().getName() + " do decr, number:" + number);
this.notifyAll();
}
}
package com.lchtest.juc;
import com.lchtest.juc.resource.Share;
/**
* 线程间通信编程步骤/ 生产者消费者模式
* 1. 创建资源类,封装资源属性和操作方法
* 2. 在资源类操作方法中:
* (1)判断 (2) 干活 (3)通知
* 3. 创建多个线程,调用资源类的操作方法
* 4. 防止虚假唤醒,判断条件放到while中
*/
public class ThreadCommunitionTest {
// 假设有两个用户线程,一个线程对变量进行加1,另一个对变量减1,两个线程交替执行
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0 ; i < 10; i ++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程a").start();
new Thread(() -> {
for (int i = 0 ; i < 10; i ++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程b").start();
new Thread(() -> {
for (int i = 0 ; i < 10; i ++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程c").start();
new Thread(() -> {
for (int i = 0 ; i < 10; i ++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程d").start();
}
}
使用ReentrantLock来实现:
package com.lchtest.juc.resource;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ShareByLock {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// +1操作
public void incr() throws InterruptedException {
// 加锁
lock.lock();
try { // try块里面放具体操作: 判断,干活,通知
while (number != 0){ // 判断
condition.await();
}
number ++; // 干活
System.out.println(Thread.currentThread().getName() + " do incr, number:" + number);
condition.signalAll(); // 通知,唤醒其他线程
} finally {
// 释放锁
lock.unlock();
}
}
// 减1操作
public void decr() throws InterruptedException {
lock.lock();
try {
while (number != 1){ // 判断,number不为1,则等待 number==1 则减1操作
condition.await();
}
number --;
System.out.println(Thread.currentThread().getName() + " do decr, number:" + number);
condition.signalAll(); // 通知
} finally {
lock.unlock();
}
}
}
调用:
package com.lchtest.juc;
import com.lchtest.juc.resource.Share;
import com.lchtest.juc.resource.ShareByLock;
/**
* 线程间通信编程步骤/ 生产者消费者模式
* 1. 创建资源类,封装资源属性和操作方法
* 2. 在资源类操作方法中:
* (1)判断 (2) 干活 (3)通知
* 3. 创建多个线程,调用资源类的操作方法
* 4. 防止虚假唤醒,判断条件放到while中
*/
public class ThreadCommunitionTestByLock {
// 假设有两个用户线程,一个线程对变量进行加1,另一个对变量减1,两个线程交替执行
public static void main(String[] args) {
ShareByLock share = new ShareByLock();
new Thread(() -> {
for (int i = 0 ; i < 10; i ++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程a").start();
new Thread(() -> {
for (int i = 0 ; i < 10; i ++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程b").start();
}
}
- 场景2: 三个线程顺序执行如下操作: 线程A打印5次,线程B打印10次,线程C打印15次,循环10轮
package com.lchtest.juc.resource;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 三个线程顺序执行如下操作: 线程A打印5次,线程B打印10次,线程C打印15次,循环10轮
*/
public class ShareResource {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int flag = 1;
public void print5(int loop) throws InterruptedException {
lock.lock();//上锁
try {
//判断
while (flag != 1){
condition1.await();
}
// 操作
for (int i = 1; i <= 5; i ++){
System.out.println(Thread.currentThread().getName() + "::" + i + ",外层循环次数:" + loop );
}
flag = 2; // 修改标志位,通知线程2开始操作
condition1.signalAll();
// 通知
} finally {
lock.unlock(); //解锁
}
}
public void print10(int loop) throws InterruptedException{
lock.lock();
try {
while (flag !=2){
condition2.await();
}
for (int i = 1; i <= 10; i++){
System.out.println(Thread.currentThread().getName() + "::" + i + ",外层循环次数:"+ loop);
}
flag = 3;
condition2.signalAll();
} finally {
lock.unlock();
}
}
public void print15(int loop) throws InterruptedException{
lock.lock();
try {
while (flag !=3){
condition3.await();
}
for (int i = 1; i <= 15; i++){
System.out.println(Thread.currentThread().getName() + "::" + i + ",外层循环次数:"+ loop);
}
flag = 1;
condition3.signalAll();
} finally {
lock.unlock();
}
}
}