多线程之间实现通讯
1 什么是多线程之间的通讯
在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步。通信是指线程之间以如何来交换信息。一般线程之间的通信机制有两种:共享内存和消息传递。
Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。
2 通讯业务需求及实现
需求是:第一个线程写入用户,另一个线程取读取用户,实现读一个,写一个操作。
2.1 代码实现基本实现
- 定义User类
package com.lijie;
public class User {
String name;
String sex;
}
- 输入线程资源
package com.lijie;
//输入类
class InputThread extends Thread {
private User user;
public InputThread(User user) {
this.user = user;
}
public void run() {
int count = 0;
while (true) {
if (count % 2 == 0) {
user.name = "张三";
user.sex = "男";
} else {
user.name = "小紅";
user.sex = "女";
}
count += 1;
}
}
}
- 输出线程资源
package com.lijie;
//输出类
class OutThread extends Thread {
private User user;
public OutThread(User user) {
this.user = user;
}
public void run() {
while (true) {
System.out.println(user.name + "====" + user.sex);
}
}
}
- 创建测试
package com.lijie;
public class Test {
public static void main(String[] args) {
User user = new User();
InputThread inputThread = new InputThread(user);
OutThread outThread = new OutThread(user);
inputThread.start();
outThread.start();
}
}
运行后你会发现,数据非常乱
2.2 使用synchronized解决线程安全问题
加上synchronized同步代码块
修改输入和输出线程资源俩个类:
package com.lijie;
//输入类
class InputThread extends Thread {
private User user;
public InputThread(User user) {
this.user = user;
}
public void run() {
int count = 0;
synchronized (this) {
while (true) {
if (count % 2 == 0) {
user.name = "张三";
user.sex = "男";
} else {
user.name = "小紅";
user.sex = "女";
}
count += 1;
}
}
}
}
package com.lijie;
//输出类
class OutThread extends Thread {
private User user;
public OutThread(User user) {
this.user = user;
}
public void run() {
synchronized (this) {
while (true) {
System.out.println(user.name + "====" + user.sex);
}
}
}
}
执行结果:发现数据没有混乱了
2.3 改变需求
我觉得这样输出也很乱,我想要一个张三,一个小红,在线程安全的情况下一个一个输出如何实现:
2.4 wait、notify和方法
- 因为涉及到对象锁,他们必须都放在synchronized中来使用. wait、notify一定要在synchronized里面进行使用。
- wait() :必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行
- notify() :唤醒因锁池中的线程,使之运行
2.6 修改代码
- 修改User类,添加标识
package com.lijie;
public class User {
String name;
String sex;
//线程通讯标识
public boolean sign = false;
}
- 修改输入类,添加wait和notify方法
package com.lijie;
//输入类
class InputThread extends Thread {
private User user;
public InputThread(User user) {
this.user = user;
}
public void run() {
int count = 0;
while (true) {
synchronized (user) {
if (user.sign == true) {
try {
// 当前线程变为等待,但是可以释放锁
user.wait();
} catch (Exception e) {
}
}
if (count % 2 == 0) {
user.name = "张三";
user.sex = "男";
} else {
user.name = "小紅";
user.sex = "女";
}
count += 1;
user.sign = true;
// 唤醒当前线程
user.notify();
}
}
}
}
- 修改输出类,添加wait和notify方法
package com.lijie;
//输出类
class OutThread extends Thread {
private User user;
public OutThread(User user) {
this.user = user;
}
public void run() {
while (true) {
synchronized (user) {
if (user.sign==false) {
try {
// 当前线程变为等待,但是可以释放锁
user.wait();
} catch (Exception e) {
}
}
System.out.println(user.name + "====" + user.sex);
user.sign = false;
// 唤醒当前线程
user.notify();
}
}
}
}
执行结果:
2.7 wait与sleep区别
- sleep()方法,是属于Thread类中的。
- wait()方法,则是属于Object类中的。
- sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是没有释放锁
- wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
3 Lock锁
在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
3.1 Lock写法
private Lock lock = new ReentrantLock();
//使用完毕释放后其他线程才能获取锁
public void lockTest(Thread thread) {
lock.lock();//获取锁
try {
} catch (Exception e) {
}finally {
lock.unlock(); //释放锁
}
3.2Lock锁示例代码
package com.lijie;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
//使用完毕释放后其他线程才能获取锁
public void lockTest(Thread thread) {
lock.lock();//获取锁
try {
System.out.println("线程" + thread.getName() + "获取锁");
} catch (Exception e) {
System.out.println("线程" + thread.getName() + "发生了异常");
} finally {
System.out.println("线程" + thread.getName() + "执行完毕释放锁");
lock.unlock(); //释放锁
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(new Runnable() {
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "线程一");
Thread thread2 = new Thread(new Runnable() {
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "线程二");
// 启动2个线程
thread2.start();
thread1.start();
}
}
4 Condition
Condition与Lock的关系就类似于synchronized与Object.wait()/signal()
- Conditionaw.ait():必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行。和Object.wait()方法很相似。
- Conditionaw.singal():唤醒因锁池中的线程,使之运行。和Object.wait()方法很相似。
4.1 Condition和Lock代码示例
package com.lijie;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class User {
public String name;
public String sex;
//定义标识变量
public boolean flag = false;
//定义Lock锁和ReentrantLock实现类
Lock lock = new ReentrantLock();
}
class InputThread extends Thread {
private User user;
//定义Condition属性,靠他来释放锁和唤醒锁
Condition newCondition;
//构造参数赋值
public InputThread(User user, Condition newCondition) {
this.user = user;
this.newCondition = newCondition;
}
//使用完毕释放后其他线程才能获取锁
public void run() {
int count = 0;
while (true) {
try {
//获取锁
user.lock.lock();
if (user.flag) {
try {
//使当前线程等待,同时释放当前锁
newCondition.await();
} catch (Exception e) {
}
}
if (count == 0) {
user.name = "张三";
user.sex = "男";
} else {
user.name = "小红";
user.sex = "女";
}
count = (count + 1) % 2;
user.flag = true;
//唤醒一个在等待中的线程
newCondition.signal();
} catch (Exception e) {
} finally {
//释放锁
user.lock.unlock();
}
}
}
}
class OutThread extends Thread {
private User user;
private Condition newCondition;
public OutThread(User user, Condition newCondition) {
this.user = user;
this.newCondition = newCondition;
}
public void run() {
while (true) {
try {
user.lock.lock();
if (!user.flag) {
try {
newCondition.await();
} catch (Exception e) {
}
}
System.out.println(user.name + "===" + user.sex);
user.flag = false;
newCondition.signal();
} catch (Exception e) {
} finally {
user.lock.unlock();
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
User user = new User();
Condition newCondition = user.lock.newCondition();
InputThread inputThread = new InputThread(user, newCondition);
OutThread outThread = new OutThread(user, newCondition);
inputThread.start();
outThread.start();
}
}
5 synchronized与Lock的区别
- 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,自动放弃的方法tryLock(),具有更完善的错误恢复机制。
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。