在多线程中同时进行i++操作 不能保证i的原子性。i++ 可以看做是i=i+1 即先从内存中读出i的值 再设置新的值。多线程操作一个线程再刚读出i的值 另外一个线程改变了i的值则不能保证数据的一致性。
synchronized则能保证原子性。synchronized 一个线程获得锁对象则会将对象标记为锁定状态。执行完毕之后释放锁
synchronize的三种应用方式
- 修饰实例方法
- 修饰静态方法
- 修饰代码块
修饰实例方法
public class Accounting implements Runnable { int i=0; public void account(){ i++; } @Override public void run() { for (int j=0;j<2000;j++){ account(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting); Thread t2=new Thread(accounting); t.start(); t2.start(); t.join(); t2.join(); System.out.print(accounting.getI()); }
我们会发现这段代码不是我们所期望的4000.正如前面所说多线程下不能保证变量的原子操作
如果我们要保证原子操作加上synchronized关键字,保证一个代码块同时只能有一个线程执行
public class Accounting implements Runnable { int i=0; public synchronized void account(){ i++; } @Override public void run() { for (int j=0;j<2000;j++){ account(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting); Thread t2=new Thread(accounting); t.start(); t2.start(); t.join(); t2.join(); System.out.print(accounting.getI()); } }
这段代码account则是用当前对象(this)作为对象锁。当一个线程进入account方法。则标记锁定状态。另外一个线程等待其他线程释放 但是我们不能保证上一个线程执行完毕这个线程就能获得锁(因为有个锁竞争的机制)
修饰静态方法
public class Accounting implements Runnable { static int i=0; public static synchronized void account(){ i++; } @Override public void run() { for (int j=0;j<2000;j++){ account(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting); Thread t2=new Thread(accounting); t.start(); t2.start(); t.join(); t2.join(); System.out.print(accounting.getI()); } }
synchronized修饰静态方法 则是将当前类的class对象作为对象锁Accounting.class
public class Accounting implements Runnable { static int i=0; public static void account(){ i++; } public synchronized void account2(){ i++; } @Override public void run() { for (int j=0;j<2000;j++){ account(); account2(); } } publaic int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting,"a1"); Thread t2=new Thread(accounting,"a2"); t.start(); t2.start(); t.join();//主线程挂起等待这个线程执行完毕在往下执行 t2.join(); System.out.print(accounting.getI()); } }
这段代码虽然account 和account2都加了synchronized关键字,但是并不能保证线程的原子性操作 account2和account并不是用的同一个对象锁。
当线程a1进入account 方法 a2线程等待阻塞等待释放。当account方法执行完毕a1释放锁 再往竞争account2的锁的时候,并不能阻止其他线程访问account方法。因为account2这个时候锁定的是accounting对象的锁。而account 使用的是Accounting.class的锁 Accounting.class锁已经被释放
修饰代码块
使用this
public class Accounting implements Runnable { int i=0; public void account(){ synchronized (this) { i++; } } @Override public void run() { for (int j=0;j<2000;j++){ account(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting,"a1"); Thread t2=new Thread(accounting,"a2"); t.start(); t2.start(); t.join();//主线程挂起等待这个线程执行完毕在网下执行 t2.join(); System.out.print(accounting.getI()); }
使用Object
public class Accounting implements Runnable { int i=0; Object lockObj=new Object(); public void account(){ synchronized (lockObj) { i++; } } @Override public void run() { for (int j=0;j<2000;j++){ account(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting,"a1"); Thread t2=new Thread(accounting,"a2"); t.start(); t2.start(); t.join();//主线程挂起等待这个线程执行完毕在网下执行 t2.join(); System.out.print(accounting.getI()); } }
使用class
.
public class Accounting implements Runnable { int i=0; Object lockObj=new Object(); public void account(){ synchronized (Accounting.class) { i++; } } @Override public void run() { for (int j=0;j<2000;j++){ account(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting,"a1"); Thread t2=new Thread(accounting,"a2"); t.start(); t2.start(); t.join();//主线程挂起等待这个线程执行完毕在网下执行 t2.join(); System.out.print(accounting.getI()); } }
我们可以发现sychronized的锁对象可以是任意的 要保证原子性必须保证多线程获取的锁对象是同一个
(之前记得别人说不能用字符串作为锁对象 会导致 总是线程a1获得锁 但是我测试没有发现这样的问题)
sychronized原理
java对象在jvm内存中机构
锁对象monitor的引用则是保存在java对象头里面
实例变量保存的实例对象的所有变量信息 包括父类变量信息
monitor的实现类是ObjectMonitor
主要成员包括_WaitSet _EntryList _Owner 用来保存ObjectWaiter对象(每个等待锁的线程都会封装成ObjectWaiter)
当多线程同时访问一段同步代码块会首先进入_EntryList 获得锁的线程则会设置到_oWner 同时monitor对象的count+1
如果调用线程的wait方法则清空_oWner count-1 同时当前线程进入_WaitSet等待被唤醒
如果当前线程执行完毕也将清空_Owner count-1
int i=0; public void account(){ synchronized (this) { i++; } } @Override public void run() { for (int j=0;j<2000;j++){ account(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting= new Accounting(); Thread t=new Thread(accounting,"a1"); Thread t2=new Thread(accounting,"a2"); t.start(); t2.start(); t.join();//主线程挂起等待这个线程执行完毕在网下执行 t2.join(); System.out.print(accounting.getI()); }
这段代码编译后 同步代码块将变为
monitorenter//进入同步方法
同步代码块内容
monitorexit//退出同步方
monitorenter 获得锁的线程则会设置到monitor对象的_oWner 同时monitor对象的count+1
monitorexit 清空_Owner count-1
线程中断
public void interrupt();//只能中断阻塞线程 需要用异常捕获 public boolean isInterrupted(); public static boolean interrupted();
package com.liqiang.sychronize; public class Accounting implements Runnable { int i = 0; public void account() { } @Override public void run() { try { while (true) { Thread.sleep(2000); System.out.println("1"); } } catch (InterruptedException e) { e.printStackTrace(); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting = new Accounting(); Thread t = new Thread(accounting, "a1"); t.start(); Thread.sleep(4000); t.interrupt(); System.out.println(t.isInterrupted()); t.join();//主线程挂起等待这个线程执行完毕在往下执行 } }
interrupt方法只能中断阻塞线程(需要try捕获异常 否则会一直执行下去)非阻塞线程需要我们手动中断
package com.liqiang.sychronize; public class Accounting implements Runnable { int i = 0; public void account() { } @Override public void run() { while (true) { if(Thread.currentThread().isInterrupted()){ break; } System.out.println("1"); } } public int getI() { return i; } public static void main(String[] args) throws InterruptedException { Accounting accounting = new Accounting(); Thread t = new Thread(accounting, "a1"); t.start(); Thread.sleep(4000); t.interrupt(); System.out.println(t.isInterrupted()); t.join();//主线程挂起等待这个线程执行完毕在网下执行 } }
Thread.sleep 与wait的区别 wait会将线程锁释放 线程保存到monitor的 _waitSet里面 等待被唤醒。 sleep是线程休眠并不释放锁