Java学习路程之多线程中的锁、停止线程和interrupt

一.多线程中的锁

1.同步代码块(同步锁)
写法:
  synchronized(锁){
 	上锁的代码
}
public class Day23 {
	public static void main(String[] args) {
	//模拟卖票,多个途径同时
	TicketRunnable runnable = new TicketRunnable();
	//利用接口的实现类创建三个线程
	Thread thread1 = new Thread(runnable, "窗口");
	Thread thread2 = new Thread(runnable, "电话");
	Thread thread3 = new Thread(runnable, "网络");
	thread1.start();
	thread2.start();
	thread3.start();
	}
}
//利用接口来保证访问的共享资源
class TicketRunnable implements Runnable{
	private int ticketNum = 100;
	public void run() {
		while (true) {
		   //锁可以使用任意对象 ,this为本类对象 ,保证锁的唯一性
			synchronized (this) {
				if (ticketNum > 0) {
					ticketNum--;
					System.out.println(Thread.currentThread().getName() + "出售剩余" + ticketNum +"张票");
				}else {
					break;
				}
			}
			//分配CPU资源
			Thread.yield();
		}
	}
}

2.同步方法 使用关键词synchronized修饰方法

class TicketRunnable implements Runnable{
	private int ticketNum = 100;
	public void run() {
		// 循环卖票
		//利用方法的返回值停止循环
		while (true) {
			if (sellTickets()) {
				break;
			}
			//让线程让出CPU的执行资源(增加让出的几率)
			Thread.yield();
		}
	}
	
	//同步方法 使用关键词synchronized修饰方法 
	//同样使用的对象锁是this
	//同步方法可以是静态方法,成员变量也用静态,使用的对象锁不是this,因为方法是静态的,静态方法的锁是类锁 类.class
	public static synchronized boolean sellTickets() {
		if (ticketNum > 0) {
			System.out.println(Thread.currentThread().getName() +"--" + ReentrantLock);
			ticketNum--;
			return false;
		}else {
			//卖完
			System.out.println("卖完了");
			return true;
		}
	}
}

3.使用Lock接口进行加锁和释放
lock()加锁方法
unLock()释放锁方法
写法:

 lock();
 try{
	加锁的代码
 }finally{
  unLock()
 	释放锁
 }
//利用接口来保证访问的共享资源,使用Lock接口进行加锁和释放
class TicketRunnable implements Runnable{
	private int ticketNum = 100;
	//声明lock锁
	//参数true可以让线程公平的进入锁
	private final ReentrantLock lock = new ReentrantLock(true);
	public void run() {
		//卖票 循环卖票
		while (true) {
			lock.lock();
			try {
				if (tickets > 0) {
					System.out.println(Thread.currentThread().getName() + "--" + ReentrantLock);
					ReentrantLock--;
				}else {
					break;
				}
			} finally {
				// 释放锁
				lock.unlock();
			}
		}
	}
}

练习:1
公司年会 进入公司有两个门(前门和后门),进门的时候 每位人都能获取一张彩票(7位数),公司有100个员工,利用多线程模拟进门过程,统计每个入口入场的人数 每个人拿到的彩票的号码 要求7位数字 7个数不能重复
打印格式:
编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是: [17, 24, 29, 30, 31, 32, 07]
编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是: [06, 11, 14, 22, 29, 32, 15]
……
从后门 入场的员工总共: 45 位员工
从前门 入场的员工总共: 55 位员工
保证总人数100即可

public class Demo04 {
	public static void main(String[] args) {
		PersonRun personRun = new PersonRun();
		Thread thread1 = new Thread(personRun, "前门");
		Thread thread2 = new Thread(personRun, "后门");
		thread1.start();
		thread2.start();
		
	}
}

class PersonRun implements Runnable{
	//声明总人数
	private int personNum = 100;
	//前门人数
	private int frontNum = 0;
	//后门人数
	private int backNum = 0;
	public void run() {
		while (true) {
			synchronized (this) {
				if (personNum > 0) {
					//还有人
					String name = Thread.currentThread().getName();
					//判断从哪个门进入
					if (name.equals("前门")) {
						frontNum++;
					}else {
						backNum--;
					}
					System.out.println("员工:" + (100 - personNum + 1) +"从"+ name + "入场!  拿到的双色球彩票号码是:" + printNum());
					personNum--;
				}else {
					//没人
					break;
				}
			}
			//让出资源
			Thread.yield();
		}
			System.out.println("从后门 入场的员工总共:"+ backNum +"位员工 ");
			System.out.println("从前门 入场的员工总共:"+ frontNum +"位员工");
	}
	//打印彩票方法
	public ArrayList<Integer> printNum() {
		//创建集合
		ArrayList<Integer> list = new ArrayList<>();
		//循环添加数字
		for (int i = 0; i < 7; i++) {
			int num = (int)(Math.random() * 100 + 1);
			if (!list.contains(num)) {
				list.add(num);
			}else {
				i--;
			}
		}
		return list;
	}
}

4.死锁
1.前提:
1).至少两个线程
2).锁的嵌套(同步代码块的嵌套)
创建死锁:

public class Day23 {
	public static void main(String[] args) {
		TestRunnable runnable = new TestRunnable();
		//创建两个线程
		Thread thread1 = new Thread(runnable);
		Thread thread2 = new Thread(runnable);
		thread1.start();
		thread2.start();
	}
}
//创建锁A
class LockA{
	private LockA() {
		
	}
	public static LockA A = new LockA();
}
//创建锁B
class LockB{
	private LockB() {
		
	}
	public static LockB B = new LockB();
}

class TestRunnable implements Runnable{
	//创建标识,判断哪个线程先进入
	boolean start = false;
	@Override
	public void run() {
		while (true) {
			////让两个线程在A锁和B锁之间循环进出
			if (!start) {
				////从a-b(同步代码块嵌套)
				synchronized (LockA.A) {
					System.out.println("我是先A锁");
					synchronized (LockB.B) {
						System.out.println("我是先B锁");
					}
				}
			}else {
				//从a-b(同步代码块嵌套)
				synchronized (LockB.B) {
					System.out.println("我是后B锁");
					synchronized (LockA.A) {
						System.out.println("我是后A锁");
					}
				}
			}
			//改变标识
			start = !start;
		}
	}
}

二.停止线程
停止线程创建标记进行停止

public class Day23 {
	public static void main(String[] args) throws InterruptedException {
		InterruptRunnable interruptRunnable = new InterruptRunnable();
		Thread thread = new Thread(interruptRunnable);
		thread.start();
		//利用标记停止线程
		interruptRunnable.isStart = true;
		//主线程运行
		Thread.sleep(1000);
		System.out.println("主线程结束");
	}
}

class InterruptRunnable implements Runnable{
	public boolean isStart = false;
	public void run() {
		//使用标记来看循环是否运行,线程停止
		while (!isStart) {
			try {
				//调用sleep中断状态被清除,从休眠状态转为运行状态或受阻塞状态
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "run");
		}
	}
	
}

三.interrupt中断线程
1.interrupt()方法改变的是线程的中断状态,调用此方法默认状态从false->true
注意:当线程中使用Object中的wait()和该类的join()和sleep()方法时,中断状态将被清除并抛出 InterruptedException异常,这时interrupt()的值没有改变

interrupt和sleep(),wait()方法

public class Day23 {
	public static void main(String[] args) throws InterruptedException {
		InterruptRunnable interruptRunnable = new InterruptRunnable();
		Thread thread = new Thread(interruptRunnable);
		thread.start();
		//休眠几秒给子线程运行时间
		Thread.sleep(3000);
		//中断线程
		thread.interrupt();
		//利用标记停止线程
		interruptRunnable.isStart = true;
		System.out.println("调用中断方法");
		//主线程运行
		Thread.sleep(1000);
		System.out.println("主线程结束");
	}
}

class InterruptRunnable implements Runnable{
	public boolean isStart = false;
	public void run() {
		// TODO Auto-generated method stub
		while (!isStart) {
			//线程休眠
			try {
				//使用interrupt方法时调用sleep方法中断状态被清除,子线程会从休眠状态转为运行状态或受阻塞状态
				//同样使用interrupt方法时调用wait()方法,等待状态也会转化为运行状态或受阻塞状态
				//注意:wait方法需要使用锁对象来调用,要先声明锁
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "run");
		}
	}
	
}

2.主线程立即获取子线程的标识状态
使用关键词volatile来标识改变状态的变量,可以让主线程立即接收到改变的值

public class Day23 {
	public static void main(String[] args) {
		ChangeRunnable changeRunnable = new ChangeRunnable();
		Thread thread = new Thread(changeRunnable);
		thread.start();
		//利用线程中的标记卡住主线程
		while (!changeRunnable.isTrue) {
			
		}
		System.out.println("主线程结束");
	}
}
class ChangeRunnable implements Runnable{
	//使用关键词volatile来表示改变状态的变量,可以让主线程立即接收到改变的值
	public volatile boolean isTrue = false;
	int num = 0;//记录循环次数
	public void run() {
		while (!isTrue) {
			num++;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (num == 5) {
				//修改状态
				//当从子线程中修改状态时,主线程不能立即接收到这个状态的改变,使用关键词volatile来表示改变状态的变量,可以让主线程立即接收到改变的值
				isTrue = true;
			}
			System.out.println(Thread.currentThread().getName() + "----" + num);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/l710820742/article/details/82777552
今日推荐