java synchronized详解,java的synchronized的实现原理
目录
1.同步同步方法2。synchronized方法将对象作为锁3。多个锁对象4。如果同步方法内部的线程抛出异常会怎么样?5.静态同步法概述面试题:.
1.多线程下如何保证正确的I结果?
2.如果出现运行时异常,线程会发生什么情况?
3.线程运行时发生异常会怎样?
为了避免临界区的竞态条件,有许多方法可以达到目的。
(1)阻塞解决方案:同步、锁定
(2)非阻塞解决方案:原子变量
Synchronized通常被称为[对象锁]。它使用互斥的方式使最多一个线程同时持有[对象锁],其他线程试图获取[对象锁]时会阻塞它。这将确保带锁的线程可以安全地执行临界区中的代码,而不用担心线程上下文切换。
00-1010当一个方法用synchronized关键字修饰时,该方法被声明为同步方法,synchronized关键字的位置在同步方法的返回类型之前。
class safe demo {//临界区资源私有静态int I=0;//临界段代码public void self increment(){ for(int j=0;j5000j){ I;} } public int getI(){ return I;} }公共类thread demo { public static void main(String[]args)throws interrupted exception { safe demo safe demo=new safe demo();//线程1和线程2执行临界段代码段Thread t1=new Thread()-{ safe demo . self increment();});线程t2=新线程(()-{ safe demo . self increment();});t1 . start();T2 . start();t1 . join();T2 . join();system . out . println(safe demo . geti());//9906 }}可以发现,当两个线程同时访问临界段的selfIncrement()方法时,会出现竞争条件的问题,即临界段代码段中两个线程的并发执行结果会因为代码执行顺序的不同而无法预测,每次运行都会得到不同的结果。因此,为了避免竞争条件的问题,我们必须确保临界段代码段操作是唯一的。这意味着当一个线程进入临界段代码段执行时,其他线程不能进入临界段代码段执行。
现在使用synchronized关键字来保护临界区代码段。代码如下:
class safe demo {//临界区资源私有静态int I=0;//临界段代码受synchronized关键字public synchronized void self increment(){ for(int j=0;j5000j){ I;} } public int getI(){ return I;}}多次运行测试用例程序,累计10000次,最终结果不再有偏差,与预期结果(10000)相同。
在方法声明中设置synchronized同步关键字,确保其方法的代码执行过程是排他的。任何时候都只允许一个线程进入同步方法(临界区代码段)。如果其他线程需要执行同样的方法,只能等待排队。
1. synchronized 同步方法
定义线程的执行逻辑:
类thread task {//临界区代码受synchronized关键字保护。公共同步void测试(){try {
System.out.println(Thread.currentThread().getName()+" begin"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" end"); } catch (InterruptedException e) { e.printStackTrace(); } }}分别创建两个线程,在两个线程的执行体中执行线程逻辑:
public class ThreadA extends Thread { ThreadTask threadTask ; public ThreadA(ThreadTask threadTask){ super(); this.threadTask = threadTask; } @Override public void run() { threadTask.test(); }}
public class ThreadB extends Thread { ThreadTask threadTask ; public ThreadB(ThreadTask threadTask){ super(); this.threadTask = threadTask; } @Override public void run() { threadTask.test(); }}
创建一个锁对象,传给两个线程:
public class Main { public static void main(String[] args) throws InterruptedException { ThreadTask threadTask = new ThreadTask(); ThreadA t1 = new ThreadA(threadTask); ThreadB t2 = new ThreadB(threadTask); t1.start(); t2.start(); }}
执行结果:
Thread-0 beginThread-0 endThread-1 beginThread-1 end
这里两个线程的锁对象都是threadTask,所以同一时间只有一个线程能拿到这个锁对象,执行同步代码块。另外,需要牢牢记住共享这两个字,只有共享资源的写访问才需要同步化,如果不是共享资源,那么就没有同步的必要。
总结:
(1) A线程先持有object对象的锁,B线程如果在这时调用object对象中的synchronized类型的方法,则需等待,也就是同步;
(2) 在方法声明处添加synchronized并不是锁方法,而是锁当前类的对象;
(3) 在Java中只有将对象作为锁,并没有锁方法这种说法;
(4) 在Java语言中,锁就是对象,对象可以映射成锁,哪个线程拿到这把锁,哪个线程就可以执行这个对象中的synchronized同步方法;
(5) 如果在X对象中使用了synchronized关键字声明非静态方法,则X对象就被当成锁;
3. 多个锁对象
创建两个线程执行逻辑ThreadTask对象,即产生了两把锁
public class Main { public static void main(String[] args) throws InterruptedException { ThreadTask threadTask1 = new ThreadTask(); ThreadTask threadTask2 = new ThreadTask(); // 两个线程分别执行两个不同的线程执行逻辑对象 ThreadA t1 = new ThreadA(threadTask1); ThreadB t2 = new ThreadB(threadTask2); t1.start(); t2.start(); }}
执行结果:
Thread-0 beginThread-1 beginThread-0 endThread-1 end
test()
方法使用了synchronized关键字,任何时间只允许一个线程进入同步方法,如果其他线程需要执行同一个方法,那么只能等待和排队。执行结果呈现了两个线程交叉输出的效果,说明两个线程以异步方式同时运行。
在系统中产生了两个锁,ThreadA的锁对象是threadTask1,ThreadB的锁对象是threadTas2,线程和业务对象属于一对一的关系,每个线程执行自己所属业务对象中的同步方法,不存在锁的争抢关系,所以运行结果是异步的。
synchronized方法的同步锁实质上使用了this对象锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象作为锁(哪个对象调用了带有synchronized关键字的方法,哪个对象就是锁),其他线程只能等待,前提是多个线程访问的是同一个对象。
4. 如果同步方法内的线程抛出异常会发生什么?
public class SafeDemo { public synchronized void selfIncrement(){ if(Thread.currentThread().getName().equals("t1")){ System.out.println("t1 线程正在运行"); int a=1; // 死循环,只要t1线程没有执行完这个方法,就不会释放锁 while (a==1){ } }else{ System.out.println("t2 线程正在运行"); } }}
public class SafeDemo { public synchronized void selfIncrement(){ if(Thread.currentThread().getName().equals("t1")){ System.out.println("t1 线程正在运行"); int a=1; while (a==1){ Integer.parseInt("a"); } }else{ System.out.println("t2 线程正在运行"); } }}
执行结果:t2线程得不到执行
t1 线程正在运行
此时,如果我们在同步方法中制造一个异常:
public class SafeDemo { public synchronized void selfIncrement(){ if(Thread.currentThread().getName().equals("t1")){ System.out.println("t1 线程正在运行"); int a=1; while (a==1){ Integer.parseInt("a"); } }else{ System.out.println("t2 线程正在运行"); } }}
线程t1出现异常并释放锁,线程t2进入方法正常输出,说明出现异常时,锁被自动释放了。
5. 静态的同步方法
在Java世界里一切皆对象。Java有两种对象:Object实例对象和Class对象。每个类运行时的类型信息用Class对象表示,它包含与类名称、继承关系、字段、方法有关的信息。JVM将一个类加载入自己的方法区内存时,会为其创建一个Class对象,对于一个类来说其Class对象是唯一的。Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机调用类加载器中的defineClass方法自动构造的,因此不能显式地声明一个Class对象。
普通的synchronized实例方法,其同步锁是当前对象this的监视锁。如果某个synchronized方法是static(静态)方法,而不是普通的对象实例方法,其同步锁又是什么呢?
public class StaticSafe { // 临界资源 private static int count = 0; // 使用synchronized关键字修饰static方法 public static synchronized void test(){ count++; }}
静态方法属于Class实例而不是单个Object实例,在静态方法内部是不可以访问Object实例的this引用的。所以,修饰static方法的synchronized关键字就没有办法获得Object实例的this对象的监视锁。
实际上,使用synchronized关键字修饰static方法时,synchronized的同步锁并不是普通Object对象的监视锁,而是类所对应的Class对象的监视锁。
为了以示区分,这里将Object对象的监视锁叫作对象锁,将Class对象的监视锁叫作类锁。当synchronized关键字修饰static方法时,同步锁为类锁;当synchronized关键字修饰普通的成员方法时,同步锁为对象锁。由于类的对象实例可以有很多,但是每个类只有一个Class实例,因此使用类锁作为synchronized的同步锁时会造成同一个JVM内的所有线程只能互斥地进入临界区段。
public class StaticSafe { // 临界资源 private static int count = 0; // 对JVM内的所有线程同步 public static synchronized void test(){ count++; }}zzzzzzzzzzzzzzzzzzz
所以,使用synchronized关键字修饰static方法是非常粗粒度的同步机制。
通过synchronized关键字所抢占的同步锁什么时候释放呢?一种场景是synchronized块(代码块或者方法)正确执行完毕,监视锁自动释放;另一种场景是程序出现异常,非正常退出synchronized块,监视锁也会自动释放。所以,使用synchronized块时不必担心监视锁的释放问题。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。