java中多线程的同步方法
版权声明:本文为博主原创文章,转载请注明出处。
https://mp.csdn.net/mdeditor/84573042
1、为什么使用同步?
Java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
2、同步的方式
不同步方式:
/*
* 项目:售票问题,模拟火车站售票系统中的多个线程同时对某一个车次车票的购买操作。
* 在主程序中首先生成3个线程,然后启动它们,共定义5张车票,每个线程都对车票进行购买操作。
*/
package xiancheng;
//非同步
class MyThread4 implements Runnable
{
//假设一共5张票
private int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
if(ticket>0)//还有票
{
try {
Thread.sleep(300);//加入延迟
}catch(InterruptedException e)
{
/*InterruptedException:当线程在活动之前或活动期间处于正在等待、
休眠或占用状态且该线程被中断时,抛出该异常。
*/
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票:ticket="+ticket--);
}
}
}
}
public class SyncTest1 {
public static void main(String[] args) {
MyThread4 mt=new MyThread4();//定义线程对象
Thread t1=new Thread(mt,"师范学院");
Thread t2=new Thread(mt,"中北大学");
Thread t3=new Thread(mt,"太原工业学院");
t1.start();
t2.start();
t3.start();
}
}
-------------------------
输出结果:
中北大学卖票:ticket=4
师范学院卖票:ticket=3
太原工业学院卖票:ticket=5
太原工业学院卖票:ticket=2
中北大学卖票:ticket=0
师范学院卖票:ticket=1
- 同步方法:
即有synchronized关键字修改的方法。由于Java的每个对象都有一个内置锁,当用此关键字修饰时,内置锁会保护整个方法。在调用该方法前,需要获取内置锁,否则就处于阻塞状态。
/*
* 项目:售票问题,模拟火车站售票系统中的多个线程同时对某一个车次车票的购买操作。
* 在主程序中首先生成3个线程,然后启动它们,共定义5张车票,每个线程都对车票进行购买操作。
*/
package xiancheng;
//synchronized同步方法
public class SyncTest2 {
public static void main(String args[]) {
MyThread5 mt = new MyThread5(); // 定义线程对象
Thread t1 = new Thread(mt, "师范学院"); // 定义Thread对象
Thread t2 = new Thread(mt, "中北大学"); // 定义Thread对象
Thread t3 = new Thread(mt, "太原工业学院"); // 定义Thread对象
t2.start();
t1.start();
t3.start();
}
}
class MyThread5 implements Runnable {
private int ticket = 5; // 假设一共有5张票
public void run() {
for (int i = 0; i < 100; i++) {
this.sale(); // 调用同步方法
}
}
public synchronized void sale() { // 声明同步方法
if (ticket > 0) { // 还有票
try {
Thread.sleep(30); // 加入延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "卖票:ticket = " + ticket--);
}
}
}
----------------
输出结果:
中北大学卖票:ticket = 5
太原工业学院卖票:ticket = 4
太原工业学院卖票:ticket = 3
太原工业学院卖票:ticket = 2
太原工业学院卖票:ticket = 1
------------------------------
修饰方法范围是整个函数。
- 同步代码块(同步对象):
即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
/*
* 项目:售票问题,模拟火车站售票系统中的多个线程同时对某一个车次车票的购买操作。
* 在主程序中首先生成3个线程,然后启动它们,共定义5张车票,每个线程都对车票进行购买操作。
*/
package xiancheng;
//synchronized同步对象
public class SyncTest3 {
public static void main(String args[]) {
MyThread6 mt = new MyThread6(); // 定义线程对象
Thread t1 = new Thread(mt, "师范学院"); // 定义Thread对象
Thread t2 = new Thread(mt, "中北大学"); // 定义Thread对象
Thread t3 = new Thread(mt, "太原工业学院"); // 定义Thread对象
t1.start();
t2.start();
t3.start();
}
}
class MyThread6 implements Runnable {
private int ticket = 5; // 假设一共有5张票
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (this) { // 要对当前对象进行同步
if (ticket > 0) { // 还有票
try {
Thread.sleep(300); // 加入延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "卖票:ticket = " + ticket--);
}
}
}
}
}
----------------
输出结果:
师范学院卖票:ticket = 5
师范学院卖票:ticket = 4
师范学院卖票:ticket = 3
太原工业学院卖票:ticket = 2
太原工业学院卖票:ticket = 1
-------------------
synchronized (this) 相当于给ticket加了锁,在一个进程正在访问ticket时,此时锁锁上,其他进程将不能访问ticket。
- 使用特殊域变量(volatile)实现线程同步:
a.volatile关键字为域变量的访问提供了一种免锁机制;
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值;
d.volatile不会提供任何原子操作,它也不能用来修改final类型的变量。
/*
* 项目:售票问题,模拟火车站售票系统中的多个线程同时对某一个车次车票的购买操作。
* 在主程序中首先生成3个线程,然后启动它们,共定义5张车票,每个线程都对车票进行购买操作。
*/
package xiancheng;
class MyThread7 implements Runnable
{
//假设一共5张票
private volatile int ticket=5;
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
if(ticket>0)//还有票
{
try {
Thread.sleep(300);//加入延迟
}catch(InterruptedException e)
{
/*InterruptedException:当线程在活动之前或活动期间处于正在等待、
休眠或占用状态且该线程被中断时,抛出该异常。
*/
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票:ticket="+ticket--);
}
}
}
}
public class SyncTest4 {
public static void main(String[] args) {
MyThread7 mt=new MyThread7();//定义线程对象
Thread t1=new Thread(mt,"师范学院");
Thread t2=new Thread(mt,"中北大学");
Thread t3=new Thread(mt,"太原工业学院");
t1.start();
t2.start();
t3.start();
}
}
-------------------
输入结果:
师范学院卖票:ticket=5
中北大学卖票:ticket=4
太原工业学院卖票:ticket=3
师范学院卖票:ticket=2
太原工业学院卖票:ticket=1
中北大学卖票:ticket=1
师范学院卖票:ticket=0
-------------------
注意:volatile是非原子操作,它**不一定**会改变原子的值。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。使用volatile关键字,多线程不具备线程安全。
- 使用重入锁实现线程同步:
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
package xiancheng;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//使用重入锁
class MyThread8 implements Runnable
{
//假设一共5张票
private int ticket=5;
private Lock lock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++)
{
lock.lock();//获得锁
if(ticket>0)//还有票
{
try {
Thread.sleep(300);//加入延迟
}catch(InterruptedException e)
{
/*InterruptedException:当线程在活动之前或活动期间处于正在等待、
休眠或占用状态且该线程被中断时,抛出该异常。
*/
e.printStackTrace();
}
finally {
lock.unlock();//释放锁
}
System.out.println(Thread.currentThread().getName()+"卖票:ticket="+ticket--);
}
}
}
}
public class SyncTest5 {
public static void main(String[] args) {
MyThread8 mt=new MyThread8();//定义线程对象
Thread t1=new Thread(mt,"师范学院");
Thread t2=new Thread(mt,"中北大学");
Thread t3=new Thread(mt,"太原工业学院");
t1.start();
t2.start();
t3.start();
}
}
-------------------
运行结果:
中北大学卖票:ticket=5
师范学院卖票:ticket=4
太原工业学院卖票:ticket=3
中北大学卖票:ticket=2
师范学院卖票:ticket=1
-----------------------------------------
// 注:关于Lock对象和synchronized关键字的选择:
// a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。
// b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
// c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
- 使用局部变量实现线程同步:
如果使用ThreadLocal(线程局部变量,继承自ThreadLocal)管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal类型的常用方法 :
ThreadLocal() : 创建线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
remove() :移除此线程局部变量当前线程的值。
*注意:在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
此同步方法具体实现参考:https://blog.csdn.net/sun1021873926/article/details/77898665
参考自:
Think in java
java JDK API 1.6.0中文版
Java多线程同步方法参考: https://blog.csdn.net/sun1021873926/article/details/77898665
Java中Synchronized的用法参考: https://blog.csdn.net/luoweifu/article/details/46613015
volatile关键字的总结参考: https://blog.csdn.net/u012723673/article/details/80682208