并发编程的原则:设计并发编程的目的是为了使程序得到更高的执行效率,但毫不能出现数据一致性(数据准确)问题,若是并发程序连最基本的执行结果准确性都没法保证,那并发编程就没有任何意义。数据库
为何会出现数据不正确:编程
若是一个资源(变量,对象,文件,数据库)能够同时被不少线程使用就会出现数据不一致问题,也就是咱们说的线程安全问题。这样的资源被称为共享资源或临界区。安全
举个例子:多线程
一个共享变量m,如今有两个线程同时对它进行累加操做,各执行10000次,那么我么期待的结果是20000,但实际上并非这样的。看代码:并发
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static volatile int m=0; public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main线程等待这连个线程执行结束后继续执行下面的代码 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最终结果:"+m); } public void run() { for(int i=0;i<10000;i++){ m++; } } }
不管运行多少次 m老是小于20000。为何会出现这样的结果呢?当线程thread1在将m++的结果写入内存以前,线程thread2已经从内存中读取了m的值,并在这个值(过期值)上进行++操做,最后将m=1写入内存中(可能就覆盖了thread1计算的m=1的值,也多是出现thread1覆盖了thread2的值)。出现这样的结果是必然的。
如何控制多线程操做共享数据引发的数据准确性问题呢?使用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称做同步互斥访问,也就是保证咱们的共享资源每次只能被一个线程使用,一旦该资源被线程使用,其余线程将不得拥有使用权。在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。函数
互斥锁:顾名思义,就是互斥访问目的的锁。post
举个简单的例子:若是对临界资源加上互斥锁,当一个线程在访问该临界资源时,其余线程便只能等待。this
在Java中,每个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,只有拥有该对象锁的线程才能访问。spa
在Java中,能够使用synchronized关键字来标记一个须要同步的方法或者同步代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便得到了该对象的锁,其余线程暂时没法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其余线程才能执行这个方法或者代码块。经过这种方式达到咱们上面提到的在同一时刻,只能有一个线程访问临界资源。线程
synchronized用法:
一、同步代码块
synchronized(synObject) { }
用法:将synchronized做用于一个给定的对象或类的一个属性,因此每当有线程执行这段代码块,该线程会先请求获取对象synObject的锁,若是该锁已被其余线程占有,那么新的线程只能等待,从而使得其余线程没法同时访问该代码块。
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static volatile int m=0; public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main线程等待这连个线程执行结束后继续执行下面的代码 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最终结果:"+m); } public void run() { synchronized (this) { for(int i=0;i<10000;i++){ m++; } } } }
该代码是使用当前对象做为互斥锁,下面咱们使用类的一个属性做为互斥锁。
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static volatile int m=0; private Object object=new Object(); public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main线程等待这连个线程执行结束后继续执行下面的代码 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最终结果:"+m); } public void run() { synchronized (object) { for(int i=0;i<10000;i++){ m++; } } } }
一、同步方法
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static int m=0; public static void main(String[] args) { Runnable run=new SynchronizedTest(); Thread thread1=new Thread(run); Thread thread2=new Thread(run); thread1.start(); thread2.start(); try { //join() 使main线程等待这连个线程执行结束后继续执行下面的代码 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最终结果:"+m); } public synchronized void run() { for(int i=0;i<10000;i++){ m++; } } }
这段代码中,synchronzied做用于一个实例方法,就是说当线程在进入run()方法前,必须获取当前对象实例锁,本例中对象实例锁就是run。在这里提醒你们认真看这三段代码中main函数的实现,在这里咱们使用Runnable建立两个线程,而且这两个线程都指向同一个Runnable接口实例,这样才能保证两个线程在工做中,使用同一个对象锁,从而保证线程安全。
一种错误的理解:
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static int m=0; public static void main(String[] args) { Thread thread1=new Thread(new SynchronizedTest()); Thread thread2=new Thread(new SynchronizedTest()); thread1.start(); thread2.start(); try { //join() 使main线程等待这连个线程执行结束后继续执行下面的代码 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最终结果:"+m); } public synchronized void run() { for(int i=0;i<10000;i++){ m++; } } }
这段代码的运行结果是错误的,请看main函数的实现方式,使用Runnable建立两个线程,可是两个线程拥有各自的Runnable实例,因此当thread1线程进入同步方法时加的是本身的对象实例锁,而thread2在进入同步方法时关注的是本身的实例锁,两个线程拥有不一样的对象实例锁,所以没法达到互斥的要求。
略做改动:
package com.jalja.base.threadTest; public class SynchronizedTest implements Runnable{ private static int m=0; public static void main(String[] args) { Thread thread1=new Thread(new SynchronizedTest()); Thread thread2=new Thread(new SynchronizedTest()); thread1.start(); thread2.start(); try { //join() 使main线程等待这连个线程执行结束后继续执行下面的代码 thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m的最终结果:"+m); } public void run() { for(int i=0;i<10000;i++){ count(); } } public static synchronized void count(){ m++; } }
这样处理结果就是我么想要的了,在这里咱们将处理业务的代码封装成一个静态的同步方法,那如今访问该同步方法须要的是当前类的锁,而类在内存中只有一份,因此不管如何,他们使用的都是同一个锁(class级别的锁)。