一 线程间通信1 - 示例代码
之前写的程序,每个线程运行的代码都是一致的。
而现在需要线程之间通信,它们代码要不一致。
A线程往资源写数据,B线程往资源读数据,同时运行。
这样就有两个run方法同时运行,也就是有两个类。
Input类对象存入数据 → 对象(String name,String Sex) → Output类对象读取数据
首先,资源要描述一下,Input和Output要描述一下。
package Test3;
class Res {
public String name;
public String sex;
}
class Input implements Runnable {
Res res;
boolean flag = true;
public Input(Res res) {
this.res = res;
}
public void run() {
while (true) {
if (flag == true) {
res.name = "麦克";
res.sex = "男";
flag = false;
} else {
res.name = "May";
res.sex = "girl";
flag = true;
}
}
}
}
class Output implements Runnable {
Res res;
public Output(Res res) {
this.res = res;
}
public void run() {
while (true) {
System.out.println(res.name + " --- " + res.sex);
}
}
}
public class ThreadTongXin {
public static void main(String[] args) {
Res res = new Res();
new Thread(new Input(res)).start();
new Thread(new Output(res)).start();
}
}
程序输出:
麦克 --- girl
麦克 --- 男
May --- girl
麦克 --- 男
麦克 --- girl
May --- 男
为什么会出现输出的数据混乱,而且一下子输出一大片同一个人数据的情况呢?
原因:一下子出一大片 的原因是Outpu输出完,不一定是Input获取到CPU执行权,
可以继续是Output获取到,连续都获取到了,就会出现一大片一个人的数据了。
另外,Input往Res类的对象res里刚存完name,Output就获取到CPU执行权,
然后就输出了,性别的数据也就混乱了。
二 线程间通信2 - 解决安全问题
当然是“同步”。
两个类的while(true)中加上同步代码块,发现还是不行。
此时应该想到,同步需要满足两个前提:
1.这个程序只同步了一个线程,另外一个没有同步,而它们处理的是同一资源;
2.不是同一个锁,改为内存中唯一的对象,例如:Input.class,Res.class,res都行。
class Input implements Runnable {
Res res;
boolean flag = true;
public Input(Res res) {
this.res = res;
}
public void run() {
while (true) {
synchronized(Res.class){ //注意这里
if (flag == true) {
res.name = "麦克";
res.sex = "男";
flag = false;
} else {
res.name = "May";
res.sex = "girl";
flag = true;
}
}
}
}
}
class Output implements Runnable {
Res res;
public Output(Res res) {
this.res = res;
}
public void run() {
while (true) {
synchronized(Res.class){ //注意这里
System.out.println(res.name + " --- " + res.sex);
}
}
}
}
为节省篇幅,仅截取Input和Ouput两个被修改类的代码。
三 线程间通信3 - 等待唤醒机制
现在需要将程序修改为:一个写入后,等另一个输出才继续写入。
为了满足需求,可以再Res类中加入标记,用于标记Input类的对象是否已写入新值。
模拟执行过程:
Input对象判断标记为false,无新值,写入并将标记改为true,Input对象再输入判断到为true,就等着。
Output对象判断,标记为true有内容,输出然后修改标记为false,唤醒Input的对象,它自己等着。
等待的线程在哪里?
线程运行时,内存中会建立线程池,等待的线程就存放在线程池中,
notify唤醒线程池中的线程,通常唤醒第一个wait的。
notify的原理,可以用“抓人游戏”做类比。
如果想唤醒线程池中全部正在wait的线程,可以用notifyAll()方法。
注意:wait()、notify()、notifyAll()是定义在Object类中的方法,
观察一下这三个方法在API文档中的描述。
注意文档中“必须拥有对象监视器”和“在此对象监视器上”的描述,
这表明,这三个方法,必须用在 synchronized“同步”当中,不然会抛异常,
这里十分重要,因为这个原因,上机浪费了一个多半小时!!!
参考: IllegalMonitorStateException 异常例子的解决方法
而且,必须标示出wait()方法所操作的线程所属的锁。
为什么?因为同步有可能会出现嵌套。
Q:为什么定义在Object类?
A:因为“锁”可以是任意对象,任意对象能调用的方法,当然是要在Object类中。
示例代码:
/*
线程间通讯:
其实就是多个线程在操作同一个资源,
但是操作的动作不同。
*/
import java.lang.*;
class Res {
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable {
private Res r;
Input(Res r) {
this.r = r;
}
public void run() {
int x = 0;
while (true) {
synchronized (r) {
if (r.flag) {
try {
r.wait();
} catch (Exception e) {
}
}
if (x == 0) {
r.name = "mike";
r.sex = "man";
} else {
r.name = "丽丽";
r.sex = "女女女女女";
}
x = (x + 1) % 2;
r.flag = true;
r.notify();
}
}
}
}
class Output implements Runnable {
private Res r;
Output(Res r) {
this.r = r;
}
public void run() {
while (true) {
synchronized (r) {
if (!r.flag) {
try {
r.wait();
} catch (Exception e) {
}
}
System.out.println(r.name + " " + r.sex);
r.flag = false;
r.notify();
}
}
}
}
public class Demo {
public static void main(String args[]) {
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
四 线程间通信4 - 代码优化
class Res {
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name, String sex) {
if (this.flag) {
try {
this.wait();
} catch (Exception e) {
}
}
this.name = name;
this.sex = sex;
this.flag = true;
this.notify();
}
public synchronized void out() {
if (!this.flag) {
try {
this.wait();
} catch (Exception e) {
}
}
System.out.println(this.name + " " + this.sex);
this.flag = false;
this.notify();
}
}
class Input implements Runnable {
private Res r;
Input(Res r) {
this.r = r;
}
public void run() {
int x = 0;
while (true) {
if (x == 0) {
r.set("mike", "man");
} else {
r.set("丽丽", "女女女女女");
}
x = (x + 1) % 2;
}
}
}
class Output implements Runnable {
private Res r;
Output(Res r) {
this.r = r;
}
public void run() {
while (true) {
r.out();
}
}
}
public class Demo {
public static void main(String args[]) {
Res r = new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
加上private和getter、setter方法赋值时需要同步,因为可能出现安全问题。
五 线程间通信5 - 生产者消费者问题
上面的示例代码,输出的是姓名,不好分辨。
现在通过生产者消费者问题(“生产一个、消费一个……”)来进行说明。
通过带编号产品(新产品编号在setter方法中+1)来进行区分。
Q:单个生产单个消费没问题,那如果换为多个生产多个消费呢?
可能会出现生产一个,消费两个的情况,或者生产两个消费一个,为什么呢?
A:因为没判断标记flag,只要将标记判断的if改为while就可以了,
但会全部等待了。还要notifyAll()。
package Test3;
class Resource {
private String name;
private int conut = 1;
private boolean ResControlFlag = false;
public synchronized void Set(String name) {
while (true) {
while (ResControlFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + this.conut++;
System.out.println(Thread.currentThread().getName() + " --------> "
+ this.name);
ResControlFlag = true;
this.notifyAll();
}
}
public synchronized void Out() {
while (true) {
while (!ResControlFlag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " <--- "
+ this.name);
ResControlFlag = false;
this.notifyAll();
}
}
}
class Producer implements Runnable {
private Resource res;
public Producer(Resource res) {
this.res = res;
}
public void run() {
res.Set("电脑");
}
}
class Consumer implements Runnable {
private Resource res;
public Consumer(Resource res) {
this.res = res;
}
public void run() {
res.Out();
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
Resource res = new Resource();
new Thread(new Producer(res), "生产者").start();
new Thread(new Consumer(res), "消费者").start();
new Thread(new Producer(res), "生产者").start();
new Thread(new Consumer(res), "消费者").start();
new Thread(new Producer(res), "生产者").start();
new Thread(new Consumer(res), "消费者").start();
}
}
Q:对于多个生产者和消费者,为什么要定义while判断标记?
A:为了让被唤醒的线程再一次判断标记。
Q:为什么要使用notifyAll?
A:因为需要唤醒对方线程,因为只用notify,
容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
六 线程间通信6 - 生产者消费者问题(升级版)
Q:这时候,问题就来了,notifyAll会唤醒在此对象监视器上等待的所有线程,
怎样才能“不唤醒自己,而只唤醒其他线程呢?”
A:JDK1.5中提供了现成的升级解决方案:将“同步synchronized
”替换成“显式锁Lock”操作,
将 object 中的 wait ,notify,notifyAll,替换成了condition 对象。
public interface Lock:
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock这个接口,把隐式的加锁,变成显式的加锁了。
public interface Condition :
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
synchronized → Lock
Condition → wait、notify 和 notifyAll
示例代码:
import java.util.concurrent.locks.*;
class Demo {
public static void main(String args[]) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t3 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name) throws InterruptedException {
lock.lock();
try {
while (flag) {
condition_pro.await();
}
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName() + "...生产者..."
+ this.name);
flag = true;
condition_con.signal();
} finally {
lock.unlock();
}
}
public void out() throws InterruptedException {
lock.lock();
try {
while (!flag) {
condition_con.await();
}
System.out.println(Thread.currentThread().getName()
+ "...消费者..........." + this.name);
flag = false;
condition_pro.signal();
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
private Resource res;
Producer(Resource res) {
this.res = res;
}
public void run() {
while (true) {
try {
res.set("+商品+");
} catch (InterruptedException e) {
}
}
}
}
class Consumer implements Runnable {
private Resource res;
Consumer(Resource res) {
this.res = res;
}
public void run() {
while (true) {
try {
res.out();
} catch (InterruptedException e) {
}
}
}
}
更多用法和说明直接看API文档就行了,说得很清楚。
七 线程停止
查阅API文档,Thread类的stop()方法已经过时。
那我们应该如何让线程停止呢?
停止线程只有一种方法,就是让run()方法结束。
让run()方法结束的两种情况:
1.通常情况:多线程运行,运行代码通常是循环结构。只要控制循环,就可以让run方法结束,也就是线程结束。
2.特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结状态进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
使用interrupt()方法,将处于冻结状态的线程强制的恢复到运行状态中来。
class Demo {
public static void main(String args[]) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while (true) {
if (num++ == 60) {
st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName() + num);
}
}
}
class StopThread implements Runnable {
private boolean flag = true;
public void run() {
while (flag) {
System.out.println(Thread.currentThread().getName() + "....run");
}
}
public void changeFlag() {
flag = false;
}
}
八 守护线程
守护线程:可以理解为“后台线程”,把线程标记为“后台线程”后,
它开启、运行等等状态跟“前台线程”都没有区别,就结束有区别,
当所有”前台线程”都结束后,”后台线程”会自动结束。
void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。
知识点:
1.setDeamon(true)设为守护线程
2.全剩下守护线程,程序结束
九 Join方法
join()方法:
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
class Demo {
public static void main(String args[]) throws Exception {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t1.join();
t2.start();
t2.join();
for (int x = 0; x < 70; x++) {
System.out.println(Thread.currentThread().getName() + " " + x);
}
System.out.println("over");
}
}
class StopThread implements Runnable {
public void run() {
for (int x = 0; x < 70; x++) {
System.out.println(Thread.currentThread().getName() + " " + x);
}
}
}
join()方法,抢先获得CPU的执行权,先于主线程执行,
例如,t1.join();。此时,主线程处于冻结状态,只有t1可以运行;
只有t1运行结束,主线程才可以运行,等待让主线程让出执行权的线程死亡。一般用于临时加入线程。
join方法的位置会有影响,如果放在多个线程开启的下面,例如,t1.start();t2.start();t1.join();,
t1和t2会交替执行,因为t1抢的是主线程的运行权,和t2无关。主线程依然在t1结束后运行,和t2的执行无关。
当A线程执行到了B线程的join方法时,A线程就会等待,等B线程都执行完,A线程才会执行。可以用来临时加入线程执行。可以嵌套使用。
如果t1等待,会造成主线程无法运行,所以使用interrupt方法来结束冻结状态,继续运行。
因为是强制恢复运行,所以可能会出现异常,所以join方法会抛出InterruptedException异常。
开启线程的线程组,称为主线程组,里面的成员构成一个线程组。也可以通过ThreadGroup创建新的对象,封装进其他的组。很少用。
十 优先级和yield方法
1、关于线程的优先级
线程的执行有优先级,可通过setPriority(int newPriority)方法更改线程的优先级。
优先级:代表抢资源的频率,设置越高,执行越频繁,优先执行。
所有线程的优先级,包括主线程,优先级默认设置为5,共有10级,数字越大,越优先执行。
如果数字相差不大,优先程度几乎没有差别,只有1、5、10之间最明显,
分别对应Thread类的三个字段:MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY
。
虽然可以设为最高级,也只是相对高而已,不可能只执行一个线程。
2、yield方法
Thread.yield();暂停当前正在执行的线程对象,并执行其他线程。
多个线程运行时,通过临时强制释放线程的执行权,使得其他线程可以执行。
这样可以减缓线程的运行,使得线程都有机会运行。
3、开发时如何编写线程
Q:什么时候用多线程?
A:当某些代码需要同时运行时,可以用单独的线程进行封装。
也可以封装成类,或者对象。
示例代码:
new Thread()//通过Thread类建立匿名类
{
public void run()
{
for (int x=0;x<30;x++ )
{
System.out.println(Thread.currentThread().getName()+"...Jobs..."+x);
}
}
}.start();
Runnable r = new Runnable()//通过Runnable接口建立匿名类对象
{
public void run()
{
for (int x=0;x<30;x++ )
{
System.out.println(Thread.currentThread().getName()+"...Intel..."+x);
}
}
};
new Thread(r).start();