本文主要介绍Java多线程中的同步,也就是如何在Java中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题。
非线程安全
什么是非线程安全?非线程安全是指多个线程对同一个对象中的实例变量进行并发访问时发生,发生的结果就是脏读,也就是取到的结果其实是被更改过的,而线程安全就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
方法内的变量为线程安全
非线程安全问题存在于”实例变量”中,如果是方法内部的私有变量,则不存在”非线程安全”的问题。
Example:
HasSelfPrivateNum.java
public class HasSelfPrivateNum {
public void add(String name){
int num=0;
if(name.equals("a")){
num=100;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
num=200;
}
System.out.println("num的值为:"+num);
}
}
ThreadA.java
public class ThreadA extends Thread{
HasSelfPrivateNum hasSelfPrivateNum;
ThreadA(HasSelfPrivateNum hasSelfPrivateNum){
this.hasSelfPrivateNum=hasSelfPrivateNum;
}
@Override
public void run() {
super.run();
hasSelfPrivateNum.add("a");
}
}
ThreadB.java与ThreadA.java相类似
运行代码Run.java
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(hasSelfPrivateNum);
ThreadB threadB = new ThreadB(hasSelfPrivateNum);
threadA.start();
threadB.start();
}
}
Result:
num的值为:200
num的值为:100
可见,方法中的变量不存在非线程安全的问题,永远是线程安全的。这是方法内部的变量是私有的特性所造成的。
实例变量的非线程安全
多个线程共同访问一个对象中的实例变量,则有可能出现非线程安全问题。
Example:
只需要将HasSelfPrivateNum.java中的int num=0;提到类里面。那么此时Result:
num的值为:200
num的值为:200
加Synchronized关键字解决问题
我们现在只需要在HasSelfPrivateNum.java里的add(String name)前加synchronized关键字即可。
public class HasSelfPrivateNum {
int num=0;
public synchronized void add(String name){
if(name.equals("a")){
num=100;
System.out.println("a set over");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
num=200;
System.out.println("b set over");
}
System.out.println("num的值为:"+num);
}
}
Result:
a set over
num的值为:100
b set over
num的值为:200
结论:多个线程访问同一个对象中的同步方法是一定是线程安全的。
多个对象多个锁
修改一下Run.java代码
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum hasSelfPrivateNum1 = new HasSelfPrivateNum();
HasSelfPrivateNum hasSelfPrivateNum2 = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(hasSelfPrivateNum1);
ThreadB threadB = new ThreadB(hasSelfPrivateNum2);
threadA.start();
threadB.start();
}
}
这里生成了二个对象,Result:
a set over
b set over
num的值为:200
num的值为:100
二个线程分别访问同一个类中的二个不同实例的相同名称的同步方法,效果却是以异步的方式运行。说明了由于创建了2个对象,就会产生了2把锁
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。
synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象锁的。这也证明了一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
Example:
public class Service {
synchronized public void f1(){
System.out.println("f1");
f2();
}
synchronized public void f2(){
System.out.println("f2");
f3();
}
synchronized public void f3(){
System.out.println("f3");
}
}
通过继承一个Thread并在run方法中执行f1();打印结果:
f1
f2
f3
可重入锁也可以支持父子类继承的环境中
public class Fu{
synchronized public void fu(){
System.out.println("fu");
}
}
Zi类继承Fu
public class Zi extends Fu{
public synchronized void zi() {
System.out.println("zi");
this.fu();
}
}
运行代码:
public class Run2 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Zi zi = new Zi();
zi.zi();
}
}).start();
}
}
Result:
zi
fu
这个例子说明,当存在父子类继承关系时,子类是完全可以通过可重入锁调用父类的同步方法的。
出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
修改HasSelfPrivateNum.java
public class HasSelfPrivateNum {
int num=0;
public synchronized void add(String name){
if(name.equals("a")){
num=100;
System.out.println("a set over");
Integer.parseInt("a");//多加了这一行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
num=200;
System.out.println("b set over");
}
System.out.println("num的值为:"+num);
}
}
Result:
线程ThreadA出现异常并释放锁,线程ThreadB进入方法正常打印,实验结论是出现异常的锁被自动释放了。
同步不具有继承性
同步不可以被继承
public class Main {
synchronized public void serviceMethod(){
System.out.println("int main 下一步 sleep begin threadName="+Thread.currentThread().getName()+
"time"+System.currentTimeMillis());
try {
Thread.sleep(5000);
System.out.println("int main 下一步 sleep end threadName="+Thread.currentThread().getName()+ "time"+System.currentTimeMillis());
} catch (InterruptedException e) {
}
}
}
public class Sub extends Main{
public void serviceMethod(){
System.out.println("int Sub 下一步 sleep begin threadName="+Thread.currentThread().getName()+
"time"+System.currentTimeMillis());
try {
Thread.sleep(5000);
System.out.println("int Sub 下一步 sleep end threadName="+Thread.currentThread().getName()+ "time"+System.currentTimeMillis());
} catch (InterruptedException e) {
}
super.serviceMethod();
}
}
public class Run2 {
public static void main(String[] args) {
final Sub sub = new Sub();
new Thread(new Runnable() {
@Override
public void run() {
sub.serviceMethod();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sub.serviceMethod();
}
}).start();
}
}
Result:
输出的前二行说明了非同步,也就是没有继承同步性。
可以在子类的方法中加上synchronized此时输出结果
下一篇是介绍synchronized同步语句块