java多线程系列(一)--synchronized同步方法
来源:互联网 发布:cms管理系统是什么 编辑:程序博客网 时间:2024/06/02 07:17
简介
synchronzied是用来保证共享数据的同步性的,用synchronized修饰一个方法或者代码块时,能保证同一时刻最多只能有一个线程运行该代码块。
一、synchronized同步方法
1.1方法内部的变量与实例域的线程安全
上面讲到共享数据,那么什么是共享数据呢?比如,类中的实例域,就是类中方法的共享数据,而方法内的临时变量就不是其他方法的共享数据,它是这个方法的私有变量,所以不存在非线程安全问题。下面看一个简单的例子
public class HasSelfPrivateNum { public void addI(String userName) { int num = 0; 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"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(userName + " num = " + num); }}``` javapublic class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef) { this.numRef = numRef; } public void run() { numRef.addI("a"); }}<div class="se-preview-section-delimiter"></div>``` javapublic class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { this.numRef = numRef; } public void run() { numRef.addI("b"); }}``` javapublic class Run { public static void main(String[] args) { HasSelfPrivateNum ref1 = new HasSelfPrivateNum(); ThreadA a = new ThreadA(ref1); a.start(); ThreadB b = new ThreadB(ref1); b.start(); }}<div class="se-preview-section-delimiter"></div>
运行结果如下,可见线程是安全的
a set over
b set over
b num = 200
a num = 100
那么我们改一下HasSlefPrivateNum类,修改如下
public class HasSelfPrivateNum { private int num = 0; 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"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(userName + " num = " + num); }}<div class="se-preview-section-delimiter"></div>
将num变量设为类的实例域,这样num变量对于方法来说就是共享的,运行结果如下
a set over
b set over
b num = 200
a num = 200
当线程a调用addI方法时,赋予num值为100,但是此时有Thread.sleep(2000),a线程阻塞2000毫秒,于此同时,线程b也在调用addI方法,
当线程a阻塞结束时线程b已经num更新为了200,所以这样线程是不安全的。那么我们在addI方法前加上synchronized,即synchronized public void addI(String userName),再运行一下
a set over
a num = 100
b set over
b num = 200
可以看到线程a和b是排队运行的,线程a执行完addI方法后,线程b才执行。所以此时是线程安全的,即两个线程访问同一个对象的同步方法(即通synchronized修饰的方法)时一定是线程安全的。
1.2不同的对象拥有不同的锁
继续修改上面的代码,将Run类修改为
public class Run { public static void main(String[] args) { HasSelfPrivateNum ref1 = new HasSelfPrivateNum(); HasSelfPrivateNum ref2 = new HasSelfPrivateNum(); ThreadA a = new ThreadA(ref1); a.start(); ThreadB b = new ThreadB(ref2); b.start(); }}<div class="se-preview-section-delimiter"></div>
运行结果如下
a set over
b set over
b num = 200
a num = 100
可见两个线程是同时运行的,即异步运行方式。原因是什么呢?synchronized关键字获取的是对象锁,而不是把一段代码或方法当作锁,并且不同的对象所拥有的锁不是同一把,一把锁只能监控本对象的synchronized修饰方法,所以上面即使是同一个类,运行方式也是异步运行,因为是两个不同的对象。
1.4理解对象锁
前面我们看到不同线程调用同一个对象中synchronized修饰方法时是排队运行的,那么我们调用非synchronized方法呢?新建一个例子
public class MyObject { synchronized public void methodA() { try { System.out.println("begin methodA threadName = " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end methodA endTime = " + System.currentTimeMillis()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void methodB() { try { System.out.println("begin methodB threadName = " + Thread.currentThread().getName() + " begin Time = " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end methodB endTime = " + System.currentTimeMillis()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}<div class="se-preview-section-delimiter"></div>``` javapublic class MyThread1 extends Thread { private MyObject object; public MyThread1(MyObject object) { this.object = object; } public void run() { object.methodA(); }}```javapublic class MyThread2 extends Thread { private MyObject object; public MyThread2(MyObject object) { this.object = object; } public void run() { object.methodB(); }}<div class="se-preview-section-delimiter"></div>``` javapublic class RunMyObject { public static void main(String[] args) { MyObject object1 = new MyObject(); MyThread1 t1 = new MyThread1(object1); t1.setName("Thread 1"); t1.start(); MyThread2 t2 = new MyThread2(object1); t2.setName("Thread 2"); t2.start(); }}
运行结果如下
begin methodA threadName = Thread 1
begin methodB threadName = Thread 2 begin Time = 1508774763367
end methodA endTime = 1508774768367
end methodB endTime = 1508774768373
线程t1调用synchronized 修饰的methodA方法,线程t2调用普通的methodB方法,两个方法是同时运行的,这就说明不同线程可以同时(异步)调用已拥有锁的非Synchronized方法,t1先持有object1对象的锁,但是t2可以异步调用methodB方法。那同时调用synchronized修饰的方法呢?将methodB方法前加上synchronized,即synchronized public void methodB(),看一下运行结果
begin methodA threadName = Thread 1
end methodA endTime = 1508775090810
begin methodB threadName = Thread 2 begin Time = 1508775090810
end methodB endTime = 1508775095815
线程t1调用methodA方法结束后,线程t2才调用methodB,可见两个线程是排队运行的(即同步运行)。所以得出结论:对象锁监控的是此对象synchronized方法,并不监控非synchronized方法。
1.5出现异常时,锁自动释放
当一个线程执行的代码出现异常时,它锁持有的锁会自动释放
public class ServiceException { synchronized public void testMethod() { if (Thread.currentThread().getName().equals("c")) { System.out.println("ThreadName = " + Thread.currentThread().getName() + " rum begin time = " + 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 exceptionTime = " + System.currentTimeMillis()); Integer.parseInt("c");//这里将会抛出一个异常 } } } else { System.out.println("Thread D run time = " + System.currentTimeMillis()); } }}``` javapublic class ThreadC extends Thread { private ServiceException service; public ThreadC(ServiceException service) { this.service = service; } public void run() { service.testMethod(); }}<div class="se-preview-section-delimiter"></div>``` javapublic class ThreadD extends Thread { private ServiceException service; public ThreadD(ServiceException service) { this.service = service; } public void run() { service.testMethod(); }}``` javapublic class RunServiceException { public static void main(String[] args) { try { ServiceException service = new ServiceException(); ThreadC c = new ThreadC(service); ThreadD d = new ThreadD(service); c.setName("c"); d.setName("d"); c.start(); Thread.sleep(1000); d.start(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}//出现异常,将会释放对象锁
运行结果如下
ThreadName = c rum begin time = 1508834369920
ThreadName = c run exceptionTime = 1508834369997
Exception in thread “c” java.lang.NumberFormatException: For input string: “c”
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at _2_1.ServiceException.testMethod(ServiceException.java:14)
at _2_1.ThreadC.run(ThreadC.java:12)
Thread D run time = 1508834370920
在testMethod方法中是用一个while语句写的死循环,也就是说当线程C调用testMethod方法时,会一直循环,那么线程C就会一直持有service对象的锁,而不释放,线程D就无法获得service对象的锁。按照这种理解,那么线程就不可能调用testMethod方法,但是在运行结果可以看到,线程D确实调用了testMethod方法,这是什么原因呢?
原因就在于Integer.parseInt(“c”);这句代码,这句代码会抛出一个NumberFormatException异常,当异常抛出之后,线程C会释放service对象的锁,接着线程D争抢到了这把锁,所以线程D就会执行testMethod方法。
1.6 同步不具有继承性
比如,一个父类Parent类,Parent类中有一个用synchronized public void A()方法,Child类继承了Parent类,同时Child类重写了A方法,但是并没有用synchronized修饰,此时Child类中的A方法并不具有同步性。
我们可以这样理解,一般来说,父类中的public方法,子类继承之后就变成了子类的私有方法(这是在不重写这些方法的前提下)。子类重写了这些方法之后(比如Child 类重写了Parent类中的A方法),子类中的方法就与父类中方法没有关系了(Child 中的A和Parent中的A方法),既然都没有关系了,父类中的方法是否synchronized与子类有何相干呢。但是如果子类并没有重写父类中的方法(比如Child类没有重写Parent的A方法),此时A方法仍然具有同步性,因为可以理解成变成子类的私有方法了嘛。所以这里的继承性指的是子类是否重写父类的方法。
总结
1.synchronized是为了同步共享数据的,因为不共享的数据也没有同步的必要。
2.synchronized获取的是对象锁,这个对象锁监控对象的synchronized方法
3.出现异常当前线程会释放锁
附:本文是基于《java多线程编程核心技术》写的,文中示例代码很多是使用的书上的。
- java多线程系列(一)--synchronized同步方法
- Java多线程入门:Synchronized同步类方法
- 【JAVA】多线程之synchronized 同步数据 方法
- Java多线程3.1:synchronized同步方法
- 【多线程】synchronized同步方法
- java Synchronized 多线程同步
- java多线程同步,Synchronized
- Java 多线程同步--synchronized
- Java 多线程 synchronized同步
- java多线程同步 synchronized
- 多线程系列四-同步-Synchronized
- [java多线程]多线程同步(一)——synchronized
- java多线程、并发系列之 (synchronized)同步与加锁机制
- java多线程系列(二)---synchronized同步代码块
- Java同步方法synchronized
- java--同步方法(synchronized)
- 初学Java多线程:使用Synchronized关键字同步类方法
- 初学Java多线程:使用Synchronized关键字同步类方法
- Trie树详解及其应用
- 深度学习新星:GAN的基本原理、应用和走向 | 硬创公开课
- 一些有意思或者要注意的小问题
- TensorFlow学习笔记(一)
- Python-opencv划线/画圆/椭圆/添加文字
- java多线程系列(一)--synchronized同步方法
- Linux命令 目录操作
- css禁止选中文字
- VMware workstation 14的安装注册与Linux虚拟机CentOS/Ubuntu的创建
- 区块链入门(2):搭建以太坊私有链,执行挖矿.
- 算法思想-深度搜索算法-leetcode相关题目总结
- Xcoode9兼容FFmmpeg
- 1024,祝所有程序员节日快乐
- eclipse运行项目可以正常运行,但是localhost:8080打不开