6. 探究并发编程中volatile和synchronized关键字的含义和用法

Java 多线程并发编程中的volatile和synchronized关键字是Java功法中的难点,这篇博文将通过一个直观的示例讲解并发编程的道法。

如果没有Java多线程开发基础的,请移步观看博文
第一集:Java使用多线程实现并发编程理论篇
第二集:创建线程的三种方法以及线程池的几种类型
第三集:并发编程一定比顺序编程速度更快么?
第四集:并发编程中为什么会出现死锁?

在这节课中,我将尝试在多线程环境下模拟一个插入数据库的操作方法。

1.1 不使用任何关键字的效果

不使用任何关键字的效果,模拟代码如下,

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadNormalTest {
	
	private  Boolean enableInsert=true;

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建当前类的实例化对象
		MyThreadNormalTest myThreadTest=new MyThreadNormalTest();
		
		//线程池中初始化两个线程
		ExecutorService executorService=Executors.newFixedThreadPool(2);

		Thread threadOne=new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});
		
        Thread threadTwo=new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});
        
        //启动第一个线程
        executorService.execute(threadOne);
        //启动第二个线程
        executorService.execute(threadTwo);
        
        //关闭线程池
		executorService.shutdown();
		
		//测试结果:
//		pool-1-thread-1:true
//		pool-1-thread-2:true
//		pool-1-thread-2:insert method start
//		pool-1-thread-1:insert method start
//		pool-1-thread-1:insert method success
//		pool-1-thread-2:insert method success
//		pool-1-thread-2:insert method end
//		pool-1-thread-1:insert method end
	}
	
	private void insertMethod() {
		System.out.println(Thread.currentThread().getName()+":"+enableInsert);
		if (enableInsert) {
			System.out.println(Thread.currentThread().getName() + ":" + "insert method start");
			try {
				// 假设插入比较耗时间
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ":" + "insert method success");
			enableInsert = false;
			System.out.println(Thread.currentThread().getName() + ":" + "insert method end");
		}
	}
}

测试结果:

pool-1-thread-1:true
pool-1-thread-2:true
pool-1-thread-2:insert method start
pool-1-thread-1:insert method start
pool-1-thread-1:insert method success
pool-1-thread-2:insert method success
pool-1-thread-2:insert method end
pool-1-thread-1:insert method end

结果分析:
线程A 执行了插入方法成功
线程B也执行插入方法成功

如此看来多线程环境下,似乎有点问题呢。

问题出在当插入执行比较耗时的时候,第一个线程正在执行插入,第二个线程判断还没插入成功,也尝试插入因此都插入了数据,导致多线程下插入数据重复。

1.2 使用synchronized关键字的效果

我们需要了解volatile 关键字有三种用法

  • 第一种:对于普通方法锁是当前实例

毕竟是一个普通方法只有创建出实例对象,才能调用。

  • 第二种:对于静态同步方法锁是当前类的class对象

毕竟一个静态的方法,是通过当前类加载的时候就可以调用

  • 第三种: 对于同步方法块,锁是synchronized括号内配置的对象

1.2.1 synchronized锁住当前实例

synchronized锁住当前实例,模拟代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadSynchronizedInstanceTest {
	
	private  Boolean enableInsert=true;

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建当前类的实例化对象
		MyThreadSynchronizedInstanceTest myThreadTest=new MyThreadSynchronizedInstanceTest();
		
		//线程池中初始化两个线程
		ExecutorService executorService=Executors.newFixedThreadPool(2);

		Thread threadOne=new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});
		
        Thread threadTwo=new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});
        
        //启动第一个线程
        executorService.execute(threadOne);
        //启动第二个线程
        executorService.execute(threadTwo);
        
        //关闭线程池
        executorService.shutdown();
		
		//测试结果
//        pool-1-thread-1:true
//        pool-1-thread-2:true
//        pool-1-thread-1:insert method start
//        pool-1-thread-1:insert method success
//        pool-1-thread-1:insert method end
	}
	
	private void insertMethod() {
		System.out.println(Thread.currentThread().getName()+":"+enableInsert);
		synchronized (this) {
			if (this.enableInsert) {
				System.out.println(Thread.currentThread().getName() + ":" + "insert method start");
				try {
					// 假设插入比较耗时间
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + ":" + "insert method success");
				this.enableInsert = false;
				System.out.println(Thread.currentThread().getName() + ":" + "insert method end");
			}
		}
	}
}

测试结果:

pool-1-thread-1:true
pool-1-thread-2:true
pool-1-thread-1:insert method start
pool-1-thread-1:insert method success
pool-1-thread-1:insert method end

结果分析:
我们可以看到被synchronized关键字修饰这个方法之后,
线程A 代码run方法全部执行完,线程B才开始执行run方法。

1.3.2 synchronized锁住方法

我们再来改一下,修改下关键字位置

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadSynchronizedMethodTest {

	private  Boolean enableInsert = true;

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		// 创建当前类的实例化对象
		MyThreadSynchronizedMethodTest myThreadTest = new MyThreadSynchronizedMethodTest();

		// 线程池中初始化两个线程
		ExecutorService executorService = Executors.newFixedThreadPool(2);

		Thread threadOne = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});

		Thread threadTwo = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});

		// 启动第一个线程
		executorService.execute(threadOne);
		// 启动第二个线程
		executorService.execute(threadTwo);

		// 关闭线程池
		executorService.shutdown();
		
		//测试结果:
//		pool-1-thread-1:true
//		pool-1-thread-1:insert method start
//		pool-1-thread-1:insert method success
//		pool-1-thread-1:insert method end
//		pool-1-thread-2:false
	}

	private synchronized void insertMethod() {
		System.out.println(Thread.currentThread().getName()+":"+enableInsert);
		if (enableInsert) {
			System.out.println(Thread.currentThread().getName() + ":" + "insert method start");
			try {
				// 假设插入比较耗时间
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ":" + "insert method success");
			enableInsert = false;
			System.out.println(Thread.currentThread().getName() + ":" + "insert method end");
		}
	}
}

测试结果:

pool-1-thread-1:true
pool-1-thread-1:insert method start
pool-1-thread-1:insert method success
pool-1-thread-1:insert method end
pool-1-thread-2:false

分析:
当synchronized作用在代码块,线程一和线程二run 方法都仍然执行,但是synchronized包裹的代码块中的所有代码只允许单个线程执行完成第二个线程才能继续。

1.3.2 synchronized锁住实例变量对象

synchronized锁住实例变量对象,模拟代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadSynchronizedVarTest {
	
	private  Boolean enableInsert=true;

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建当前类的实例化对象
		MyThreadSynchronizedVarTest myThreadTest=new MyThreadSynchronizedVarTest();
		
		//线程池中初始化两个线程
		ExecutorService executorService=Executors.newFixedThreadPool(2);

		Thread threadOne=new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});
		
        Thread threadTwo=new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});
        
        //启动第一个线程
        executorService.execute(threadOne);
        //启动第二个线程
        executorService.execute(threadTwo);
        
        //关闭线程池
		executorService.shutdown();
		
		//测试结果
//		pool-1-thread-2:true
//		pool-1-thread-1:true
//		pool-1-thread-1:insert method start
//		pool-1-thread-1:insert method success
//		pool-1-thread-1:insert method end
	}
	
	private void insertMethod() {
		System.out.println(Thread.currentThread().getName()+":"+enableInsert);
		synchronized (enableInsert) {
			if (enableInsert) {
				System.out.println(Thread.currentThread().getName() + ":" + "insert method start");
				try {
					// 假设插入比较耗时间
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + ":" + "insert method success");
				enableInsert = false;
				System.out.println(Thread.currentThread().getName() + ":" + "insert method end");
			}
		}
	}
}

测试结果:

pool-1-thread-2:true
pool-1-thread-1:true
pool-1-thread-1:insert method start
pool-1-thread-1:insert method success
pool-1-thread-1:insert method end

1.3 使用volatile关键字的效果

代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadVolatileTest {

	private  volatile Boolean enableInsert = true;

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		// 创建当前类的实例化对象
		MyThreadVolatileTest myThreadTest = new MyThreadVolatileTest();

		// 线程池中初始化两个线程
		ExecutorService executorService = Executors.newFixedThreadPool(2);

		Thread threadOne = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});

		Thread threadTwo = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				myThreadTest.insertMethod();
			}
		});

		// 启动第一个线程
		executorService.execute(threadOne);
		// 启动第二个线程
		executorService.execute(threadTwo);

		// 关闭线程池
		executorService.shutdown();
		
		//测试结果
//		pool-1-thread-1:insert method start
//		pool-1-thread-2:insert method start
//		pool-1-thread-2:insert method success
//		pool-1-thread-1:insert method success
//		pool-1-thread-1:insert method end
//		pool-1-thread-2:insert method end
	}

	private void insertMethod() {
		
		System.out.println(Thread.currentThread().getName()+":"+enableInsert);
		if (enableInsert) {
			System.out.println(Thread.currentThread().getName() + ":" + "insert method start");
			try {
				// 假设插入比较耗时间
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ":" + "insert method success");
			enableInsert = false;
			System.out.println(Thread.currentThread().getName() + ":" + "insert method end");
		}
	}
}

测试结果:

pool-1-thread-1:true
pool-1-thread-2:true
pool-1-thread-1:insert method start
pool-1-thread-2:insert method start
pool-1-thread-1:insert method success
pool-1-thread-2:insert method success
pool-1-thread-2:insert method end
pool-1-thread-1:insert method end

当前这个关键字不知为何似乎没有达到理想的效果
有知道的可以在本篇博文下留言,一起探讨这个关键字怎么用
????


1.4 总结:

1.4.1 synchronized

  • 重量级锁
  • 利用synchronized实现同步的基础:Java中每一个对象都可以作为锁 对于普通方法,锁是当前实例
  • 对于静态同步方法,锁是当前类的Class对象 对于同步代码块,锁是synchronized括号里配置的对象
  • 当一个线程试图访问代码块时,它首先必须得到锁,退出或者抛出异常必须释放锁

1.4.2 volatile

Java语言规范第三版中定义:

  • Java编程语言允许运行线程访问共享变量,为了确保共享变量能被准确和一致性地更新,线程应该确保通过排他锁单独获得这个变量。
  • 轻量级别的synchronized锁,用于保证共享变量的可见性
  • 可见性的意思是当一个线程修改一个共享变量的时候,另外一个线程可以读到这个修改的值
  • 如果一个字段被声明为volatile,Java线程内存模型确保所有的线程看到这个变量的值是一致的

你可能感兴趣的:(#,Java,并发多线程)