public class PrintObject { public void printString(){ System.out.println("begin"); if(Thread.currentThread().getName().equals("a")){ PrintStream printStream = System.out; printStream.println("线程 a 运行"); } if(Thread.currentThread().getName().equals("b")){ System.out.println("线程 b 运行"); } System.out.println("end"); } }
public static void main(String[] a) { PrintObject pb = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); thread1.start(); Thread thread2 = new Thread(pb::printString); thread2.setName("b"); thread2.start(); }
咱们建立两个线程来调用同一业务对象的相同功能时, 能够看到下面输出.java
begin begin 线程 a 运行 end 线程 b 运行 end
两个线程在一块儿执行 printString
方法, 而且交叉打印. 也就是说当咱们启动一个线程执行某个方法的时候就是异步执行, 至于为啥要这样演示, 是由于下面的同步.数据库
将 synchronized public void printString()
方法上加入 synchronized
关键字, 来使方法同步. 安全
执行结果:网络
begin 线程 a 运行 end begin 线程 b 运行 end
那么为何加入 synchronized
关键字后就会同步呢? 这是由于关键字 synchronized
会取得一把对象锁, 而不是把一段代码或方法当作锁; 哪一个线程先执行带 synchronized
关键字的方法, 哪一个线程就持有该方法所属的对象的锁 Look, 那么其余线程只能呈等待状态.异步
这里有个前提是多个线程访问同一个对象, 下面演示的是多个线程访问不一样的对象.this
public class PrintObject { synchronized public void printString(){ System.out.println("begin"); if(Thread.currentThread().getName().equals("a")){ PrintStream printStream = System.out; printStream.println("线程 a 运行"); try { Thread.sleep(100000); } catch (InterruptedException e) { } } if(Thread.currentThread().getName().equals("b")){ System.out.println("线程 b 运行"); } System.out.println("end"); } }
public static void main(String[] a) { PrintObject pb = new PrintObject(); PrintObject pb1 = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); thread1.start(); Thread thread2 = new Thread(pb1::printString); thread2.setName("b"); thread2.start(); }
执行结果线程
begin 线程 a 运行 begin 线程 b 运行 end
让 a 线程睡眠 100000 毫秒, 能够看到 a 线程并无执行完, b 线程就运行了. 这也可以证实 synchronized
关键字取得是对象锁.code
另外还须要注意一点, 咱们使用两个线程执行同一对象的不一样同步方法时, 若是线程 a 在睡眠, 那么线程 b 也会一直等待, 线程 a 执行完毕后再去执行.orm
注: 同步方法必定是线程安全的.
若是一个获取锁的线程调用其它的synchronized修饰的方法, 会发生什么?对象
在一个线程使用synchronized方法时调用该对象另外一个synchronized方法, 即一个线程获得一个对象锁后再次请求该对象锁, 是永远能够拿到锁的.
在Java内部, 同一个线程调用本身类中其余synchronized方法/块时不会阻碍该线程的执行, 同一个线程对同一个对象锁是可重入的, 同一个线程能够获取同一把锁屡次, 也就是能够屡次重入. 缘由是Java中线程得到对象锁的操做是以线程为单位的, 而不是以调用为单位的.
这种状况也能够发生在继承中, 也就是说子类的同步方法调用父类的同步方式时, 时能够锁重入的.
可是, 若是子类重写了父类的方法, 并无使用 synchronized 关键字, 则同步就失效了. 由于子类重写父类的方法, 当咱们调用方法执行代码时, 执行的是子类的方法, 因此变成了异步执行.
public class PrintObject { public synchronized void printString(){ try { System.out.println(Thread.currentThread().getName() + " 执行"); System.out.println(Thread.currentThread().getName() + " 插入数据到数据库"); // 让线程休眠, 模拟出网络延时 Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + " 共享数据减1"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName() + " 插入数据到数据库"); Thread.sleep(5000); if (Thread.currentThread().getName().equals("b")) { SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); } } catch (InterruptedException e) { e.printStackTrace(); } } }
PrintObject pb = new PrintObject(); Thread thread1 = new Thread(pb::printString); thread1.setName("a"); Thread thread2 = new Thread(pb::printString); thread2.setName("b"); SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); thread1.start(); thread2.start();
执行结果
47:34 a 执行 a 插入数据到数据库 a 共享数据减1 a 插入数据到数据库 b 执行 b 插入数据到数据库 b 共享数据减1 b 插入数据到数据库 48:04
咱们上面这段程序两个线程所有执行完所用的时间为 30 秒, 这里能够看出同步方法存在一个很大的弊端.
就是说咱们的某个线程开始执行方法时, 不管咱们操做的是否是共享数据, 别的线程都会等待此线程释放锁. 而后继续执行.
但是咱们在插入数据到数据库的时候, 并非在操做共享数据, 那么咱们有没有什么办法, 只同步操做共享数据的那部分代码呢?
咱们就可使用 synchronized 同步代码块, 将程序修改为下面样子.
public class PrintObject { public void printString(){ try { System.out.println(Thread.currentThread().getName() + " 执行"); System.out.println(Thread.currentThread().getName() + " 插入数据到数据库"); // 让线程休眠, 模拟出网络延时 Thread.sleep(5000); synchronized(this) { System.out.println(Thread.currentThread().getName() + " 共享数据减1"); Thread.sleep(5000); } System.out.println(Thread.currentThread().getName() + " 插入数据到数据库"); Thread.sleep(5000); if (Thread.currentThread().getName().equals("b")) { SimpleDateFormat df = new SimpleDateFormat("mm:ss"); System.out.println(df.format(new Date())); } } catch (InterruptedException e) { e.printStackTrace(); } } }
执行结果
54:12 b 执行 a 执行 b 插入数据到数据库 a 插入数据到数据库 a 共享数据减1 a 插入数据到数据库 b 共享数据减1 b 插入数据到数据库 54:32
减小了10秒的执行时间, 提升了执行效率.
同步方法和同步代码块的锁都是同一把锁. 同步方法获取的是该方法的对象锁, 而同步代码块获取中的参数是 this, 表示当前对象. 因此获取的是同一把锁.
synchronized 关键字能够应用在 static 静态方法上, 表示当前的 *.java 文件对应的 Class 类进行持锁.
虽然运行结果与 synchronized 关键字加到非 static 静态方法上的结果相似, 可是是对 Class 类进行加锁, 而 Class 锁能够对类的全部对象起做用.
synchronized (DemoApplication.class) { }