【Java多线程】多线程同步——对“synchronized”关键字的简单理解

既然一个进程中可以有多个线程,并且多个线程共享资源,那么,由谁来管理这些个共享的资源,以至于不被各个线程争抢呢?

什么是多线程安全

线程安全的定义:

  • 对全局变量或者静态变量,同时都有读、写操作,从而造成变量数据紊乱,产生错误。

发生线程安全的原因:

  1. 线程安全问题都是由全局变量及静态变量引起的。
  2. 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;
  3. 若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全

解决线程安全的方案:

  • Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,
  • 第一个是 JVM 实现的 synchronized(内部锁),
  • 而另一个是 JDK1.5 实现的 ReentrantLock(重入锁)。

synchronized实现多线程互斥

对synchronized的理解:

  1. 是Java 中的关键字,synchronized 是内置的语言实现
  2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;

synchronized特性:

  • synchronized: 同步(锁,又称为内部锁,因为它是用Java关键字实现的),可以修饰代码块和方法,能够保证在同一时刻最多只有一个线程执行该段代码
  • 一旦使用synchronized,多线程执行起来就了。因为每次执行“锁中的代码时”,都要检查有没有线程正在占用这段代码。
  • 如果被占用了,则要等待这个占用线程执行完毕,才有机会执行该线程。

synchronized使用的三种方式:

  1. 修饰方法:对当前对象加锁,进入同步代码前要获得当前对象的锁。
  2. 修饰静态方法:给当前类加锁,会作用于类的所有对象实例。
  • 因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。
  • 所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
  1. 修饰代码块:指定加锁对象。

总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。

同步代码块

同步代码块的格式:

	synchronized(锁对象){ //锁对象是所有对象都能共享的对象
	……
	}

注意: 它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

理解synchronized如何加锁对象


public class ThreadDemo {
	public static void main(String[] args) {
		MyMultiThread mmt = new MyMultiThread();
		Thread t1 = new Thread(mmt);
		Thread t2 = new Thread(mmt);
		
		t1.setName("线程1");
		t2.setName("线程2");
		
		t1.start();
		t2.start();
	}
}

class MyMultiThread implements Runnable {
	private Object obj = new Object();; //synchronized加锁的对象时obj
	private int num = 100;
	
	@Override
	public void run() {
		while(true) {
			synchronized(obj) { //加锁obj对象,一旦有线程获得这个对象,其他线程试图获取它时将阻塞!
				if(num>0) {
					System.out.println(Thread.currentThread().getName()+"执行减法!"+num--);
				} else {
					break;
				}
			}
		}
	}
}

同步代码块实例

问题: 实现火车站窗口卖票功能
1, 火车站卖票窗口有3个窗口,同时卖票;
2, 火车站有一定数量的火车票,每售出一张数量减一;当火车票的数量小于1的时候,停止售票;

用同步代码块实现卖火车票:

public class Main {
	public static void main(String[] args) {
		// 创建线程对象
		TicketThread tt = new TicketThread();

		Thread t = new Thread(tt);
		t.setName("窗口1");
		Thread t2 = new Thread(tt);
		t2.setName("窗口2");
		Thread t3 = new Thread(tt);
		t3.setName("窗口3");

		// 启动线程对象
		t.start();
		t2.start();
		t3.start();
	}
}

class TicketThread implements Runnable {
	int tickets = 100; // 火车票
	Object obj = new Object();
	@Override
	public void run() {
		//出售火车票
		while(true) {
			synchronized (obj) {
				if(tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName() + ":出售" +tickets--);
				}
			}
		}
	}
}

同步方法

同步方法的格式:

修饰符 synchronized 返回类型 方法名(参数列表) {
	方法体
}

**同步方法: **

  1. 使用关键字synchronized修饰的方法,一旦被一个线程访问,则整个方法全部锁住,其他线程则无法访问。
  2. 普通方法中的锁是this对象;
  3. 它和同步代码块一样,作用于同一个对象。

用同步方法实现卖火车票

public class Main {
	public static void main(String[] args) {
		// 创建线程对象
		TicketThread tt = new TicketThread();

		Thread t = new Thread(tt);
		t.setName("窗口1");
		Thread t2 = new Thread(tt);
		t2.setName("窗口2");
		Thread t3 = new Thread(tt);
		t3.setName("窗口3");

		// 启动线程对象
		t.start();
		t2.start();
		t3.start();
	}
}

class TicketThread implements Runnable {
	int tickets = 100; // 火车票
	Object obj = new Object();
	@Override
	public void run() {
		//出售火车票
		while(true) {
			method2();
		}
	}
	
	private synchronized void method2() { //同步方法的锁对象就是this对象
		if (tickets > 0) {
			try {
				Thread.sleep(100); //线程休眠
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ":" + tickets--);
		}
	}
}

同步静态方法

同步静态方法的格式:

//同步一个静态方法:
修饰符 synchronized static 返回类型 方法名(参数列表) {
方法体
}

特性:

  1. static方法中的锁是类本身的字节码文件,即“类名.class”;
  2. 作用于整个类。
  3. 也就是说,这个类的所有实例都被加上同一把锁。

用同步static方法实现卖火车票

public class Main {
	public static void main(String[] args) {
		// 创建线程对象
		TicketThread tt = new TicketThread();

		Thread t = new Thread(tt);
		t.setName("窗口1");
		Thread t2 = new Thread(tt);
		t2.setName("窗口2");
		Thread t3 = new Thread(tt);
		t3.setName("窗口3");

		// 启动线程对象
		t.start();
		t2.start();
		t3.start();
	}
}

class TicketThread implements Runnable {
	static int tickets = 100; // 火车票
	Object obj = new Object();
	@Override
	public void run() {
		while(true) {//出售火车票
			method2();
		}
	}
	
	private static synchronized void method2() { //静态方法中,锁是本类自己,即“本类名.class”
		if (tickets > 0) {
			try {
				Thread.sleep(100); //线程休眠
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ":" + tickets--);
		}
	}
}

JDK1.6之后对锁的优化

由于synchronized关键字对于多线程的同步的支持性不是很友好。所以,在JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。

具体JDK1.6对锁做了哪些优化和升级,请关注本站的后续文章!


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值