前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java基础(十一):多线程

java基础(十一):多线程

作者头像
Vincent-yuan
发布2020-06-16 15:49:56
3750
发布2020-06-16 15:49:56
举报
文章被收录于专栏:Vincent-yuan

目录

线程基础内容

  • 程序、进程与线程
  • 线程的创建和启动
  • 线程的生命周期
  • 线程控制

线程同步

  • 线程同步的必要性
  • 线程同步的实现
  • 死锁

线程间通信

  • 线程间通信的必要性
  • 线程间通信的实现
1.程序、进程与线程

程序Program 程序是一段静态的代码,它是应用程序执行的蓝本

进程Process 进程是指一种正在运行的程序,有自己的地址空间

进程的特点

  • 动态性
  • 并发性
  • 独立性

线程Thread

  • 进程内部的一个执行单元,它是程序中一个单一的顺序控制流程。
  • 线程又被称为轻量级进程(lightweight process)
  • 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程

线程特点

  • 轻量级进程
  • 独立调度的基本单位
  • 可并发执行
  • 共享进程资源

线程和进程的区别

2.线程的创建和启动

线程的创建

  • 方式1:继承Java.lang.Thread类,并覆盖run() 方法
  • 方式2:实现Java.lang.Runnable接口,并实现run() 方法
  • 方法run( )称为线程体。

线程的启动

  • 新建的线程不会自动开始运行,必须通过start( )方法启动
  • 不能直接调用run()来启动线程,这样run()将作为一个普通方法立即执行,执行完毕前其他线程无法执行
  • Java程序启动时,会立刻创建主线程,main就是在这个线程上运行。当不再产生新线程时,程序是单线程的

两种线程创建方式的比较

继承Thread类方式的多线程

  • 优势:编写简单
  • 劣势:无法继承其它父类

实现Runnable接口方式的多线程

  • 优势:可以继承其它类,多线程可共享同一个Runnable对象
  • 劣势:编程方式稍微复杂,如果需要访问当前线程,需要调用Thread.currentThread()方法

实现Runnable接口方式要通用一些。

Thread类常用方法

3.线程的生命周期

新生状态:

  • 用new关键字建立一个线程对象后,该线程对象就处于新生状态。
  • 处于新生状态的线程有自己的内存空间,通过调用start进入就绪状态

就绪状态:

  • 处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU
  • 当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称之为“cpu调度”。

运行状态:

  • 在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任务而死亡。
  • 如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。

阻塞状态:

  • 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
  • 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。

死亡状态:

  • 死亡状态是线程生命周期中的最后一个阶段。
  • 线程死亡的原因有三个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过执行stop方法来终止一个线程[不推荐使用】,三是线程抛出未捕获的异常
4.线程控制方法

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。

线程调度器按照线程的优先级决定应调度哪个线程来执行。

线程的优先级用数字表示,范围从1到10

  • Thread.MIN_PRIORITY = 1
  • Thread.MAX_PRIORITY = 10
  • Thread.NORM_PRIORITY = 5

使用下述方法获得或设置线程对象的优先级。

  • int getPriority();
  • void setPriority(int newPriority);

注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。

Join ():

  • 阻塞指定线程等到另一个线程完成以后再继续执行

Sleep () :

  • 使线程停止运行一段时间,将处于阻塞状态
  • 如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行!

yield ()

  • 让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态
  • 如果调用了yield方法之后,没有其他等待执行的线程,这个时候当前线程就会马上恢复执行!

setDaemon()

  • 可以将指定的线程设置成后台线程
  • 创建后台线程的线程结束时,后台线程也随之消亡
  • 只能在线程启动之前把它设为后台线程

stop()

  • 结束线程,不推荐使用
5.线程同步

当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全

线程同步:当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用

线程同步的实现方案

  • 同步代码块 synchronized (obj){ }
  • 同步方法 private synchronized void makeWithdrawal(int amt) {}

同步监视器

  • synchronized (obj){ }中的obj称为同步监视器
  • 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this,也就是该对象本身

同步监视器的执行过程

  • 第一个线程访问,锁定同步监视器,执行其中代码
  • 第二个线程访问,发现同步监视器被锁定,无法访问
  • 第一个线程访问完毕,解锁同步监视器
  • 第二个线程访问,发现同步监视器未锁,锁定并访问

线程同步的好处 : 解决了线程安全问题

线程同步的缺点

  • 性能下降
  • 会带来死锁

死锁

  • 当两个线程相互等待对方释放“锁”时就会发生死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
  • 多线程编程时应该注意避免死锁的发生
6.线程通信

在生产者消费者问题中,仅有synchronized是不够的

  • synchronized可阻止并发更新同一个共享资源,实现了同步
  • synchronized不能用来实现不同线程之间的消息传递(通信)

Java提供了3个方法解决线程之间的通信问题

均是java.lang.Object类的方法 都只能在同步方法或者同步代码块中使用,否则会抛出异常

生产者消费者的实现思路

  • 定义产品类
  • 定义消费者线程
  • 定义生产者线程
  • 测试运行

产品类

代码语言:javascript
复制
//产品类
public class Product {
    private String name;//馒头 玉米饼
    private String color;//白色 黄色
    private boolean isProduce = false;//是否生产产品
    public synchronized void get(){
        //如果没有生产,等待
        if(isProduce == false)
        {
            wait();
        }
    
        System.out.println(“消费者消费:”+name+“ ”+color); //消费产品
        isProduce = false; //修改状态:没有生产
        notify();//通知生产者生产
    }
    public synchronized void put(String name,String color){
        //如果已经生产,等待
        if(isProduce == true){    
            wait();//生产产品
        }
        this.name = name;
        this.color = color;
        System.out.println("生产者生产:"+this.name+" "+this.color);
        isProduce = true; //修改状态:已经生产
        notify(); //通知消费者消费
    }
}

消费者线程

代码语言:javascript
复制
//消费者线程
public class Consumer implements Runnable{
    private Product product;
    public Consumer() {
        super();
    }
    public Consumer(Product product) {
        super();
        this.product = product;
    }
    public void run() {
        while(true){
            product.get();
        }
    }
}

生产者线程

代码语言:javascript
复制
//生产者线程
public class Producer implements Runnable {
    private Product product;
    public Producer() {  }
    public Producer(Product product) {
        this.product = product;
    }
    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                product.put("馒头", "白色");
            } else {
                product.put("玉米饼", "黄色");
            }
            i++;
        }
    }
}

测试类

代码语言:javascript
复制
//测试类
public class TestCommunication {
    public static void main(String[] args) {
        //创建产品类(生产者和消费者操作的是同一个产品)
        Product product = new Product();
        //创建两个线程
        Consumer c = new Consumer(product);
        Thread t1 = new Thread(c);
        Producer p = new Producer(product);
        Thread t2 = new Thread(p);
        //启动两个线程
        t1.start();
        t2.start();
    }
}

补充:

为什么通信?

不通信就无法实现生产和消费的交替进行

如何通信:wait 等待 notify唤醒 notifyAll 唤醒所有的阻塞线程

代码示例:

代码语言:javascript
复制
/**
 * 测试两种实现线程方式的区别
 *         区别
 * 
 * @author Terry
 *
 */
public class ThreadDemo3 {

    /**
     * @param args
     */
    public static void main(String[] args) {
        
        //new了两个线程对象——s1和s2
        //其中两个对象各对应一个内存区域。线程运行过程中运行都是自己内存块中的数据        
        Shop1 s1 = new Shop1("小武");
        s1.start();
        
        Shop1 s2 = new Shop1("小潘");
        s2.start();
        
        /*
        //实例化了两个线程对象,所以分配了两块内存空间
        //执行过程中操作的是自己的内存空间
        Shop2 s3 = new Shop2("小武");
        s3.run();    
        
        Shop2 s4 = new Shop2("小潘");
        s4.run();
        
        
        //实际实例化了两个线程对象
        //所以同样分配两个内存空间
        Thread t1 = new Thread(new Shop2("小武"));
        t1.start();
        
        Thread t2 = new Thread(new Shop2("小潘"));
        t2.start();
        
        
        //创建了两个线程对象,但是使用的是同一个对象——s5        
        Shop2 s5 = new Shop2("w");
        Thread t3 = new Thread(s5);
        t3.start();
        
        Thread t4 =new Thread(s5);
        t4.start();
        */
        
    
        

        
    }
}

/**
 * 因为业务的拓展,现在可以实现多窗口的出售
 *         要求:每天只卖10个
 * @author Terry
 *
 */
class Shop1 extends Thread{
    //private int count = 10;
    //使用静态变量可以有效的实现资源共享(因为在内存中只有一份count)
    private static int count = 10;
    public Shop1(String name) {
        super(name);
    }
    public void run(){
        //判断是否已经卖完
        while(count>0){
            count--;
            System.out.println(this.getName() +"卖出了一个烧饼" + ",现在剩余" + count);
        }
    }
}

/**
 * 使用接口实现上面的代码
 * @author Terry
 *
 */
class Shop2 implements Runnable{
    //私有变量,存储剩余烧饼的个数
    private int count = 10;
    //存储当前人的姓名
    private String name="";
    
    public Shop2(String name) {
        this.name  = name;
    }
    
    /**
     * 实现销售的方法
     */
    public void run(){
        //判断是否已经卖完
        while(count>0){
            count--;
            System.out.println(Thread.currentThread().getId() + "、" + this.name +"卖出了一个烧饼" + ",现在剩余" + count);
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.程序、进程与线程
  • 2.线程的创建和启动
  • 4.线程控制方法
  • 5.线程同步
  • 6.线程通信
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档