学习内容来源于蚂蚁课堂,感谢蚂蚁课堂大佬的指导,本文仅供学习记录使用,如有不适,请联系作者。
一、什么是多线程之间的通讯
多个线程对同一个资源(共享资源),每个线程对共享资源做的动作不同,操作不同。
1.1产生问题的原因:
- 举例:生产者写操作,username = 仓小姐,sex=女,消费者线程读操作,读到username = 仓小姐,sex未读取时,生产者线程写操作 username = 加老师,sex = 男,此时读线程读到sex = 男,打印 仓小姐,男。
- 举例:根据java内存模型,读线程本地私有内存没有及时与主内存同步,造成数据混乱。
1.2解决思路
写线程与读线程要进行同步,读的时候不能写,写的时候不能读,可见性不具备原子性(加Volatile关键字不管用)线程不安全,因为你即使具备了及时可见,但是你无法改变例1的情况,无法改变共享资源的某个属性被变更这一事实。
/**
*
*问题代码演示
*/
public class Demo01 {
public static void main(String[] args) {
Res res = new Res();
Out out = new Out(res);
Input input = new Input(res);
out.start();
input.start();
}
}
/**
* 共享资源
*/
class Res {
public String userName;
public String sex;
}
/**
* 写操作
*/
class Out extends Thread{
Res res;
public Out(Res res) {
this.res = res;
}
@Override
public void run() {
//写操作
int count = 0;
while (true) {
if (count == 0) {
res.userName = "苍小姐";
res.sex = "女";
}else {
res.userName = "加老师";
res.sex = "男";
}
// 计算是奇数还是偶数
count = (count + 1) % 2;
}
}
}
/**
* 读操作
*/
class Input extends Thread{
Res res;
public Input(Res res) {
this.res = res;
}
@Override
public void run() {
while (true) {
System.out.println(res.userName + "," + res.sex);
}
}
}
...省略
苍小姐,女
加老师,男
苍小姐,男
加老师,女
加老师,男
加老师,女
加老师,女
...省略
二、wait()与notify()
/**
*
* 做到消费一个生产一个,生产一个消费一个
*
* wait()让当前线程产品能够运行状态变为休眠状态
* notify()让当前线程从休眠状态变为准备状态
* 同步状态才能使用,并且是同一个锁资源
*
*
* wait()用于同步中,可以释放锁的资源,需要notify唤醒
* sleep()不会释放锁的资源 ,只要到时间就会唤醒
*/
public class Demo02 {
public static void main(String[] args) {
Res2 res = new Res2();
Out2 out = new Out2(res);
Input2 input = new Input2(res);
out.start();
input.start();
}
}
class Res2 {
public volatile String userName;
public volatile String sex;
//为true写操作等待,为flase读操作等待
public volatile boolean flag = false;
}
class Out2 extends Thread{
Res2 res;
public Out2(Res2 res) {
this.res = res;
}
@Override
public void run() {
//写操作
int count = 0;
while (true) {
synchronized (res) {
System.out.println("写线程进入");
if (res.flag) {
try {
res.wait(); //让当前线程产品能够运行状态变为休眠状态,并释放锁资源
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (count == 0) {
res.userName = "苍老师";
res.sex = "女";
}else {
res.userName = "崔嘉文";
res.sex = "男";
}
// 计算是奇数还是偶数
count = (count + 1) % 2;
res.flag = true;
System.out.println("写线程结束");
//唤醒读操作
res.notify();
}
}
}
}
class Input2 extends Thread{
Res2 res;
public Input2(Res2 res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
System.out.println("读线程进入");
if (!res.flag) {
try {
res.wait(); //让当前线程产品能够运行状态变为休眠状态,并释放锁资源
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(res.userName + "," + res.sex);
res.flag = false;
System.out.println("读线程结束");
//唤醒写操作
res.notify();
}
}
}
}
//输出
...省略
苍小姐,女
加老师,男
苍小姐,女
加老师,男
苍小姐,女
加老师,男
苍小姐,女
...省略
此代码有不妥之处或提供更好的例证,欢迎大家反馈!
三、JDK1.5 Lock锁
lock锁 jdk1.5并发包 保证线程安全问题 属于手动挡,手动上锁,手动释放锁 灵活性高。
synchronized锁 从代码开始就上锁 代码结束就释放锁 内置锁,自动化,效率低,扩展不高,不能自定义。
public class Demo03 {
public static void main(String[] args) {
Res3 res = new Res3();
//用于线程间通讯
Condition condition = res.lock.newCondition();
Out3 out = new Out3(res,condition);
Input3 input = new Input3(res,condition);
out.start();
input.start();
}
}
class Res3 {
public volatile String userName;
public volatile String sex;
public volatile boolean flag = false;
//重入锁
Lock lock = new ReentrantLock();
}
class Out3 extends Thread {
Res3 res;
Condition condition;
public Out3(Res3 res,Condition condition) {
this.res = res;
this.condition = condition;
}
@Override
public void run() {
// 写操作
int count = 0;
while (true) {
//上锁
res.lock.lock();
try {
if (res.flag) {
//只能这样写,锁对象.await()只能用于同步synchronized中
//是本线程休眠,并释放锁
condition.await();
}
if (count == 0) {
res.userName = "苍小姐";
res.sex = "女";
} else {
res.userName = "加老师";
res.sex = "男";
}
// 计算是奇数还是偶数
count = (count + 1) % 2;
res.flag = true;
//唤醒
condition.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
res.lock.unlock();
}
}
}
}
class Input3 extends Thread {
Res3 res;
Condition condition;
public Input3(Res3 res,Condition condition) {
this.res = res;
this.condition = condition;
}
@Override
public void run() {
while (true) {
res.lock.lock();
try {
if (!res.flag) {
condition.await();
}
System.out.println(res.userName + "," + res.sex);
res.flag = false;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
res.lock.unlock();
}
}
}
}
//输出
...省略
苍小姐,女
加老师,男
苍小姐,女
加老师,男
苍小姐,女
...省略
3.1 Lock锁与synchronized锁的区别
- lock接口可以尝试非阻塞地获取锁,当前线程尝试获取锁。如果这一时刻没有被其他线程获取到,则成功获取并持有锁。
- lock接口能被中断的获取锁,与synchronized不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时释放锁。
- lock接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。
后续深入讨论
四、怎么停止线程
- stop 不建议,方法过期,强制中断,不可恢复。
- 建议从代码执行完毕角度考虑。
- interrupt(),抛出异常,在try{}catch{}中做手脚。
public class Demo04 {
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
System.out.println("主线程");
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(1000);
System.out.println("主线程..." + i);
if (i == 6) {
thread.stopThread(false);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class StopThread extends Thread {
private volatile boolean flag = true;
@Override
public void run() {
System.out.println("子线程开始");
while (flag) {
}
System.out.println("子线程结束");
}
public void stopThread(boolean flag) {
System.out.println("调用了子线程停止方法");
this.flag = flag;
System.out.println("将flag的值修改为" + flag);
}
}
...省略
主线程...3
主线程...4
主线程...5
主线程...6
调用了子线程停止方法
将flag的值修改为false
子线程结束
主线程...7
主线程...8
...省略
五、ThreadLock
本地线程,为每一个线程提供一个本地局部变量。
原理:Map<当前线程,value>
public class Demo05 {
public static void main(String[] args) {
ResNumber resNumber = new ResNumber();
ThreadLockDemo t1 = new ThreadLockDemo(resNumber);
ThreadLockDemo t2 = new ThreadLockDemo(resNumber);
ThreadLockDemo t3 = new ThreadLockDemo(resNumber);
t1.start();
t2.start();
t3.start();
}
}
class ResNumber{
public int count = 0;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
};
};
public String getNumber() {
//获取值
count = threadLocal.get() + 1;
//赋值
threadLocal.set(count);
return count + "" ;
}
}
class ThreadLockDemo extends Thread{
private ResNumber resNumber;
public ThreadLockDemo(ResNumber resNumber) {
this.resNumber = resNumber;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(getName() + "---" + resNumber.getNumber());
}
}
}
Thread-0---1
Thread-0---2
Thread-0---3
Thread-1---1
Thread-1---2
Thread-1---3
Thread-2---1
Thread-2---2
Thread-2---3