Java多线程入门(二)

会当凌绝顶,一览众山小

| @Author:TTODS

Java多线程入门(二)

线程同步

为什么要线程同步?

使用多线程可以让我们的程序更加充分的利用CPU,提高程序的效率,但是同时也带来了一些问题。多线程在使用线程公共资源的时候往往会遇到问题。
想象一下这样一个情景:一个商店中有三件商品,但是四个顾客同时来购买商品,他们都能买到商品吗?
在这里插入图片描述
我们用代码模拟一下:

public class Test{
       public static void main(String[] args) {
             //创建一个商店
             Store store = new Store();
             //创建四个共用一个商店的顾客线程
             CustomerThread ct1 = new CustomerThread("顾客1",store);
             CustomerThread ct2 = new CustomerThread("顾客2",store);
             CustomerThread ct3 = new CustomerThread("顾客3",store);
             CustomerThread ct4 = new CustomerThread("顾客4",store);
             //启动四个线程
             ct1.start();
             ct2.start();
             ct3.start();
             ct4.start();
       }
} 
//商店类
class Store {
       //剩余商品数量
       static int goodsNumble = 3;
       public Store(){
       }
       //出售一件商品
 void sell() {
        //先判断商品是否售完
             if(goodsNumble<=0)
                    System.out.println("商店:出售失败,商品卖完了");
             else {
                    System.out.println("此时还有"+goodsNumble+"件商品");
                    goodsNumble--;
                    System.out.println("售出一件商品,商品数量减1");
                    }
       }
}
class CustomerThread extends Thread{
       private Store store;
       public CustomerThread(String name,Store store) {
             // TODO 自动生成的构造函数存根
             setName(name);
             this.store = store;
       }
       @Override
       public void run() {
             buy();
       }
       //购买商品,调用store的sell方法
       public void buy() {
             store.sell();
       }
}

运行结果
在这里插入图片描述

从上面的代码和运行结果我们可以看到,因为顾客几乎是同时到达商店的,所以他们到的时候商品的水量都是3,于是商店给每一位顾客都出售了一件商品。这显然不是我们想要的结果。然后我们给Store类的sell()方法加上synchronized关键字,局部修改如下:

 synchronized void sell() {
        //先判断商品是否售完
             if(goodsNumble<=0)
                    System.out.println("商店:出售失败,商品卖完了");
             else {
                    System.out.println("此时还有"+goodsNumble+"件商品");
                    goodsNumble--;
                    System.out.println("售出一件商品,商品数量减1");
                    }
       }

运行结果
在这里插入图片描述

可以看到在我们给sell方法使用了synchronized关键字后Stroe会按照一定的顺序来为顾客服务,这就是synchronized方法的效果.当当前线程使用synchronized方法时,会给当前对象(store)上锁,上锁后其他线程在使用相同对象的方法时进入阻塞,需等待此线程中的该方法结束,才能继续运行。

线程同步的三个方法

为了介绍线程同步的三个方法,我们先来看一下这样一个场景:有一个房间,房间里居住着四个人,四个人共用着房间里的一部电话。每个人都可以在任何时候使用电话,但是如果他们在同一时间都来使用这部电话又会发生什么呢?
在这里插入图片描述
我们用代码模拟一下:

public class SynchronizedMethod {
       public static void main(String[] args) {
             Room r = new Room();
             //创建并启动4个学生线程
             StudentThread s1  =new StudentThread("s1",r);
             StudentThread s2  =new StudentThread("s2",r);
             StudentThread s3  =new StudentThread("s3",r);
             StudentThread s4  =new StudentThread("s4",r);
             s1.start();
             s2.start();
             s3.start();
             s4.start();
       }
}

class Room {
       public Phone phone;
       public Room() {
             // TODO 自动生成的构造函数存根
             phone = new Phone();
       }       
}

class Phone{
       static final int FREE = 1,BUSY = 0;
       //电话的状态
       private int status=FREE;
       public Phone() {
       
       }
       //判断电话是否空闲
       public boolean  isFree() {
             return status==FREE;
       }
       //打电话,半秒后挂断,为了打印电话使用者的名字,我们传入一个参数
       public void call(String user) {
             if(!isFree()) {
                    System.out.println("有人正在使用电话哦");
                    return;
             }
             status = BUSY;
             System.out.println(user+"打出了电话...");
             try {
                    Thread.sleep(500);
                    hangUp(user);
             } catch (InterruptedException e) {
                    // TODO 自动生成的 catch 块
                    e.printStackTrace();
             }
       }
       //挂电话
       public  void hangUp(String user) {
             status = Phone.FREE;
             System.out.println(user+"挂断了电话...");
       }
}

class StudentThread extends Thread{
       private Room room;
       public StudentThread(String name,Room room) {
             setName(name);
             this.room = room;
       }
       @Override
       public void run() {
                    this.room.phone.call(getName());
       }
}

运行结果
在这里插入图片描述

我们可以发现当线程s1使用了对象phonecall方法的时候,其他三个线程仍然使用了call方法。这就是线程不同步的效果。接下来我们分别用三种方法实现线程同步。

  • synchronized方法
    Phone类的call声明为sychronized方法(在call方法前面加上关键字即可),修改部分代码如下:
       public synchronized void call(String user) {
             if(!isFree()) {
                    System.out.println("有人正在使用电话哦");
                    return;
             }
             status = BUSY;
             System.out.println(user+"打出了电话...");
             try {
                    Thread.sleep(500);
                    hangUp(user);
             } catch (InterruptedException e) {
                    // TODO 自动生成的 catch 块
                    e.printStackTrace();
             }
       }

运行结果
在这里插入图片描述

  • synchronized
    call函数内添加synchronized代码块,此方法与上一方法相比,优点在于你不需将整个方法都变成同步的,只需把关键代码块变为同步即可,修改部分代码如下:
public  void call(String user) {
             synchronized (this) {
                    if(!isFree()) {
                           System.out.println("有人正在使用电话哦");
                           return;
                    }
                    status = BUSY;
                    System.out.println(user+"打出了电话...");
                    try {
                           Thread.sleep(500);
                           hangUp(user);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
             }
       }

运行结果
在这里插入图片描述

  • Lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Phone{
       private Lock lock= new ReentrantLock();
       static final int FREE = 1,BUSY = 0;
       //电话的状态
       private int status=FREE;
       public Phone() {
             
       }
       //判断电话是否空闲
       public boolean  isFree() {
             return status==FREE;
       }
       //打电话,半秒后挂断,为了打印电话使用者的名字,我们传入一个参数
       public  void call(String user) {
             //上锁
             lock.lock();
                    if(!isFree()) {
                           System.out.println("有人正在使用电话哦");
                           return;
                    }
                    status = BUSY;
                    System.out.println(user+"打出了电话...");
                    try {
                           Thread.sleep(500);
                           hangUp(user);
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                    }
                    //释放锁
                    lock.unlock();
       }
       //挂电话
       public  void hangUp(String user) {
             status = Phone.FREE;
             System.out.println(user+"挂断了电话...");
       }
}

运行结果
在这里插入图片描述

线程死锁

线程死锁,是指多个线程在使用公共资源时各占了资源的一部分,导致每个线程都等待其他线程释放资源才能完成工作,最终导致每个线程都无法完成工作,互相卡死。
想象一下这样的情景:上学时,你和你的同桌共用一个笔记本和一支钢笔,只是你的老师讲到了一个要点,于是你顺势抢到了钢笔,而你的同桌拿到了笔记本,谁也不肯让步,于是谁也无法完成笔记。
我们用代码模拟一下:

public class DeadLock {
       public static void main(String[] args) {
             Stationery stationery = new Stationery();
             //p1 takeNotes时先获取并锁定本子,然后再试图获取笔
             Person p1 = new Person("p1",stationery) {
                    @Override
                    public void takeNotes() {
                           synchronized (stationery.notebook) {
                                 getNotebook();
                                 synchronized (stationery.pen) {
                                        getPen();
                                 }
                           }
                    }
             };

             //p2 takeNotes时先获取并锁定笔,然后再试图获取本子
             Person p2 = new Person("p2",stationery) {
                    @Override
                    public void takeNotes() {
                           synchronized (stationery.pen) {
                                 getPen();
                                 synchronized (stationery.notebook) {
                                        getNotebook();
                                 }
                           }
                    }
             };
             p1.start();
             p2.start();
       }
}

//文具类(共用资源)
class Stationery{
       //笔
       public static Pen pen = new Pen();
       //笔记本
       public static Notebook notebook= new Notebook();
}

class Pen{
       
}

class Notebook{

}
abstract class Person extends Thread{
       Stationery stationery;
       public Person(String name,Stationery s) {
             // TODO 自动生成的构造函数存根
             setName(name);
             stationery = s;
       }
       //获取笔
       Pen getPen() {
                    System.out.println(getName()+":我拿打笔了");
                    try {
                           Thread.sleep(1);
                    } catch (InterruptedException e) {
                           // TODO 自动生成的 catch 块
                           e.printStackTrace();
                    }
             return stationery.pen;
       };
       //获取本子
       Notebook getNotebook() {
                    System.out.println(getName()+":我拿打笔记本了");
                    try {
                           Thread.sleep(1);
                    } catch (InterruptedException e) {
                           // TODO 自动生成的 catch 块
                           e.printStackTrace();
                    }
             return stationery.notebook;
       };
       //抽象方法 
       public abstract void takeNotes();
       @Override
       public  void run() {
             takeNotes();
       };
}

运行结果(程序并没有结束而是一直卡住无法往下运行)
在这里插入图片描述

线程的生命周期

在这里插入图片描述

  • 新建态 :Thread t = new Thread(),线程对象被创建,此时还没有使用start()方法,启动线程
  • 就绪状态 :t.start()后进入就绪状态,但具体什么时候运行取决于CPU的调度
  • 运行态:线程占用CPU运行
  • 阻塞态:线程在运行时,使用了Thread.sleep()或者进行I/O操作等不在占用CPU,则由运行态进入阻塞态,待事件结束再进入就绪态
  • t.stop()线程结束,进入终止态(消亡状态)

- THE END -
  • 3
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值