java多线程之synchronized和volatile关键字

来源:互联网 发布:阿里云香港机房 编辑:程序博客网 时间:2024/05/18 18:55

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

未加同步锁

这里写图片描述

加了同步锁

这里写图片描述

多个对象监视器多个锁

多个线程访问多个对象,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();    }}

这里写图片描述

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秒钟,效率提升很高。

这里写图片描述

一半同步一半异步

把上面的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();        }    }}

互相等待,导致线程假死

这里写图片描述

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服务器模式中运行

这里写图片描述

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服务器模式中运行

这里写图片描述

注意事项

**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静态方法上是给对象加锁


此为读书笔记,还望各位多多指导

0 0