对象及变量的并发访问

synchronized同步方法

非线程安全的问题主要存在于实例变量中,如果是方法内部的私有变量,则不存在非线程安全问题。

1. 实例变量非线程安全

如果多个线程共同访问1个对象中的实例变量则有可能出现非线程安全的问题。
demo如下 :

public class HasSelfPrivateNum {	
    private int num = 0;	
    public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            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 Run {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
        ThreadA athread = new ThreadA(numRef);
        athread.start();
        ThreadB bthread = new ThreadB(numRef);
        bthread.start();
    }
}

运行效果:
a set over!
b set over!
b num=200
a num=200

解决方法:只需要在addI() 方法前面加synchronized即可。通过用关键字synchronized声
明的方法一定是排队运行的,只有**共享资源的读写访问**才需要同步化。如果不是,
则根本没有同步的必要。改过之后的运行效果如下:
a set over!
a num=100
b set over!
b num=200

结论:当两个线程访问同一个对象中的同步方法时一定是线程安全的。

2. 多个对象多个锁

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            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 Run {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
        ThreadA athread = new ThreadA(numRef1);
        athread.start();
        ThreadB bthread = new ThreadB(numRef2);
        bthread.start();
    }
}
运行效果:
a set over!
b set over!
b num=200
a num=100

结论:上述两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的方式进行的。

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁,所以上面的示例中,哪个线程先执行带synchhronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个对象访问的是同一个对象。
如果多个线程访问多个对象,则会创建多个锁。

3. synchronized方法与锁对象

public class MyObject {
    synchronized public void methodA() {
        try {
            System.out.println("begin methodA threadName="
                    + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end endTime=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void methodB() {
        try {
            System.out.println("begin methodB threadName="
                    + Thread.currentThread().getName() + " begin time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private MyObject object;
    public ThreadA(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}

public class ThreadB extends Thread {
    private MyObject object;
    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

public class Run {
    public static void main(String[] args) {
        MyObject object = new MyObject();
        ThreadA a = new ThreadA(object);
        a.setName("A");
        ThreadB b = new ThreadB(object);
        b.setName("B");
        a.start();
        b.start();
    }
}

运行效果
begin methodA threadName=A
begin methodB threadName=B begin time=1521539509210
end endTime=1521539514210
end

给methodB()方法加上synchronized类型的方法。
运行效果如下:
begin methodA threadName=A
end endTime=1521539982244
begin methodB threadName=B begin time=1521539982244
end

结论:

  1. A线程先持有object对象 的Lock锁,B线程可以以异步 的方式调用 object对象 中的非synchronized类型的方法。
  2. A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

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(5000);
            this.password = password;
            System.out.println("setValue method thread name="
                    + Thread.currentThread().getName() + " username="
                    + username + " password=" + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     public void getValue() {
        System.out.println("getValue method thread name="
                + Thread.currentThread().getName() + " username=" + username
                + " password=" + password);
    }
}

public class ThreadA extends Thread {
    private PublicVar publicVar;
    public ThreadA(PublicVar publicVar) {
        super();
        this.publicVar = publicVar;
    }
    @Override
    public void run() {
        super.run();
        publicVar.setValue("B", "BB");
    }
}

public class Test {
    public static void main(String[] args) {
        try {
            PublicVar publicVarRef = new PublicVar();
            ThreadA thread = new ThreadA(publicVarRef);
            thread.start();
            Thread.sleep(200);// ��ӡ����ܴ�ֵ��СӰ��
            publicVarRef.getValue();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
运行结果:
getValue method thread name=main username=B password=AA
setValue method thread name=Thread-0 username=B password=BB

出现脏读的原因是getValue()方法并不是同步的。所以可以在任意时刻调用。
解决办法是加上在getValue()加上synchronized关键字。加上后的运行效果如下:

setValue method thread name=Thread-0 username=B password=BB
getValue method thread name=main username=B password=BB

脏读一定会出现在操作实例变量的情况下,这就是不同线程争抢实例变量的结果。

5. synchronized锁重入

在一个synchronized方法/块内部调用本部类的其他synchronized方法/块时,是永远可以得到锁的。

public class MyThread extends Thread {
    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}

public class Service {
    synchronized public void service1() {
        System.out.println("service1");
        service2();
    }
    synchronized public void service2() {
        System.out.println("service2");
        service3();
    }
    synchronized public void service3() {
        System.out.println("service3");
    }
}

public class Run {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

运行效果:
service1
service2
service3

可重入锁的概念是:自己可以再次获取自己的内部锁,比如有一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话就会造成死锁 。

可重入锁也支持在父子类继承的环境中:

public class MyThread extends Thread {
    @Override
    public void run() {
        Sub sub = new Sub();
        sub.operateISubMethod();
    }
}

public class Main {
    public int i = 10;
    synchronized public void operateIMainMethod() {
        try {
            i--;
            System.out.println("main print i=" + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class Sub extends Main {
    synchronized public void operateISubMethod() {
        try {
            while (i > 0) {
                i--;
                System.out.println("sub print i=" + i);
                Thread.sleep(100);
                this.operateIMainMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

运行效果如下:
sub print i=9
main print i=8
sub print i=7
main print i=6
sub print i=5
main print i=4
sub print i=3
main print i=2
sub print i=1
main print i=0

结论:当存在父子类继承关系时,子类是完全可以可重入锁调用父类的同步方法的。

6. 出现异常,锁自动释放

public class Service {
    synchronized public void testMethod() {
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + " run beginTime=" + System.currentTimeMillis());
            int i = 1;
            while (i == 1) {
                if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
                    System.out.println("ThreadName="
                            + Thread.currentThread().getName()
                            + " run   exceptionTime="
                            + System.currentTimeMillis());
                    Integer.parseInt("a");
                }
            }
        } else {
            System.out.println("Thread B run Time="
                    + System.currentTimeMillis());
        }
    }
}

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        try {
            Service service = new Service();
            ThreadA a = new ThreadA(service);
            a.setName("a");
            a.start();
            Thread.sleep(500);
            ThreadB b = new ThreadB(service);
            b.setName("b");
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行效果:
ThreadName=a run beginTime=1521613028384
ThreadName=a run   exceptionTime=1521613033721
Thread B run Time=1521613033722
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.parseInt(Integer.java:615)
    at service.Service.testMethod(Service.java:15)
    at extthread.ThreadA.run(ThreadA.java:13)

线程a出现异常,线程b进入方法正常打印。结论:出现异步,锁自动释放。

7. 同步不具有继承性

public class Main {
    synchronized public void serviceMethod() {
        try {
            System.out.println("int main 下一步 sleep begin threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int main 下一步 sleep   end threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Sub extends Main {
    @Override
    public void serviceMethod() {
        try {
            System.out.println("int sub 下一步 sleep begin threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int sub 下一步 sleep   end threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            super.serviceMethod();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class MyThreadA extends Thread {
    private Sub sub;
    public MyThreadA(Sub sub) {
        super();
        this.sub = sub;
    }
    @Override
    public void run() {
        sub.serviceMethod();
    }
}
public class MyThreadB extends Thread {
    private Sub sub;
    public MyThreadB(Sub sub) {
        super();
        this.sub = sub;
    }
    @Override
    public void run() {
        sub.serviceMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        Sub subRef = new Sub();
        MyThreadA a = new MyThreadA(subRef);
        a.setName("A");
        a.start();
        MyThreadB b = new MyThreadB(subRef);
        b.setName("B");
        b.start();
    }
}

运行效果:
int sub 下一步 sleep begin threadName=A time=1521614488673
int sub 下一步 sleep begin threadName=B time=1521614488684
int sub 下一步 sleep   end threadName=A time=1521614493673
int main 下一步 sleep begin threadName=A time=1521614493673
int sub 下一步 sleep   end threadName=B time=1521614493684
int main 下一步 sleep   end threadName=A time=1521614498674
int main 下一步 sleep begin threadName=B time=1521614498674
int main 下一步 sleep   end threadName=B time=1521614503674

在子类Sub serviceMethod方法加上synchronized后运行效果如下:
int sub 下一步 sleep begin threadName=A time=1521614592637
int sub 下一步 sleep   end threadName=A time=1521614597637
int main 下一步 sleep begin threadName=A time=1521614597637
int main 下一步 sleep   end threadName=A time=1521614602638
int sub 下一步 sleep begin threadName=B time=1521614602638
int sub 下一步 sleep   end threadName=B time=1521614607638
int main 下一步 sleep begin threadName=B time=1521614607638
int main 下一步 sleep   end threadName=B time=1521614612638

总结

本节知识点:

  1. 非线程安全的问题主要存在于实例变量中,如果是方法内部的私有变量,则不存在非线程安全问题
  2. 两个线程访问同一个对象中的同步方法时一定是线程安全的
  3. synchronized关键字是对象锁,多个线程,多个对象会创建多个锁。
  4. 对象内synchronized方法和非synchronized方法的调用情况: 当有对象锁的时候,同步调用synchronized方法,异步调用非synchronized方法
  5. 脏读出现的原因是方法不同步造成的。脏读一定会出现在操作实例变量的情况下,这就是不同线程争抢实例变量的结果。
  6. 可重入锁的两种情况
    1. 自己可以获取自己的内部锁
    2. 也支持出现在父子类继承的关系中,子类可以通过可重入锁调用父类的同步方法
  7. 当出现异常时,锁自动释放
  8. 同步不能继承:当父类在synchronized service()方法时,子类有覆盖service()方法时,子类方法并不能同步,必须要在子类加上synchronized方法才能同步。