【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){}
- 【java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结
- 【java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结
- 《Java多线程编程核心技术》(二)对象及变量的并发访问
- Java多线程编程核心技术---对象及变量的并发访问(一)
- Java多线程编程核心技术---对象及变量的并发访问(二)
- 多线程编程核心技术读书笔记(二):对象及变量的并发访问(synchronized关键字)
- 多线程编程核心技术读书笔记(二):对象及变量的并发访问(volatile关键字)
- Java多线程核心技术(二):对象及变量的并发访问访问
- java多线程编程2--对象及变量的并发访问
- java多线程编程3--对象及变量的并发访问
- 多线程编程学习二(对象及变量的并发访问)
- 多线程编程学习二(对象及变量的并发访问)
- 多线程编程学习二(对象及变量的并发访问)
- Java多线程--对象及变量的并发访问
- Java多线程之对象及变量的并发访问
- Java多线程编程——对象及变量的并发访问 02
- (转载)多线程编程学习二(对象及变量的并发访问)
- 《java多线程编程核心技术》之并发访问
- c语言实现菲波那切数列对大数求余
- Techsoft.ASTRA.Pro.v15.0.Win32_64 1DVD(结构分析)
- 自媒体书单:从入门、写作、策划到运营自媒体必须收藏的系列好书
- 1)Spring基础
- 关于软件开发四个大类型的介绍
- 【java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结
- 机器学习实战随笔
- 调试的总结
- 本地远程访问Ubuntu16.04.3服务器上的Jupyter notebook
- golang-Http操作错误
- OpenCV学习篇2:viz模块简单用法
- str类型的split方法的特殊情况
- stm32l011F4之 低功耗睡眠模式
- CSI ETABS 2016 v16.0.3 build 1567 Win32_64 2CD