2.1 synchronized 同步方法
“非线程安全”会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是读到的数据其实是被更改过的。
“线程安全”就是获取的实例变量的值是经过同步处理的,不会出现脏读的现象。
2.1.1 方法内的变量是线程安全的
“非线程安全”的问题存在于“实例变量”中,如果是方法内部的私有变量,就是“线程安全”的。
测试代码:
public class HasSelfPrivateNum {
public void addI(String username){
try{
int num =0;
if(username.equals("a")){
num =100;
System.out.println("a set over");
Thread.sleep(1000);
}else{
num =200;
System.out.println("b set over");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
}
@Override
public void run(){
super.run();
numRef.addI("a");
}
}
public class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
}
@Override
public void run(){
super.run();
numRef.addI("b");
}
}
public class ThreadSafeDemo {
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA thread_a = new ThreadA(numRef);
thread_a.start();
ThreadB thread_b = new ThreadB(numRef);
thread_b.start();
}
}
运行结果:
a set over
b set over
方法中的变量不存在“非线程安全”的问题。
2.1.2 实例变量非线程安全
多线程访问的对象中如果有多个实例变量,运行的结果可能出现交叉的情况。
沿用2.1.1中代码,仅修改HasSelfPrivateNum.java文件,将intnum =0;移到addI()方法的外面。
public class HasSelfPrivateNum {
int num =0;
public void addI(String username){
try{
if(username.equals("a")){
num =100;
System.out.println("a set over");
Thread.sleep(500);
}else{
num =200;
System.out.println("b set over");
}
System.out.println(username+",,,num="+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
a set over
b set over
b,,,num=200
a,,,num=200
两个线程同时操作对象中的实例变量,出现了“非线程安全”的问题。
避免这个问题的做法是在addI前加关键字synchronized。
public class HasSelfPrivateNum {
int num =0;
synchronized public void addI(String username){
try{
if(username.equals("a")){
num =100;
System.out.println("a set over");
Thread.sleep(500);
}else{
num =200;
System.out.println("b set over");
}
System.out.println(username+",,,num="+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行结果:
a set over
a,,,num=100
b set over
b,,,num=200
由于是同步方法,所以是顺序访问。
2.1.3 多个对象多个锁
沿用2.1.2的代码,仅修改ThreadSafeDemo.java,让两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,也就是为两个线程绑定了不同的实例对象。
public class ThreadSafeDemo {
public static void main(String[] args) {
HasSelfPrivateNum numRefA = new HasSelfPrivateNum();
HasSelfPrivateNum numRefB = new HasSelfPrivateNum();
ThreadA thread_a = new ThreadA(numRefA);
thread_a.start();
ThreadB thread_b = new ThreadB(numRefB);
thread_b.start();
}
}
运行结果:
a set over
b set over
b,,,num=200
a,,,num=100
虽然是同一个同步的方法,却是异步的方式运行的。因为synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁,所以,在一个线程先执行synchronized方法时,这个线程就持有了该方法所属对象的锁,那么其他要访问这个方法的线程只能等待,调用synchronized方法一定是排队运行的(只要是同一个锁对象下synchronized修饰的方法,而不论是不是同一个synchronized方法),但是这个前提是多个线程访问的同一个对象。
当访问的是多个对象时,就产生了多个锁,同步操作就变成了异步执行了。
2.1.4 前面的同步是在赋值时进行的,读取时也可能出现意外,这就是“脏读”。
测试代码:
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password){
try{
this.username = username;
Thread.sleep(1000);
this.password = password;
System.out.println("setValue,thread name="+Thread.currentThread().getName()+
",username="+username+",password="+password);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void getValue(){
System.out.println("getValue,thread name="+Thread.currentThread().getName()+
",username="+username+",password="+password);
}
}
public class ThreadA extends Thread {
private PublicVar numRef;
public ThreadA(PublicVar numRef){
super();
this.numRef = numRef;
}
@Override
public void run(){
super.run();
numRef.setValue("B","BB");
}
}
public class ThreadSafeDemo {
public static void main(String[] args) throws InterruptedException {
PublicVar numRef = new PublicVar();
ThreadA thread_a = new ThreadA(numRef);
thread_a.start();
Thread.sleep(200);
numRef.getValue();
}
}
运行结果:
getValue,threadname=main,username=B,password=AA
setValue,threadname=Thread-0,username=B,password=BB
第一行输出出现“脏读”,原因是getValue方法没有同步,需要加上synchronized关键字。
运行结果:
setValue,threadname=Thread-0,username=B,password=BB
getValue,threadname=main,username=B,password=BB
2.1.5 synchronized锁重入
关键字synchronized拥有锁重入的功能,就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。在一个synchronized方法内部调用本类的其他synchronized方法时,是永远可以得到锁的。
测试代码:
public class ReEntrySynchronized {
synchronized public void service_1(){
System.out.println("service_1");
service_2();
System.out.println("service_1--end");
}
synchronized public void service_2(){
System.out.println("service_2");
service_3();
}
synchronized public void service_3(){
System.out.println("service_3");
}
}
public class ThreadA extends Thread {
@Override
public void run(){
ReEntrySynchronized rs = new ReEntrySynchronized();
rs.service_1();
}
}
public class ThreadSafeDemo {
public static void main(String[] args) throws InterruptedException {
ThreadA thread_a = new ThreadA();
thread_a.start();
}
}
运行结果:
service_1
service_2
service_3
service_1—end
“可重入锁”:自己可以再次获取自己的内部锁,如一个线程获取了某个对象的锁,在这个对象锁还没释放时,当这个线程再次获取这个对象的锁时还是可以获取的,如果不可锁重入的话,就会导致死锁。
2.1.6 可重入锁支持在父子类的继承环境中,也就是说子类可以通过“可重入锁”调用父类的同步方法。
测试代码:
public class ReEntrySynchronized {
public int i= 10;
synchronized public void service_1(){
i--;
System.out.println("service_1,i="+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ReEntryLock extends ReEntrySynchronized {
synchronized public void service_sub(){
while(i > 0){
i--;
System.out.println("service_1,i="+i);
try {
Thread.sleep(100);
service_1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class ThreadA extends Thread {
@Override
public void run(){
ReEntryLock rs = new ReEntryLock();
rs.service_sub();
}
}
public class ThreadSafeDemo {
public static void main(String[] args) throws InterruptedException {
ThreadA thread_a = new ThreadA();
thread_a.start();
}
}
运行结果:
service_1,i=9
service_1,i=8
service_1,i=7
service_1,i=6
service_1,i=5
service_1,i=4
service_1,i=3
service_1,i=2
service_1,i=1
service_1,i=0
虽然“可重入锁”可以存在父子继承关系中,但是同步是不可以继承的,就是说父类有一个synchronized修饰的同步方法,子类覆写的这个方法不会自动继承同步性质,想要子类的这个方法也具有同步性质,需要添加synchronized关键字。
2.1.7 出现异常,锁自动释放,当一个线程执行的代码出现异常时,其所持有的锁会自动释放。