开篇知识了解
插入知识
JMM(java内存模型)的关键技术都是围绕着多线程的原子性,可见性,有序性来建立的。因此,必须了解这些概念
原子性(volatile不能保证他的原子性)
原子性指一个操作室不可中断的。即使在多个线程中一起执行的时候,一个操作一旦开始,就不会被其他干扰。
可见性
可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个值的修改
有序性
有序性问题可能是最难理解的一个,对于一个线程的执行代码而言,我们总是习惯地认为代码的执行是从前往后,一次执行的。就一个串行任务来讲,确实会这样,但是在并行的情况下这个说不准。不能确定哪一个线程跑在了前面
synchronized 由来
在为了加快程序的运行的速度,很多时候我们都会并行执行从而获得更大的运行效率,但是有个前提,我们不能牺牲程序的正确性。如果在并行后,连基本的执行结果都达不到,那么并行就已经失去了意义。还有一个关键字==volatile==,volatile使用之后,虽然有所改善,但是,==volatile==并不能保证线程的原子性操作,他只能保证可见性,他只能确保一个线程修改了数据后,其他线程能够看到这个改动,但是两个线程同时修改一个数据的时候却会产生冲突,这也是我们写单例的时候已经在对象上使用了==volatile==关键字,但是还会会推荐使用双重加锁的机制。
/**
* Created by xinchang on 2017/11/17.
*/
public class LoginInfo {
//第一把锁,保证内存中数据的可见性
private static volatile LoginInfo mInstance = null;
private LoginInfo(){
}
public static LoginInfo getInstance(){
if(mInstance==null){
synchronized (LoginInfo.class){//第二把锁,保证线程执行的原子性和有序性
if(mInstance == null){
mInstance = new LoginInfo();
}
}
}
return mInstance;
}
}
看一段有问题的volatile代码:
/**
* Created by xinchang on 2017/11/17.
*/
public class VolatileTest implements Runnable {
static volatile int i = 0;
public void run() {
for(int j = 0 ; j < 10000000;j++){
i++;
}
}
public static void main(String[] args){
VolatileTest volatileTest = new VolatileTest();
Thread thread1 = new Thread(volatileTest);
Thread thread2 = new Thread(volatileTest);
thread1.start();
thread2.start();
try {
thread1.join();//jion表示当前线程阻塞,等待其他线程跑完自己再跑,这里表示主线程等待thread1和thread2跑完后再执行输出
thread2.join();
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
11527075
这个结果要比我们想要的结果小的多,大部分情况下这个情况都要比我们想要的结果小的多的多。并没有达到我们想要的效果,虽然他使用了volatile关键字。因为volatile同时执行的时候并不能保证同步(不保证原子性),不懂volatile关键字的可以看一下下面这篇文章
我们需要保证线程的完全同步,在线程1执行的时候,线程2不仅不能写,而且也不能读,所以有了==synchorized==
synchronized定义
关键字synchorized的作用是实现线程间的同步,他的原理是在同步代码块中的代码,一次只能有一个线程进去执行,从而保证数据的准确性与安全性。
synchronized 用法
synchronized有很多种的的用法,可以在代对象上,可以在方法上,方法又分为静态方法和实例方法。总结:
- 指定加锁对象:对给定的对象加锁,进入同步代码块前要获得给定对象的锁(this)
- 实例方法:相当于对当前实例加锁,进入方法前要获得指定对象的锁(this)
- 静态方法:相当于对当前类的加锁,进入方法前要获得当前类的锁(A.class)
代码举例:
public class SynchronizedTest implements Runnable {
static int i = 0;
public void add(){
synchronized (this) {
for (int j = 0; j < 10000000; j++) {
i++;
}
}
}
public void run() {
add();
}
public static void main(String[] args) {
SynchronizedTest synchronizedTest= new SynchronizedTest();
Thread thread1 = new Thread(synchronizedTest);
Thread thread2 = new Thread(synchronizedTest);
thread1.start();
thread2.start();
try {
thread1.join();//jion表示当前线程阻塞,等待其他线程跑完自己再跑,这里表示主线程等待thread1和thread2跑完后再执行输出
thread2.join();
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
20000000
或者写成:
/**
* Created by xinchang on 2017/11/17.
*/
public class SynchronizedTest implements Runnable {
static int i = 0;
public synchronized void add(){
for (int j = 0; j < 10000000; j++) {
i++;
}
}
public void run() {
add();
}
public static void main(String[] args) {
SynchronizedTest synchronizedTest= new SynchronizedTest();
Thread thread1 = new Thread(synchronizedTest);
Thread thread2 = new Thread(synchronizedTest);
thread1.start();
thread2.start();
try {
thread1.join();//jion表示当前线程阻塞,等待其他线程跑完自己再跑,这里表示主线程等待thread1和thread2跑完后再执行输出
thread2.join();
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
20000000
两个线程都给i加了10000000次,所以执行结果在加锁后显示是正确的。
这里使用了Runnable接口创建了两个线程,并且这两个线程都同时指向了同一个Runnable接口实例,这样才能保证两个线程在工作的时候能在同一个线程上加上锁,保证线程的安全。(注意是两个线程锁在了同一个对象上),那么如果锁在了不同的对象上呢?
一种错误的同步方式(在不同的对象上进行加锁)
在上面的代码上稍加修改:
/**
* Created by xinchang on 2017/11/17.
*/
public class SynchronizedTest implements Runnable {
static int i = 0;
public synchronized void add(){ //这里添加了static 关键字,锁为SynchronizedTest.class
for (int j = 0; j < 10000000; j++) {
i++;
}
}
public synchronized void run() {
add();
}
public static void main(String[] args) {
SynchronizedTest synchronizedTest1= new SynchronizedTest();
SynchronizedTest synchronizedTest2= new SynchronizedTest();//注意,这里新new了一个runnable对象
Thread thread1 = new Thread(synchronizedTest1);
Thread thread2 = new Thread(synchronizedTest2);
thread1.start();
thread2.start();
try {
thread1.join();//jion表示当前线程阻塞,等待其他线程跑完自己再跑,这里表示主线程等待thread1和thread2跑完后再执行输出
thread2.join();
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
11451816
从结果看来,并不是我们想要的结果,因为这两个线程指向了不同的runnable实例,线程1在进入同步方法之前加锁自己的runnable,只关心自己的锁,线程2在进去同步方法之前加锁自己的runnable,只关心自己的锁,这是两把不同的锁,所以是不能保证线程安全的。
修改上面的错误方法
- 第一种方法就是在同一个Runnable对象上加锁,上面所说
- 将锁变成当前类的锁,前面总结过,如果获取当前类的锁,需要加在静态方法上,如果将上面的i++操作放到静态方法中呢?
/**
* Created by xinchang on 2017/11/17.
*/
public class SynchronizedTest implements Runnable {
static int i = 0;
public static synchronized void add(){ //这里添加了static 关键字,锁为SynchronizedTest.class
for (int j = 0; j < 10000000; j++) {
i++;
}
}
public synchronized void run() {
add();
}
public static void main(String[] args) {
SynchronizedTest synchronizedTest1= new SynchronizedTest();
SynchronizedTest synchronizedTest2= new SynchronizedTest();//注意,这里新new了一个runnable对象
Thread thread1 = new Thread(synchronizedTest1);
Thread thread2 = new Thread(synchronizedTest2);
thread1.start();
thread2.start();
try {
thread1.join();//jion表示当前线程阻塞,等待其他线程跑完自己再跑,这里表示主线程等待thread1和thread2跑完后再执行输出
thread2.join();
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
20000000
正确执行,在改为static之后,锁变成了当前类的锁,只有份儿,所以线程得到了同步
其他
除了用于线程同步,确保线程安全外,synchronized还可以保证线程间的可见性和有序性,从可见性的角度将,synchronize完全可以替代volatile,只是用法上没那么方法,但是功能强大,volatile只能作用于属性,却不能作用于方法,有序性来说,因为加了锁,一次只允许一个线程执行,所以保证了有序性。其实,在多线程的情况下,被synchronized包裹的代码块中是串行执行的。