相关链接:
【Java多线程编程核心技术】1.Java多线程技能-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结
【Java多线程编程核心技术】3.线程间通信 -笔记总结
【Java多线程编程核心技术】4.Lock的使用-笔记总结
【Java多线程编程核心技术】5.定时器Timer-笔记总结
【Java多线程编程核心技术】6.单例模式与多线程-笔记总结
【Java多线程编程核心技术】7.拾遗增补-笔记总结
线程与线程之间不是独立的个体,他们彼此之间可以互相通信和协作。
可以通过不停地while语句轮询机制来检测某一个条件,但这样特别耗费CPU资源。(轮询间隔时间小,更浪费CPU资源,如果间隔时间大,有可能获取不到想要的数据)
餐厅里厨师与服务员之间的交互模式就属于等待/通知机制。
服务员去到菜的时间取决于厨师,所以服务员就有“等待(wait)”状态;
厨师做好菜放在“菜品传递台”上,其实就相当于一种“通知(notify)”;
wait 使线程停止运行,notify 使停止的线程继续运行。
关键字 synchronized 可以将任何一个 Object 对象作为同步对象看待,而 Java 为每个 Object 都实现了 wait() 和 notify() 方法,他们必须用在被 synchronized 同步的 Object 的临界区内。
通过调用 wait 方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而 notify 操作可以唤醒一个因调用了 wait 方法而处于阻塞状态的线程,使其进入就绪状态。
被重新唤醒的线程会试图重新获得临界区的控制权,继续执行临界区内 wait 之后的代码。
wait 方法可以使调用该方法的线程释放共享资源的锁,从运行状态退出,进入等待状态,直到再次被唤醒。
notify() 方法可以随机唤醒等待对列中等待同一共享资源的一个线程,并使该线程退出等待状态,进入可运行状态。
notifyAll() 方法可以随机唤醒等待对列中等待同一共享资源的所有线程,并使这些线程退出等待状态,进入可运行状态。
敲黑板,划重点:wait()立即释放锁,notify等线程执行完后再释放。
Java为每一个Object都实现了wait()和notify()方法,但它们必须用在被synchronized同步的Object的临界区,否则会抛出异常(IllegalMonitorStateException)
新创建一个线程对象后,在调用它的 start() 方法,系统会为此线程分配 CPU 资源,使其处于 Runnable(可运行)状态,如果线程抢占到 CPU 资源,此线程就会处于 Running (运行)状态
Runnable 和 Running 状态之间可以相互切换,因为线程有可能运行一段时间后,有其他优先级高的线程抢占了 CPU 资源,此时线程就从 Running 状态变成了 Runnable 状态。
线程进入 Runnable 状态大致有如下五种情况:
* 调用 sleep() 方法后经过的时间超过了指定的休眠时间
* 线程调用的阻塞 IO 已经返回,阻塞方法执行完毕
* 线程成功的获得了试图同步的监视器
* 线程正在等待某个通知,其他线程发出了通知
* 处于挂状态的线程调用了 resume 恢复方法
Blocked 是阻塞的意思,例如线程遇到一个 IO 操作,此时 CPU 处于空闲状态,可能会转而把 CPU 时间片分配给其他线程,这时也可以称为 “暂停”状态。Blocked 状态结束之后,进入 Runnable 状态,等待系统重新分配资源
出现阻塞状态的有如下五种情况:
* 线程调用 sleep 方法,主动放弃占用的处理器资源
* 线程调用了阻塞式 IO 方法,在该方法返回之前,该线程被阻塞
* 线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有
* 线程等待某个通知
* 程序调用了 suspend 方法将该线程挂起
run 方法运行结束后进入销毁阶段,整个线程执行完毕。
每个锁对象都是两个队列,一个是就绪队列,一个是阻塞队列
就绪队列:将要获得锁的线程,一个线程被唤醒后,才会进入就绪队列,等待CPU的调度
阻塞队列:被阻塞的线程,例如被wait后,就会进入阻塞队列
当方法wait()被执行后,锁被自动释放,但执行完notify()方法,锁却不自动释放。
当线程呈wait()状态时,调用线程对象的interrupt()方法会抛出如下异常
begin wait()
java.lang.InterruptedException
出现异常了,因为呈wait状态的线程被interrupt了!
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Unknown Source)
at service.Service.testMethod(Service.java:9)
at extthread.ThreadA.run(ThreadA.java:17)
如果通知过早,则会打乱程序正常的运行逻辑,例如在先notify()后,再wait(),会导致线程一直处于wait状态,不会被通知。
public class Add {
private String lock;
public Add(String lock) {
super();
this.lock = lock;
}
public void add() {
synchronized (lock) {
ValueObject.list.add("anyString");
lock.notifyAll();
}
}
}
public class Subtract {
private String lock;
public Subtract(String lock) {
super();
this.lock = lock;
}
public void subtract() {
try {
synchronized (lock) {
if (ValueObject.list.size() == 0) {
// while (ValueObject.list.size() == 0) {
System.out.println("wait begin ThreadName="
+ Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName="
+ Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size=" + ValueObject.list.size());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ValueObject {
public static List list = new ArrayList();
}
public class ThreadAdd extends Thread {
private Add p;
public ThreadAdd(Add p) {
super();
this.p = p;
}
@Override
public void run() {
p.add();
}
}
public class ThreadSubtract extends Thread {
private Subtract r;
public ThreadSubtract(Subtract r) {
super();
this.r = r;
}
@Override
public void run() {
r.subtract();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
String lock = new String("");
Add add = new Add(lock);
Subtract subtract = new Subtract(lock);
ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
subtract1Thread.setName("subtract1Thread");
subtract1Thread.start();
ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
subtract2Thread.setName("subtract2Thread");
subtract2Thread.start();
Thread.sleep(1000);
ThreadAdd addThread = new ThreadAdd(add);
addThread.setName("addThread");
addThread.start();
}
}
在if (ValueObject.list.size() == 0)情况下,输出结果:
wait begin ThreadName=subtract1Thread
wait begin ThreadName=subtract2Thread
wait end ThreadName=subtract2Thread
list size=0
wait end ThreadName=subtract1Thread
Exception in thread "subtract1Thread" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(Unknown Source)
at java.util.ArrayList.remove(Unknown Source)
at entity.Subtract.subtract(Subtract.java:24)
at extthread.ThreadSubtract.run(ThreadSubtract.java:16)
更换为while(ValueObject.list.size() == 0)情况下,输出结果:
wait begin ThreadName=subtract2Thread
wait begin ThreadName=subtract1Thread
wait end ThreadName=subtract1Thread
list size=0
wait end ThreadName=subtract2Thread
wait begin ThreadName=subtract2Thread
出现异常的原因:实现了两次删除remove()操作,第一次能正常删除,第二次则报异常
用while(…)替换掉if(…),当线程被通知(notify)后,会再进一次进行判断长度是非为0。
一生产与一消费:操作值
生产者:
package entity;
//生产者
public class P {
private String lock;
public P(String lock) {
super();
this.lock = lock;
}
public void setValue() {
try {
synchronized (lock) {
if (!ValueObject.value.equals("")) {
lock.wait();
}
String value = System.currentTimeMillis() + "_"
+ System.nanoTime();
System.out.println("set的值是" + value);
ValueObject.value = value;
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
消费者:
package entity;
//消费者
public class C {
private String lock;
public C(String lock) {
super();
this.lock = lock;
}
public void getValue() {
try {
synchronized (lock) {
if (ValueObject.value.equals("")) {
lock.wait();
}
System.out.println("get的值是" + ValueObject.value);
ValueObject.value = "";
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
存储值的对象:
package entity;
public class ValueObject {
public static String value = "";
生产者线程:
package extthread;
import entity.P;
public class ThreadP extends Thread {
private P p;
public ThreadP(P p) {
super();
this.p = p;
}
@Override
public void run() {
while (true) {
p.setValue();
}
}
}
消费者线程:
package extthread;
import entity.C;
public class ThreadC extends Thread {
private C r;
public ThreadC(C r) {
super();
this.r = r;
}
@Override
public void run() {
while (true) {
r.getValue();
}
}
}
运行类:
package test;
import entity.P;
import entity.C;
import extthread.ThreadP;
import extthread.ThreadC;
public class Run {
public static void main(String[] args) {
String lock = new String("");
P p = new P(lock);
C r = new C(lock);
ThreadP pThread = new ThreadP(p);
ThreadC rThread = new ThreadC(r);
pThread.start();
rThread.start();
}
}
输出结果:
set的值是1511090029379_209828520790577
get的值是1511090029379_209828520790577
set的值是1511090029379_209828520806165
get的值是1511090029379_209828520806165
set的值是1511090029379_209828520837751
get的值是1511090029379_209828520837751
set的值是1511090029379_209828520848006
get的值是1511090029379_209828520848006
set的值是1511090029379_209828520858262
get的值是1511090029379_209828520858262
set的值是1511090029379_209828520868517
get的值是1511090029379_209828520868517
set的值是1511090029379_209828520885336
get的值是1511090029379_209828520885336
多生产与多消费:操作值-假死
在未将notify()更换为notifyAll()之前,容易造成假死现象。
“假死”现象其实就是线程进入waiting等待状态。如果全部的线程都进入waiting状态,则呈现就不再执行任何业务功能了,整个项目呈停止状态。
假死出现的主要原因是有可能连续唤醒同类,导致所有线程呈waiting状态。
在将notify更换为notifyAll()以后,解除假死现象。
需要注意的是,在多生产与多消费中,一定要将if (ValueObject.value.equals(“”)) 更换为while(….),理由同上一节。
管道流是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。
通过使用管道,实现不同线程间的通信,而无需借助类似于临时文件类的东西
1.PipedInputStream和PipedOutputStream
2.PipedReader和PipedWriter
public class ThreadRead extends Thread {
private ReadData read;
private PipedInputStream input;
public ThreadRead(ReadData read, PipedInputStream input) {
super();
this.read = read;
this.input = input;
}
@Override
public void run() {
read.readMethod(input);
}
}
package extthread;
import java.io.PipedOutputStream;
import service.WriteData;
public class ThreadWrite extends Thread {
private WriteData write;
private PipedOutputStream out;
public ThreadWrite(WriteData write, PipedOutputStream out) {
super();
this.write = write;
this.out = out;
}
@Override
public void run() {
write.writeMethod(out);
}
}
package service;
import java.io.IOException;
import java.io.PipedInputStream;
public class ReadData {
public void readMethod(PipedInputStream input) {
try {
System.out.println("read :");
byte[] byteArray = new byte[20];
int readLength = input.read(byteArray);
while (readLength != -1) {
String newData = new String(byteArray, 0, readLength);
System.out.print(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package service;
import java.io.IOException;
import java.io.PipedOutputStream;
public class WriteData {
public void writeMethod(PipedOutputStream out) {
try {
System.out.println("write :");
for (int i = 0; i < 300; i++) {
String outData = "" + (i + 1);
out.write(outData.getBytes());
System.out.print(outData);
}
System.out.println();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package test;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import service.ReadData;
import service.WriteData;
import extthread.ThreadRead;
import extthread.ThreadWrite;
public class Run {
public static void main(String[] args) {
try {
WriteData writeData = new WriteData();
ReadData readData = new ReadData();
PipedInputStream inputStream = new PipedInputStream();
PipedOutputStream outputStream = new PipedOutputStream();
// inputStream.connect(outputStream);
outputStream.connect(inputStream);
ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
threadWrite.start();
Thread.sleep(2000);
ThreadRead threadRead = new ThreadRead(readData, inputStream);
threadRead.start();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
write :
1234567891011121314151 ......
read :
1234567891011121314151........
通过 // inputStream.connect(outputStream);// outputStream.connect(inputStream); 使两个Stream之间产生通信链接,这样才可以将数据进行输出与输入。
通过管道进行线程间通信:字符流
public void readMethod(PipedReader input) {
try {
System.out.println("read :");
char[] byteArray = new char[20];
int readLength = input.read(byteArray);
while (readLength != -1) {
String newData = new String(byteArray, 0, readLength);
System.out.print(newData);
readLength = input.read(byteArray);
}
System.out.println();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
.........
大部分与字节流类似
(最近也在琢磨做一个真正能派上用场的多线程备份数据库的小项目,后期完成了会在这里贴一个后续链接,先占个位吧~ 哈哈哈哈哈, 2017年11月19日留)
目标:创建20个线程,其中10个线程是将数据备份到数据库A,另外10个线程将数据备份到数据库B中去,并且备份数据库A和备份数据库B是交叉进行的。
DBTools:
package service;
public class DBTools {
volatile private boolean prevIsA = false;
synchronized public void backupA() {
try {
while (prevIsA == true) {
wait();
}
for (int i = 0; i < 5; i++) {
System.out.println("★★★★★");
}
prevIsA = true;
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void backupB() {
try {
while (prevIsA == false) {
wait();
}
for (int i = 0; i < 5; i++) {
System.out.println("☆☆☆☆☆");
}
prevIsA = false;
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
BackupA/BackupB:
package extthread;
import service.DBTools;
public class BackupA extends Thread {
private DBTools dbtools;
public BackupA(DBTools dbtools) {
super();
this.dbtools = dbtools;
}
@Override
public void run() {
dbtools.backupA();
}
}
package extthread;
import service.DBTools;
public class BackupB extends Thread {
private DBTools dbtools;
public BackupB(DBTools dbtools) {
super();
this.dbtools = dbtools;
}
@Override
public void run() {
dbtools.backupB();
}
}
Run:
package test.run;
import service.DBTools;
import extthread.BackupA;
import extthread.BackupB;
public class Run {
public static void main(String[] args) {
DBTools dbtools = new DBTools();
for (int i = 0; i < 20; i++) {
BackupB output = new BackupB(dbtools);
output.start();
BackupA input = new BackupA(dbtools);
input.start();
}
}
}
结果:
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
☆☆☆☆☆
★★★★★
★★★★★
★★★★★
★★★★★
★★★★★
.........
交替打印的原理就是:volatile private boolean prevIsA=false;
join():等待线程对象销毁。
package extthread;
public class MyThread extends Thread {
@Override
public void run() {
try {
int secondValue = (int) (Math.random() * 10000);
System.out.println(secondValue);
Thread.sleep(secondValue);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package test;
import extthread.MyThread;
public class Test {
public static void main(String[] args) {
try {
MyThread threadTest = new MyThread();
threadTest.start();
threadTest.join();
System.out.println("我想当threadTest对象执行完毕后我再执行,我做到了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
9715
我想当threadTest对象执行完毕后我再执行,我做到了
join 方法具有使线程排队运行的作用,有些类似同步的运行效果。join 与 synchronized 的区别是:join 在内部使用 wait() 方法进行等待,而 synchronized 关键字使用的是 “对象监视器” 原理做为同步。
在join过程中,如果当前线程对象被中断(interrupt),则当前线程出现异常(InterruptedException)。
方法join(long)中的参数是设定是等待的时间
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
只等待XXXX毫秒时间
方法join(long)的功能是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点
而Thread.sleep(long)方法却不释放锁!
通过类ThreadLocal实现每一个线程都有自己的共享变量,解决的就是每个线程绑定自己的值。
package extthread;
import tools.Tools;
public class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("ThreadA" + (i + 1));
} else {
System.out.println("ThreadA get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package extthread;
import tools.Tools;
public class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("ThreadB" + (i + 1));
} else {
System.out.println("ThreadB get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package tools;
public class Tools {
public static ThreadLocal tl = new ThreadLocal();
}
package test;
import tools.Tools;
import extthread.ThreadA;
import extthread.ThreadB;
public class Run {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
for (int i = 0; i < 100; i++) {
if (Tools.tl.get() == null) {
Tools.tl.set("Main" + (i + 1));
} else {
System.out.println("Main get Value=" + Tools.tl.get());
}
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
ThreadB get Value=ThreadB1
Main get Value=Main1
ThreadA get Value=ThreadA1
ThreadB get Value=ThreadB1
Main get Value=Main1
ThreadA get Value=ThreadA1
Main get Value=Main1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
Main get Value=Main1
...........
通过继承ThreadLocal类,重写 initialValue方法
package ext;
public class ThreadLocalExt extends ThreadLocal {
@Override
protected Object initialValue() {
return "我是默认值 第一次get不再为null";
}
}
实现首次get()不为null;
该类扩展了 ThreadLocal,为子线程提供从父线程那里继承的值:在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。
通常,子线程的值与父线程的值是一致的;但是,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数(parentValue+“XXXX”)。