一.多线程中的锁
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);
}
}
}