Day 30 学习分享 - 线程

1. 线程安全
1.1 线程安全概述
	如果有多个线程在同时运行, 这些线程有可能会同时运行一段代码, 程序每次运行结果和单线程运行的结果是一样的, 而且其他的变量的值也和预期相同, 这就是一个安全的线程.
1.2 案例演示
public class Ticket implements Runnable {

	// 共100票
	int ticket = 100;

	@Override
	public void run() {
		while (ticket > 0) {
			// 同步代码块结合对象锁
			if (ticket <= 0) {
				return;
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ticket--;
			System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余:" + ticket);
		}
	}
}
public class ThreadDemo {

	public static void main(String[] args) {
		
		// 创建票对象
		Ticket ticket = new Ticket();

		// 创建3个窗口
		Thread t1 = new Thread(ticket, "窗口小姐姐A");
		Thread t2 = new Thread(ticket, "窗口小姐姐B");
		Thread t3 = new Thread(ticket, "窗口小姐姐C");

		t1.start();
		t2.start();
		t3.start();
	}
}
1.3 问题发现及解决
	通过运行发现, 上面的程序出现了问题, 票出现了重复的票
	其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全
本质原因:
	有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了。

解决办法:
	一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待
1.3.1 同步方法
同步方法:
	在方法声明上加上synchronized

public synchronized void method(){
   	可能会产生线程安全问题的代码
}
public class Ticket implements Runnable {

	// 共100票
	int ticket = 10;

	@Override
	public void run() {
		while (ticket > 0) {
			sellTickets();
		}
	}

	// 同步方法,作用和同步代码块一样
	public synchronized void sellTickets() {
		if (ticket <= 0) {
			return;
		}
		ticket--;
		// 在锁中进行sleep, 不会释放锁标记,其他线程仍然访问不了
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余为" + ticket);
	}
}
1.3.2 同步代码块
	同步代码块: 在代码块声明上 加上synchronized  
	
	synchronized (锁对象) {
	可能会产生线程安全问题的代码
}
public class Ticket implements Runnable {

	// 共100票
	int ticket = 10;

	// 定义锁对象,任何对象都可以充当一个对象锁
	// Object lock = new Object();

	String str = new String();

	@Override
	public void run() {
		while (ticket > 0) {
			// 同步代码块结合对象锁
			synchronized (str) {
				if (ticket <= 0) {
					return;
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				ticket--;
				System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余:" + ticket);
			}
		}
	}
}
1.3.3 总结
	a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
	b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
	c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义。
1.4 死锁
	不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉
1.5 Lock
	除了使用Synchronized来锁定临界资源, 我们还可以使用Lock接口
	
	代码格式:
	void lock();
	void unlock();
	
	Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能。通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁

	我们使用Lock接口,以及其中的lock()方法和unlock()方法替代synchronized,对卖票案例中Ticket类进行如下代码修改:
public class ReentrantLockDemo01 implements Runnable {

	// 需求:100张
	// 临界资源
	int count = 100;

	// 定义一个ReentrantLock类的对象
	ReentrantLock lock = new ReentrantLock();

	@Override
	public void run() {
		while (count > 0) {
			// 加锁
			// lock()
			lock.lock();
			if (count <= 0) {
				return;
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count--;
			System.out.println("售票员" + Thread.currentThread().getName() + "售出一张票,剩余为" + count);
			// 解锁
			// unlock()
			lock.unlock();
			// 注意:lock()和unlock()都是成对出现的
		}
	}
}
public class TestLock {

	public static void main(String[] args) {
		
		ReentrantLockDemo01 r=new ReentrantLockDemo01();
		
		Thread t0 = new Thread(r, "喜羊羊");
		Thread t1 = new Thread(r, "沸羊羊");
		Thread t2 = new Thread(r, "灰太狼");
		Thread t3 = new Thread(r, "小灰灰");

		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}
1.6 等待唤醒机制
	线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
	
常用方法:
	wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
	notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
	notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
	
	所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
public class Demo1 {

	public static void main(String[] args) {
		Test1 thread = new Test1();
		
		Thread ta = new Thread(thread);
		ta.setName("线程AAAA");  
		ta.start(); //开子线程AAA
		
		Thread tb = new Thread(thread);
		tb.setName("线程BBBB");
		tb.start(); //开子线程BBBB
	}
}

class Test1 implements Runnable {

	@Override
	synchronized public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName() + "===" + i);
			if (i == 2) {
				try {
					wait(); // 退出运行状态,放弃资源锁,进入到等待队列状态
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if (i == 1) {
				notify(); // 从等待的序列中唤起 一个线程
			}
			if (i == 4) {
				notify();
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值