从今天开始学习java多线程,平时自己写的程序很少用到多线程,也导致对于多线程这里一直不是很理解,接下来着重研究多线程。
多线程的安全问题
当我们有多个线程对同一个资源进行操作的时候,就会出现多线程安全问题,比如,A线程对一个资源设置值,B线程从同样的资源获取A资源设置的值,有可能在A没有完全设置完成,B线程就获取了执行权,此时就会出现多线程安全问题,这样说,大家可能不太理解,看下代码:
Resource.java
package com.test.safe;
public class Resource {
String name;
String sex;
}
A.java
package com.test.safe;
public class A implements Runnable {
private Resource resource;
public A(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
boolean isEng = true;
while(true) {
if (isEng) {
resource.name = "jerry";
resource.sex = "woman";
isEng = false;
} else {
resource.name = "杰瑞";
resource.sex= "男孩";
isEng = true;
}
}
}
}
B.java
package com.test.safe;
public class B implements Runnable {
private Resource resource;
public B(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
System.out.println(resource.name+"======="+resource.sex);
}
}
}
代码比较简单,这里A和B线程使用的是同一个资源,A资源设置resource的值,B线程获取resource的值。这里为了保证A,B两个线程操作的是同一个资源,使用了通过构造方法来初始化。为了区分效果,这里A线程分别设置不同的值。
测试代码:
Resource resource = new Resource();
new Thread(new A(resource)).start();
new Thread(new B(resource)).start();
此时打印结果如下:
可以看到这里就出现了线程安全问题。为什么会出现这样的问题呢??比如A线程对name赋值为jerry之后,还没来得及给sex设置woman值,此时线程的执行权就被B线程获取到了,所以会获取到的是”jerry======男孩”
解决线程安全问题
我们可以在两个线程操作同一个资源的地方使用synchronized为其加上锁即可。
A.java
synchronized (resource) {
if (isEng) {
resource.name = "jerry";
resource.sex = "woman";
isEng = false;
} else {
resource.name = "杰瑞";
resource.sex= "男孩";
isEng = true;
}
}
B.java
synchronized (resource) {
System.out.println(resource.name+"======="+resource.sex);
}
这里操作的都是同一个对象,resource,为了保证两个线程使用的同一个锁,所以synchronized中的对象必须唯一,这里使用了resoure对象保证唯一性。
此时输出结果如下:
可以看到此时已经正常输出了。
等待唤醒机制
虽然我们加了同步锁可是大家又没有发现,有一个问题,就是每次输出一大片中文,在输出一大片英文,如果我们想设置一个,在输出一个,应该怎么实现??这里就需要使用到等待唤醒机制。
这里在Resource.java中定义一个标记,由于该标记必须唯一,所以放在了Resource.java中,用来标记当前线程是否可以获取执行权:
package com.test.safe;
public class Resource {
String name;
String sex;
boolean flag = false;
}
此时A线程的run方法如下:
@Override
public void run() {
boolean isEng = true;
while(true) {
synchronized (resource) {
if (resource.flag) {//如果当前flag==true,表示B线程正在执行,此时需要等待,等待期间,后边的代码是不会执行的。
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (isEng) {
resource.name = "jerry";
resource.sex = "woman";
isEng = false;
} else {
resource.name = "杰瑞";
resource.sex= "男孩";
isEng = true;
}
resource.flag = true; //A线程设置数据执行结束,将flag设置为true,表示线程池中的其他线程可以获得线程的执行权了
resource.notify(); //唤醒使用同一个锁的线程,这里就是B线程
}
}
}
B线程的run方法如下:
@Override
public void run() {
while (true) {
synchronized (resource) {
if (!resource.flag) {//B线程正好和A线程相反
try {
resource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(resource.name+"======="+resource.sex);
resource.flag = false;
resource.notify();
}
}
}
此时运行的效果如下: