【java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结

来源:互联网 发布:防蓝光近视眼镜 知乎 编辑:程序博客网 时间:2024/05/17 06:48

synchronized同步方法

方法内的变量为线程安全

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

实例变量非线程安全

package service;public class HasSelfPrivateNum {     private int num = 0;//   synchronized public void addI(String username) {     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();          }     }}package extthread;import service.HasSelfPrivateNum;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");     }}package extthread;import service.HasSelfPrivateNum;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");     }}package test;import service.HasSelfPrivateNum;import extthread.ThreadA;import extthread.ThreadB;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=200a num=200加入synchronized关键字同步后,输出结果:a set over!a num=100b set over!b num=200

用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉的情况
如果对象仅有一个实例变量,则有可能出现覆盖的情况(如上面例子)
需在public void addI(String username)前加关键字synchronized实现同步
在两个线程访问同一个对象中的同步方法时一定是线程安全的(原文

多个对象多个锁

其他代码与上例相同(+synchronized关键字后的)package test;import service.HasSelfPrivateNum;import extthread.ThreadA;import extthread.ThreadB;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=200a num=100

两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的方式运行的。
原因:此处关键字synchronized取得的锁是对象锁。在多个线程访问同一个对象时,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁LOCK,则其他线程只能呈等待状态。 而这里是多个线程访问多个对象,则JVM会创建多个锁,每个线程所持有的锁互不相同,所以此处是异步的方式运行。
同步:synchronized 异步:asynchronized

synchronized方法与锁对象

调用关键字synchronized声明的方法一定是排队运行的(这里是建立在资源共享的情况下,只有共享资源的读写访问才需要同步化,但如果不是共享资源,就没同步的必要)
1.A线程先持有Object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
2.A线程先持有Object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

脏读

发生脏读的情况是:在读取实例变量时,此值已经被其他线程更改过了

package entity;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() {     public void getValue() {//未加synchronized关键字,造成脏读          System.out.println("getValue method thread name="                   + Thread.currentThread().getName() + " username=" + username                   + " password=" + password);     }}package extthread;import entity.PublicVar;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");     }}package test;import entity.PublicVar;import extthread.ThreadA;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()方法前加入synchronized,输出结果:getValue method thread name=main username=B password=AAsetValue method thread name=Thread-0 username=B password=BB加入关键字后,输出结果:setValue method thread name=Thread-0 username=B password=BBgetValue method thread name=main username=B password=BB

此处出现脏读是因为 getValue()方法并不是同步的,所以可以在任意时候进行调用
当线程thread执行完publicVar.setValue(“B”,”BB”)后,释放对象锁后(证明当前已给两个实例变量同时赋完值),main线程才能去获取这个对象锁,并执行getValue()
脏读一定会出现操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果(原文)

synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。
重入锁:自己可以再次获取自己的内部锁。(未释放锁之前)
可重入锁也支持父子类继承中:当存在父子类继承关系时,子类能完全可以通过”可重入锁”调用父类的同步方法的

出现异常,锁自动释放

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

同步不具有继承性

同步不可以继承,也就是当父类里的方法加上了关键字synchronized成为同步方法时,如果继承父类的子类重写方法后,如果不带有关键字synchronized则不具有同步。

synchronized同步语句块

synchronized方法的弊端

package mytask;import commonutils.CommonUtils;public class Task {     private String getData1;     private String getData2;     public synchronized void doLongTimeTask() {          try {              System.out.println("begin task");              Thread.sleep(3000);              getData1 = "长时间处理任务后从远程返回的值1 threadName="                        + Thread.currentThread().getName();              getData2 = "长时间处理任务后从远程返回的值2 threadName="                        + Thread.currentThread().getName();              System.out.println(getData1);              System.out.println(getData2);              System.out.println("end task");          } catch (InterruptedException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }     }}

每个线程去调用这个方法时,都需要等待3000毫秒,而我们可以发现这段代码里,会发生“非线程安全”问题的代码是在对getData1,getData2 赋值处,而前面的等待3000毫秒对其无实质性影响,所以得通过使用synchronized同步代码块进行优化。

synchronized同步代码块的使用

public class ObjectService {     public void serviceMethod() {          try {              synchronized (this) {                   System.out.println("begin time=" + System.currentTimeMillis());                   Thread.sleep(2000);                   System.out.println("end    end=" + System.currentTimeMillis());              }          } catch (InterruptedException e) {              e.printStackTrace();          }     }}

用同步代码块解决同步方法的弊端

package mytask;public class Task {     private String getData1;     private String getData2;     public void doLongTimeTask() {          try {              System.out.println("begin task");              Thread.sleep(3000);              String privateGetData1 = "长时间处理任务后从远程返回的值1 threadName="                        + Thread.currentThread().getName();              String privateGetData2 = "长时间处理任务后从远程返回的值2 threadName="                        + Thread.currentThread().getName();              synchronized (this) {                   getData1 = privateGetData1;                   getData2 = privateGetData2;              }              System.out.println(getData1);              System.out.println(getData2);              System.out.println("end task");          } catch (InterruptedException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }     }}

当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块

一半异步,一半同步

不在synchronized块中的就是异步执行,在synchronized块中就是同步执行。

synchronized代码块间的同步性

当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,说明synchronized使用的“对象监视器”是一个

同步synchronized(this)代码块是锁定当前对象的

和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

synchronized public void otherMethod(){    ......}public void doLongTimeTask(){    synchronized(this){        ........    }}通过以上代码形式进行验证

将任意对象作为对象监视器

synchronized同步方法或synchronized(this)同步代码块有2种作用:
1.对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
2.同一时间只有一个线程可以执行synchronized同步方法(或synchronized(this))同步代码块中的代码。

Java还支持对“任意对象”作为“对象监视器”来实现同步的功能,使用格式:synchronized(非this 对象x)

package service;public class Service {     private String usernameParam;     private String passwordParam;     private String anyString = new String();     public void setUsernamePassword(String username, String password) {          try {              synchronized (anyString) {                   System.out.println("线程名称为:" + Thread.currentThread().getName()                             + "在" + System.currentTimeMillis() + "进入同步块");                   usernameParam = username;                   Thread.sleep(3000);                   passwordParam = password;                   System.out.println("线程名称为:" + Thread.currentThread().getName()                             + "在" + System.currentTimeMillis() + "离开同步块");              }          } catch (InterruptedException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }     }}

锁非this对象具有一定的优点:如果在一个类有很多synchronized方法,这时虽然能实现同步,但会收到阻塞,影响效率。但如果使用同步代码块锁非this对象,则是异步的,不与其他锁this同步方法争抢this锁,大大提高运行效率

同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的,这样极易造成“脏读”问题!(如下代码)

package mylist;import java.util.ArrayList;import java.util.List;public class MyOneList {    private List<String> list = new ArrayList<String>();    synchronized public void add(String data) {        list.add(data);    };    synchronized public int getSize() {        return list.size();    };}package mythread;import mylist.MyOneList;import service.MyService;public class MyThread1 extends Thread {    private MyOneList list;    public MyThread1(MyOneList list) {        super();        this.list = list;    }    @Override    public void run() {        MyService msRef = new MyService();        msRef.addServiceMethod(list, "A");    }}package mythread;import service.MyService;import mylist.MyOneList;public class MyThread2 extends Thread {    private MyOneList list;    public MyThread2(MyOneList list) {        super();        this.list = list;    }    @Override    public void run() {        MyService msRef = new MyService();        msRef.addServiceMethod(list, "B");    }}package service;import mylist.MyOneList;public class MyService {     public MyOneList addServiceMethod(MyOneList list, String data) {          try {//            synchronized (list) {                   if (list.getSize() < 1) {    //此处未加同步代码块时                        Thread.sleep(2000);                        list.add(data);                   }//            }          } catch (InterruptedException e) {              e.printStackTrace();          }          return list;     }}package test;import mylist.MyOneList;import mythread.MyThread1;import mythread.MyThread2;public class Run {    public static void main(String[] args) throws InterruptedException {        MyOneList list = new MyOneList();        MyThread1 thread1 = new MyThread1(list);        thread1.setName("A");        thread1.start();        MyThread2 thread2 = new MyThread2(list);        thread2.setName("B");        thread2.start();        Thread.sleep(6000);          System.out.println("listSize=" + list.getSize());    }}未加入同步代码块时,脏读问题,输出结果:listSize=2//此处脏读发生的原因:两个线程以异步的方式返回list参数的size()大小。加入后,输出结果:listSize=1

细化3个结论

1.当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
2.当其他线程执行x对象中synchronized同步方法时呈同步效果。
3.当其他线程执行x对象方法里面的synchronized(this)代码块时也呈同步效果
注意:如果其他线程调用不加synchronized关键字的方法时,还是异步调用。

静态同步synchronized方法与synchronized(Class)代码块

关键字synchronized也可以应用static静态方法上,即对当前的*.java文件对应的Class类进行持锁。

synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

package extthread;import service.Service;public class ThreadA extends Thread {     private Service service;     public ThreadA(Service service) {          super();          this.service = service;     }     @Override     public void run() {          service.printA();     }}package extthread;import service.Service;public class ThreadB extends Thread {     private Service service;     public ThreadB(Service service) {          super();          this.service = service;     }     @Override     public void run() {          service.printB();     }}package extthread;import service.Service;public class ThreadC extends Thread {     private Service service;     public ThreadC(Service service) {          super();          this.service = service;     }     @Override     public void run() {          service.printC();     }}package service;public class Service {     synchronized public static void printA() {          try {              System.out.println("线程名称为:" + Thread.currentThread().getName()                        + "在" + System.currentTimeMillis() + "进入printA");              Thread.sleep(3000);              System.out.println("线程名称为:" + Thread.currentThread().getName()                        + "在" + System.currentTimeMillis() + "离开printA");          } catch (InterruptedException e) {              e.printStackTrace();          }     }     synchronized public static void printB() {          System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"                   + System.currentTimeMillis() + "进入printB");          System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"                   + System.currentTimeMillis() + "离开printB");     }     synchronized public void printC() {          System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"                   + System.currentTimeMillis() + "进入printC");          System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"                   + System.currentTimeMillis() + "离开printC");     }}package test;import service.Service;import extthread.ThreadA;import extthread.ThreadB;import extthread.ThreadC;public class Run {    public static void main(String[] args) {        Service service = new Service();        ThreadA a = new ThreadA(service);        a.setName("A");        a.start();        ThreadB b = new ThreadB(service);        b.setName("B");        b.start();        ThreadC c = new ThreadC(service);        c.setName("C");        c.start();    }}输出结果:线程名称为:A在1510896952762进入printA线程名称为:C在1510896952762进入printC线程名称为:C在1510896952762离开printC线程名称为:A在1510896955768离开printA线程名称为:B在1510896955768进入printB线程名称为:B在1510896955768离开printB

异步原因是持有不同的锁,一个是对象锁,另外一个是Class锁,而Class锁可以对类的所有对象实例起作用。

package service;public class Service {     synchronized public static void printA() {          try {              System.out.println("线程名称为:" + Thread.currentThread().getName()                        + "在" + System.currentTimeMillis() + "进入printA");              Thread.sleep(3000);              System.out.println("线程名称为:" + Thread.currentThread().getName()                        + "在" + System.currentTimeMillis() + "离开printA");          } catch (InterruptedException e) {              e.printStackTrace();          }     }     synchronized public static void printB() {          System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"                   + System.currentTimeMillis() + "进入printB");          System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"                   + System.currentTimeMillis() + "离开printB");     }}    public static void main(String[] args) {          Service service1 = new Service(); //类的其中一个对象          Service service2 = new Service();//类的另外一个对象          ThreadA a = new ThreadA(service1);          a.setName("A");          a.start();          ThreadB b = new ThreadB(service2);          b.setName("B");          b.start();     }(部分代码省略)输出结果:线程名称为:A在1510897139446进入printA线程名称为:A在1510897142459离开printA线程名称为:B在1510897142459进入printB线程名称为:B在1510897142459离开printB

同步synchronized(class)代码块的作用其实和synchronize static 方法的作用一样。 //synchronized(Service.Class){}

阅读全文
0 0