synchronized同步方法

脏读

在多个线程对同一个对象中的实例变量进行并发访问的时候,取到的数据可能是被更改过的,称之为“脏读”,这就是非线程安全的。解决的方法为synchronized关键字进行同步,使之操作变成同步而非异步。

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();
        }
    }

    synchronized 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();
        }

    }
}

未加同步锁

java如何解决脏读 java怎么防止数据脏读_synchroniz

加了同步锁

java如何解决脏读 java怎么防止数据脏读_synchroniz_02

多个对象监视器多个锁

多个线程访问多个对象,JVM会创建多个锁

public class Test {
    public static void main(String[] args) {
            TestRunable public1 = new TestRunable();
            TestRunable public2 = new TestRunable();
            ThreadA athread = new ThreadA(public1);
            athread.start();
            ThreadB bthread = new ThreadA(public2);
            bthread.start();
    }
}

上面示例是两个线程访问同一个类的两个不同实例对象,效果是异步的方式运行。即时加了同步锁也是异步执行,因为创建了两个对象,将会产生两把锁。

锁重入

当一个线程得到一个对象锁后,再次请求此对象锁是可以再次得到该对象锁的。就是在自己已经获得该对象锁的前提下,还可以再次获取自己的锁。可重入锁也支持在父子类继承的环境中。

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();
    }
}

java如何解决脏读 java怎么防止数据脏读_synchroniz_03

synchronized同步语句块

同步语句块的好处

public class Task {

    private String getData1;

    /**
     * synchronized public void doLongTimeTask(){}
     * 如果同步锁在方法上
     * A线程执行的时候会锁住3秒钟,然B线程再执行,效率太低
     * 所以同步代码块是效率最高的方法
     */
     public void doLongTimeTask() {
        try {
            System.out.println("begin task");
            Thread.sleep(3000);

            synchronized (this) {
                getData1 = Thread.currentThread().getName();
            }

            System.out.println(getData1);
            System.out.println("end task");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class MyThread1 extends Thread {

    private Task task;

    public MyThread1(Task task) {
        super();
        this.task = task;
    }

    @Override
    public void run() {
        super.run();
        task.doLongTimeTask();
    }

}
public class MyThread2 extends Thread {

    private Task task;

    public MyThread2(Task task) {
        super();
        this.task = task;
    }

    @Override
    public void run() {
        super.run();
        task.doLongTimeTask();
    }

}
public class Run {

    public static void main(String[] args) {
        Task task = new Task();
        MyThread1 thread1 = new MyThread1(task);
        thread1.start();
        MyThread2 thread2 = new MyThread2(task);
        thread2.start();
    }
}

如果同步方法的话,程序跑完大概在6秒钟左右,A线程B线程各用时3秒钟
如果同步语句块的话,让耗时的操作异步执行,那么程序跑完大概也就3秒钟,效率提升很高。

java如何解决脏读 java怎么防止数据脏读_java_04

一半同步一半异步

把上面的Task类修改如

在synchronized 中就是同步,不在synchronized 中就是异步,可以运行看下结果

public class Task {
     public void doLongTimeTask() {
         for (int i = 0; i < 100; i++) {
             System.out.println("nosynchronized threadName="
                     + Thread.currentThread().getName() + " i=" + (i + 1));
         }
         System.out.println("");
         synchronized (this) {
             for (int i = 0; i < 100; i++) {
                 System.out.println("synchronized threadName="
                         + Thread.currentThread().getName() + " i=" + (i + 1));
             }
         }
    }
}

死锁

不同的线程都在等待不可能释放的锁,从而导致所有任务都无法完成,造成线程的假死。

public class DealThread implements Runnable {

    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setFlag(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        if (username.equals("a")) {
            synchronized (lock1) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("按lock1->lock2代码顺序执行了");
                }
            }
        }
        if (username.equals("b")) {
            synchronized (lock2) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("按lock2->lock1代码顺序执行了");
                }
            }
        }
    }

}
public class Run {
    public static void main(String[] args) {
        try {
            DealThread t1 = new DealThread();
            t1.setFlag("a");

            Thread thread1 = new Thread(t1);
            thread1.start();

            Thread.sleep(100);

            t1.setFlag("b");
            Thread thread2 = new Thread(t1);
            thread2.start();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

互相等待,导致线程假死

java如何解决脏读 java怎么防止数据脏读_多线程_05

volatile关键字

volatile关键字提示线程每次从共享内存中读取变量,而不是私有内存。
适用场合是在多个线程中可以感知实例变量被更改了。

在JVM被设置成-server服务器模式运行时,为了线程运行的效率,线程一直在私有堆栈中。其他线程更改的实例变量值却会更新在公共堆栈中。

解决异步死循环

public class RunThread extends Thread {

    //volatile 关键字 isRunning变量将从公共堆栈中取值
    volatile private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void run() {
        System.out.println("进入run了");
        while (isRunning == true) {
        }
        System.out.println("线程被停止了!");
    }

}
public class Run {
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(1000);
            thread.setRunning(false);
            System.out.println("已经赋值为false");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

在-server服务器模式中运行

java如何解决脏读 java怎么防止数据脏读_java如何解决脏读_06

volatile的非原子性(synchronized的代码块有volatile同步的功能)

volatile关键字增加了多线程之间实例变量的可见性,但是不能保证同步性

public class Service {

    private boolean isContinueRun = true;

    public void runMethod() {
        String anyString = new String();
        while (isContinueRun == true) {
        //synchronized 可以使多个线程访问统一资源具有同步性
        //可以同步 工作内存中的私有变量和公共内存中的变量
            synchronized (anyString) {
            }
        }
        System.out.println("停下来了!");
    }

    public void stopMethod() {
        isContinueRun = false;
    }
}
public class ThreadA extends Thread {
    private Service service;

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

    @Override
    public void run() {
        service.runMethod();
    }
}
public class ThreadB extends Thread {
    private Service service;

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

    @Override
    public void run() {
        service.stopMethod();
    }

}
public class Run {

    public static void main(String[] args) {
        try {
            Service service = new Service();

            ThreadA a = new ThreadA(service);
            a.start();

            Thread.sleep(1000);

            ThreadB b = new ThreadB(service);
            b.start();

            System.out.println("已经发起停止的命令了!");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

在-server服务器模式中运行

java如何解决脏读 java怎么防止数据脏读_java_07

注意事项

**synchronized关键字锁住变量的时候最好不要用String类型的,要考虑字符串常量池的问题

例如:

String str="aaa";
String str1="aaa";
//java中的字符串常量池会导致同步失效
//System.out.print(str==str1);//true
//所以最好用 Object o=new Object();
synchronized(str){
    //TODO
}

synchronized关键字加到static静态方法上是给Class类加锁

Class锁可以对类的所有对象实例起作用。

synchronized关键字加到非static静态方法上是给对象加锁