Java多线程编程2--同步锁定--synchronized同步方法、脏读、锁重入

    线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

1、方法内的变量为线程安全
  “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题(这是方法内部的变量是私有的特性造成的,所得结果也就是“线程安全”的了。

2、实例变量非线程安全
    如果多个线程共同访问1个对象中的实例变量,则可能出现”非线程安全“问题。
    如果对象仅有1个实例变量,则有可能出现覆盖的情况。

public class HasSelfPrivateNum {
    private int num = 0;

    public synchronized 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) {
            e.printStackTrace();
        }

    }
}
public class MyThread1 extends Thread {
    private HasSelfPrivateNum numRef;

    public MyThread1(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    public void run() {
        numRef.addI("a");
    }
}
public class MyThread2 extends Thread{
    private HasSelfPrivateNum numRef;

    public MyThread2 (HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    public void run() {
        numRef.addI("b");
    }
}
//内部类
class Test {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();//只创建一个对象

        MyThread1 myThread1 = new MyThread1(numRef);
        myThread1.start();

        MyThread2 myThread2 = new MyThread2(numRef);
        myThread2.start();
    }
}
b set over!
b num=200
a set over!
a num=100
当然结果也有可能是先输出b,再输出a,线程有随机性
如果把HasSelfPrivateNum类里的synchronized关键字去除,则单例模式中的实例变量为非线程安全状态,结果为
a set over!
b set over!
b num=200
a num=200

3、多个对象多个锁
   上面的HasSelfPrivateNum类,MyThread1,MyThread2不变,只修改main方法代码如下

//内部类,这个方法里创建了两个HasSelfPrivateNum对象
class Test {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();

        MyThread1 aThread1 = new MyThread1(numRef1);
        aThread1.start();

        MyThread2 bThread2 = new MyThread2(numRef2);
        bThread2.start();
    }
}
a set over!
b set over!
b num=200
a num=100
    上面示例是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的方式运行的。本示例由于创建了2个业务对象,在系统中产生出2个锁,所以运行结果是异步的.   
    从上面程序运行结果来看,虽然在Has SelfPrivateNumJava中使用了synchronized关键字,但打印的顺序却不是同步的,是交叉的。为什么是这样的结果呢?
    关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。
    但如果多个线程访间多个对象,则JVM会创建多个锁。上面的示例就是创建了2个HasSelfPrivateNumjava类的对象,所以就会产生出2个锁。
    同步的单词为synchronized,异步的单词为asynchronized。

结论:调用用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。

-----------------------------------------------------------------------------------------------------------------------------

4、脏读

    虽然在赋值时进行了同步,但在取值时有可能出现一些意想不到的意外,这种情况就是脏读(dirtyRead)。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。

public class PublicVar {
    public String username = "username";
    public String password = "password";

    public synchronized void setValue(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(3000);
            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() {  //如果加synchronized,就不会出现脏读
        System.out.println("getValue method thread name=" + Thread.currentThread().getName()
                + ",  username=" +username + ",  password="+password);
    }
}
public class MyThread1 extends Thread {
    private PublicVar publicVar;

    public MyThread1(PublicVar publicVar) {
        super();
        this.publicVar = publicVar;
    }

    public void run() {
        publicVar.setValue("username2", "password2");
    }
}
class Test {
    public static void main(String[] args) throws InterruptedException {
        PublicVar publicVar = new PublicVar();

        MyThread1 myThread1 = new MyThread1(publicVar);
        myThread1.start();

        Thread.sleep(2000);
        publicVar.getValue();
    }
}
getValue method thread name=main,  username=username2,  password=password
setValue method thread name=Thread-0,  username=username2,  password=password2
上面的结果在取值时出现脏读,username变成了username2了
    出现脏读是因为public void getValue()方法并不是同步的,所以可以在任意时候进行调用。解决办法当然就是加上同步synchronized关键字

  可见,方法setValue()和getValue()被依次执行。通过这个案例不仅要知道脏读是通过synchronized关键字解决的,还要知道如下内容:
    当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了时象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。
    当A线程调用anyObject对象加入synchronized关健字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。这时A线程已经执行了一个完整的任务,也就是说usernarne和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。

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

5、synchronized锁重入
    关键字synchronized拥有锁重入的功能。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
    “可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
    可重入锁也支持在父子类继承的环境中。

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

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

    public synchronized void service3() {
        System.out.println("service3");
    }
}
public class MyThread extends Thread {
    public void run() {
        Service service = new Service();
        service.service1();
    }
}
class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
service1
service2
service3

----------------------------------------------------------------------------------------------------
6、出现异常,锁自动释放
    当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

public class Service {
   public synchronized 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 beginTime=" + System.currentTimeMillis());
                   Integer.parseInt("a");
               } else {
                   System.out.println("Thread B run Time=" + System.currentTimeMillis());
               }
           }
       }
   }
}
public class MyThread1 extends Thread {
    private Service service;

    public MyThread1(Service service) {
        super();
        this.service = service;
    }

    public void run() {
        service.testMethod();
    }
}
public class MyThread2 extends Thread{
    private Service service;

    public MyThread2(Service service) {
        super();
        this.service = service;
    }

    public void run() {
        service.testMethod();
    }
}
//内部类
class Test {
    public static void main(String[] args) {
        try {
            Service service = new Service();
            MyThread1 aThread = new MyThread1(service);
            aThread.setName("a");
            aThread.start();

            Thread.sleep(1000);
            MyThread2 bThread2 = new MyThread2(service);
            bThread2.setName("b");
            bThread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Thread B run Time=1462333804804
Thread B run Time=1462333804804
Thread B run Time=1462333804804
ThreadName=a,  run beginTime=1462333804804
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    线程a出现异常并释放锁,线程b进入方法正常打印,实验的结论就 出现异常的锁被自动释放。

7、同步不具有继承性

你可能感兴趣的:(多线程,synchronized,同步,锁,脏读)