Java多线程编程---传统线程同步与通信技术(synchronized)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zengxiantao1994/article/details/79840373

Java线程的同步

        多线程的并发,给我们编程带来很多好处,完成更多更有效率的程序。但是也给我们带来线程安全问题。

        解决问题的关键就是要保证容易出问题的代码的原子性所谓原子性就是指:当a线程在执行某段代码的时候,别的线程必须等到a线程执行完后,它才能执行这段代码。也就是排队一个一个解决。

        java处理线程同步的方法非常简单,只需要在需要同步的代码段,用:synchronized(Object){你要同步的代码}即可。

售票案例演示

package com.zxt.synch;

/**
 * 
 * @Description: 使用synchronized锁,来实现多线程同步
 *
 * @author: zxt
 *
 * @time: 2018年4月6日 下午5:44:15
 *
 */
public class TicketThread {

	public static void main(String[] args) {

		// 创建一个窗口售票   启动三次(三个线程操作)
		TicketWindow tw1 = new TicketWindow();

		Thread t1 = new Thread(tw1);
		Thread t2 = new Thread(tw1);
		Thread t3 = new Thread(tw1);

		t1.start();
		t2.start();
		t3.start();
	}
}

// 售票窗口类
class TicketWindow implements Runnable {
	// 一共两千张票
	private static int nums = 2000;

	public void run() {

		while (true) {
			
			try {
				// 出票速度1秒出一张
				Thread.sleep(100);
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			// 这里必须保证代码的原子性 以确保nums的互斥访问
			// 同步代码块  (必须使用同一个对象进行加锁)
			synchronized (this) {
				// 判断是否还有票
				if (nums > 0) {
					// 显示售票信息
					System.out.println(Thread.currentThread().getName()
							+ "在售出第 " + nums + "车票");
					nums--;
					
				} else {
					break;
				}
			}
		}
	}
}

        对同步机制的解释:

     java任意类型的对象都有一个标志位,该标志位具有01两种状态,其开始状态为1,当某个线程执行了synchronized(Object)语句后,object对象的标志位变为0的状态,直到执行完整个synchronized语句中的代码块后,该对象的标志位又回到1状态。

        当一个线程执行到synchronized(Object)语句的时候,先检查Object对象的标志位,如果为0状态,表明已经有另外的线程正在执行synchronized包括的代码,那么这个线程将暂时阻塞,让出CPU资源,直到另外的线程执行完相关的同步代码,并将Object对象的标志位变为1状态,这个线程的阻塞就被取消,线程能继续运行,该线程又将Object的标志位变为0状态,防止其它的线程再进入相关的同步代码块中。

        如果有多个线程因等待同一个对象的标志位而处于阻塞状态时,当该对象的标志位恢复到1状态时,只会有一个线程能够进入同步代码执行,其它的线程仍处于阻塞的状态。

        特别说明:

        1、上面所说的标志位用术语讲就是对象锁,文件锁。数据库会有行锁、表锁等

        2synchronized(object)//object(就是对象锁)可以是任意类型对象

Java线程的通信

        首先介绍几个概念:这些方法都是Object的方法,并不是线程的方法!

wait()方法

        wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法。

        当前的线程必须拥有当前对象的monitor,也即lock,就是锁,才能调用wait()方法,即wait()方法的调用必须放在synchronized方法或synchronized块中。

        线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。注意:notify()通知唤醒wait的线程后,并不是立马就能获得锁,而是先到锁池中和其他线程一样竞争(没有什么优先权,也没有什么劣势),获得锁后也不是立马就能执行,而是就绪等待调度程序的调度。

        与sleep比较:

        当线程调用了wait()方法时,它会释放掉对象的锁。Thread.sleep()是线程方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。并且睡眠结束能恢复到之前的运行状态。

notify()方法

        notify()方法会唤醒一个等待当前对象的锁的线程。notify()方法应该是被拥有对象的锁的线程所调用。换句话说,和wait()方法一样,notify方法调用必须放在synchronized方法或synchronized块中。

        如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关。被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。

        实例2:需求:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次。

package com.zxt.synch;

/**
 * 
 * @Description: 子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次
 *
 * @author: zxt
 *
 * @time: 2018年4月6日 下午7:12:39
 *
 */
public class ThreadCommunication {

	public static void main(String[] args) {
		// 分析,子线程和主线程各自的循环应该不被打乱,所以应该加锁同步,而如此循环50次的逻辑,应该由需要此功能的调用者处理
		final Business business = new Business();
		
		// 子线程
		new Thread(new Runnable() {

			public void run() {
				for (int i = 1; i <= 50; i++) {
					business.sub(i);
				}

			}
		}).start();

		// 主线程
		for (int i = 1; i <= 50; i++) {
			business.main(i);
		}
	}

}

/**
 * 
 * @Description: 同步的功能封装在资源内部(这样调用它的线程则不再需要考虑线程同步问题)
 *
 * @author: zxt
 *
 * @time: 2018年4月6日 下午7:36:02
 *
 */
class Business {
	// 表示是否应该sub函数执行(为true表示sub执行)
	private boolean bSubShould = true;
	
	public synchronized void sub(int i) {
		while(!bSubShould) {
			try {
				// 非sub函数执行时,则等待
				this.wait();
				
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for (int j = 1; j <= 10; j++) {
			System.out.println("this is sub thread sequence of " + j + ", loop of " + i);
		}
		
		// 下次执行是main函数
		bSubShould = false;
		// 同时需要唤醒等待的线程
		this.notify();
	}

	public synchronized void main(int i) {
		while(bSubShould) {
			try {
				// 非住线程执行时,则等待
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for (int j = 1; j <= 100; j++) {
			System.out.println("this is main thread sequence of " + j + ", loop of " + i);
		}
		
		// 下次执行的是sub函数
		bSubShould = true;
		// 同时需要唤醒等待的线程
		this.notify();
	}
}

        为什么使用wait()方法时,一般是需要while循环而不是if?

        while会一直执行循环,直到条件满足,执行条件才会继续往下执行。if只会执行一次判断条件,不满足就会等待。这样就会出现问题。

        我们知道用notify() 和notifyAll()可以唤醒线程,一般我们常用的是notifyAll(),因为notify(),只会随机唤醒一个睡眠线程,并不一定是我们想要唤醒的线程。如果使用的是notifyAll(),唤醒所有的线程,那你怎么知道他想唤醒的是某个正在等待的wait()线程呢,如果用while()方法,就会再次判断条件是不是成立,满足执行条件了,就会接着执行,而if会直接唤醒wait()方法,继续往下执行,根本不管这个notifyAll()是不是想唤醒的是自己还是别人,可能此时if的条件根本没成立。

        总结:将同步的代码写在类(资源的内部),而不是写在线程中,这样不同的线程访问,都是同步的,不用在每个线程中实现同步。

猜你喜欢

转载自blog.csdn.net/zengxiantao1994/article/details/79840373