线程安全是并发编程中的重要关注点,应该注意到的是,形成线程安全问题的主要诱因有两点java
所以为了解决这个问题,咱们可能须要这样一个方案,当存在多个线程操做共享数据时,须要保证同一时刻有且只有一个线程在操做共享数据,其余线程必须等到该线程处理完数据后再进行,这种方式有个高尚的名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其余线程只能处于等待的状态,直到当前线程处理完毕释放该锁。
在 Java 中,关键字 synchronized 能够保证在同一个时刻,只有一个线程能够执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操做),同时咱们还应该注意到synchronized另一个重要的做用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其余线程所看到(保证可见性,彻底能够替代Volatile功能),这点确实也是很重要的。编程
synchronized关键字最主要有如下3种应用方式,下面分别介绍安全
所谓的实例对象锁就是用synchronized修饰实例对象中的实例方法,注意是实例方法不包括静态方法并发
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; public class AccountingSync2 implements Runnable { //共享资源(临界资源) static int i = 0; /** * synchronized 修饰实例方法 */ synchronized void getI() { if (i % 1000000 == 0) { print(i); } } public synchronized void increase() { i++; getI(); } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(i); } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); AccountingSync2 accountingSync2 = new AccountingSync2(); exec.execute(accountingSync2); exec.execute(accountingSync2); exec.shutdown(); } } 最后的结果为: 1000000 1519541 2000000 2000000
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; public class AccountingSync2 implements Runnable { //共享资源(临界资源) static int i = 0; /** * synchronized 修饰实例方法 */ synchronized void getI() { if (i % 1000000 == 0) { print(i); } } public synchronized void increase() { i++; getI(); } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(i); } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); AccountingSync2 accountingSync2 = new AccountingSync2(); exec.execute(accountingSync2); exec.execute(new AccountingSync2()); exec.shutdown(); } } #输出结果: 1000000 1249050 1329218
当synchronized做用于静态方法时,其锁就是当前类的class对象锁。因为静态成员不专属于任何一个实例对象,是类成员,所以经过class对象锁能够控制静态成员的并发操做。须要注意的是若是一个线程A调用一个实例对象的非static synchronized 方法,而线程B须要调用这个实例对象所属类的静态 synchronized方法,是容许的,不会发生互斥现象,由于访问静态 synchronized 方法占用的锁是当前类的 class 对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。ide
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; class OtherTask implements Runnable{ AccountingSyncClass accounting = new AccountingSyncClass(); @Override public void run(){ for (int j = 0; j < 1000000; j++) { accounting.increaseForObject(); } print(accounting.getI()); } } public class AccountingSyncClass implements Runnable { //共享资源(临界资源) private static int i = 0; /** * synchronized 修饰实例方法 */ public synchronized void increaseForObject() { i++; } public synchronized static void increase() { i++; } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(i); } public int getI(){ return i; } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new AccountingSyncClass()); exec.execute(new AccountingSyncClass()); exec.execute(new OtherTask()); // 1 exec.shutdown(); } } 输出结果为: 1459696 2692181 2754098 注释掉代码中的 1 那一行代码的输出结果为: 1468495 2000000
除了使用关键字修饰实例方法和静态方法外,还可使用同步代码块,在某些状况下,咱们编写的方法体可能比较大,同时存在一些比较耗时的操做,而须要同步的代码又只有一小部分,若是直接对整个方法进行同步操做,可能会得不偿失,此时咱们可使用同步代码块的方式对须要同步的代码进行包裹,这样就无需对整个方法进行同步操做了。this
public class AccountingSync implements Runnable{ static AccountingSync instance=new AccountingSync(); static int i=0; @Override public void run() { //省略其余耗时操做.... //使用同步代码块对变量i进行同步操做,锁对象为instance synchronized(instance){ for(int j=0;j<1000000;j++){ i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(instance); Thread t2=new Thread(instance); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
//this,当前实例对象锁 synchronized(this){ for(int j=0;j<1000000;j++){ i++; } } //class对象锁 synchronized(AccountingSync.class){ for(int j=0;j<1000000;j++){ i++; } }
package sychronized; import static net.mindview.util.Print.*; import java.util.concurrent.*; class OtherTask implements Runnable{ AccountingSyncClass accounting = new AccountingSyncClass(); @Override public void run(){ for (int j = 0; j < 1000000; j++) { accounting.increaseForObject(); } print(accounting.getAdapterInteger()); } } class AdapterInteger { private int i = 0; public synchronized void increase(){ ++i; } public synchronized int getI(){ return i; } } public class AccountingSyncClass implements Runnable { //共享资源(临界资源) private static AdapterInteger adapterInteger = new AdapterInteger(); /** * synchronized 修饰实例方法 */ public synchronized void increaseForObject() { adapterInteger.increase(); } public synchronized static void increase() { adapterInteger.increase(); } @Override public void run() { for (int j = 0; j < 1000000; j++) { increase(); } print(getAdapterInteger()); } public static int getAdapterInteger() { return adapterInteger.getI(); } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); exec.execute(new AccountingSyncClass()); exec.execute(new AccountingSyncClass()); exec.execute(new OtherTask()); exec.shutdown(); } } #输出结果为 1183139 2688189 3000000
这样三个线程中的任务的锁都是咱们的共享变量 adapterInteger 对象的锁,这样就能够完成真正的同步,无论哪一个线程都是得到了 adapterInteger 对象的锁才能运行相应的代码。线程