多线程学习-第一节

一、线程简介

  1. 程序:为完成特定任务,有某种语言编写的一组指令集和。即指一段静态的代码,静态对象。
  2. 进程:是程序执行一次的过程,或正在运行中的程序。是一个动态的过程,有它自身的产生、存在和消亡的过程。
    程序是静态的,进程是动态的
    进程作为资源分配的单位,系统在运行时为每个进程分配不同的内存区域
  3. 线程:进程可以进一步细分为线程,是一个程序内部的一条执行路径
    (1)一个进程同时并行执行多个线程,就是支持多线程
    (2)线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
    (3)一个进程中的多个线程共享相同的内存单元和内存地址空间-------->它们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间的通信更简便、高效,但多个线程操作共享的系统资源可能会带来安全隐患。
    (4)线程开启不一定立即执行,由CPU调度执行
    (5)线程创建四种方式,解决线程安全有三种方式
    (6)一个java程序至少 有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程,发生异常 会影响主线程
  4. 线程分类
    (1)守护线程
    (2)用户线程

二、线程创建

1.继承Thread类(重点)

(1)继承thread类

package thread.demo;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/07/22 14:12
 */
public class ExtendsThread extends Thread{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}

(2)创建线程

package thread.demo;

import java.util.concurrent.*;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/07/22 14:19
 */
public class TicketTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //继承Thread方式卖票
        ExtendsThread t1 = new ExtendsThread();
        ExtendsThread t2 = new ExtendsThread();
        ExtendsThread t3 = new ExtendsThread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

2.实现Runnable接口(重点)

(1)实现Runnable接口

package thread.demo;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/07/22 14:18
 */
public class ImplementsRunnable implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}

(2)创建线程

package thread.demo;

import java.util.concurrent.*;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/07/22 14:19
 */
public class TicketTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //实现Runnable方式
        ImplementsRunnable run = new ImplementsRunnable();

        Thread t4 = new Thread(run);
        Thread t5 = new Thread(run);
        Thread t6 = new Thread(run);

        t4.setName("窗口4");
        t5.setName("窗口5");
        t6.setName("窗口6");

        t4.start();
        t5.start();
        t6.start();

    }
}

3.实现Callable接口

(1)实现Callable接口

package thread.demo;

import java.util.concurrent.Callable;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/11/04 14:42
 */
public class ImplementsCallable implements Callable<String> {
    private static int ticket = 100;

    @Override
    public String call() throws Exception {
        while (true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticket);
                ticket--;
            } else {
                break;
            }
        }
        return "成功了";
    }
}

(2)创建线程

package thread.demo;

import java.util.concurrent.*;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/07/22 14:19
 */
public class TicketTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //实现Callable接口
        ImplementsCallable callable = new ImplementsCallable();

        FutureTask<String> task1 = new FutureTask<>(callable);
        FutureTask<String> task2 = new FutureTask<>(callable);
        FutureTask<String> task3 = new FutureTask<>(callable);

        Thread t7 = new Thread(task1);
        Thread t8 = new Thread(task2);
        Thread t9 = new Thread(task3);

        t7.setName("窗口7");
        t8.setName("窗口8");
        t9.setName("窗口9");

        t7.start();
        t8.start();
        t9.start();

    }
}

4.使用线程池

三、线程的状态

  1. 新建
    当一个Thread类或其子类被声明并创建,新的线程对象处于新建状态

  2. 就绪
    新建的线程调用start()方法之后,将进入线程队列等待CPU时间片,此时已具备运行条件,只是没有分配到CPU资源:

     sleep()时间结束
    
     jion()线程结束
    
     获取同步锁
    
     notify()/notifyAll()
    
  3. 运行
    当就绪的线程被调度并获取到CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能

  4. 阻塞
    在某种特殊情况下,线程被认为挂起或执行输入输出操作时,让出CPU并临时终止执行,进入阻塞状态:

     sleep()
    
     join()
    
     等待同步锁
    
     wait()
    
  5. 死亡
    线程完成了它的全部工作或线程被提前强制性的终止(调用stop()方法)或出现异常导致结束

五、线程调度策略

  1. 时间片:同优先级先进先出队列,使用时间片策略
  2. 抢占式:对比高优先级,高优先级的线程抢占CPU,使用抢占式策略
  3. 涉及方法:
    (1)getPriority():获取线程优先级
    (2)setPriority():设置线程优先级

六、线程安全问题

1.同步机制synchronized

(1)同步代码块

package thread.unsafe;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/11/05 12:13
 */
public class UnSafeBank {
    public static void main(String[] args) {
        Account account = new Account(1000,"结婚基金");

        Drawing you = new Drawing(account,50,"老公");
        Drawing girlFriend = new Drawing(account,100,"老婆");

        you.start();
        girlFriend.start();


    }
}

class Account{
    int money;//账户余额
    String name;//账户名称

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

class Drawing extends Thread{
    Account account;

    int drawingMoney;//取多少钱

    int nowMoney;//取钱的人

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run(){
        synchronized (account) {
            if (account.money - drawingMoney < 0) {
                System.out.println(this.getName() + "取钱时,账户余额不足!");
                return;
            }
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额
            account.money = account.money - drawingMoney;
            //手里的前
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name + "余额为:" + account.money);
            System.out.println(this.getName() + "手里的钱:" + nowMoney);
        }
    }
}

(2)同步方法

package thread.unsafe;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/11/05 11:50
 */
public class SellTicket implements Runnable{
    private static int ticketNum = 10;

    private Boolean flag = true;

    @Override
    public void run() {
        while (flag){
            buy();
        }
    }

    public synchronized void buy(){
        if (ticketNum > 0) {
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + ticketNum);
            ticketNum--;
        } else {
            this.stop();
        }
    }

    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        SellTicket sellTicket = new SellTicket();
        new Thread(sellTicket,"窗口1").start();
        new Thread(sellTicket,"窗口2").start();
        new Thread(sellTicket,"窗口3").start();
    }
}

2.产生死锁的四个必要条件

(1)互斥条件:一个资源每次只能被一个进程使用
(2)一个进程因请求资源而阻塞时,保持的资源不放
(3)不剥夺条件:进程已获得的资源,未使用完之前,不可剥夺
(4)循环等待条件:若干进程之间形成头尾相接的循环等待资源关系

3.Lock锁

(1)ReenTrantLock:可重入锁

package thread.lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 概述:
 * 作者:zhujie
 * 创建时间:2021/11/05 13:27
 */
public class TestLock {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket, "窗口1").start();
        new Thread(buyTicket, "窗口2").start();
        new Thread(buyTicket, "窗口3").start();

    }
}

class BuyTicket implements Runnable {
    private static int ticket = 10000;

    private Boolean flag = true;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        buyTicket();
    }

    public void buyTicket() {

        while (flag) {
            try {
                lock.lock();
                if (ticket < 1) {
                    flag = false;
                    return;
                }
                System.out.println(Thread.currentThread().getName() + ":售出票号" + ticket);
                ticket--;
            }finally {
                lock.unlock();
            }

        }
    }
}

4.使用顺序

Lock锁----->同步代码块----->同步方法

七、线程通讯

  1. wait():一旦执行此方法,线程进入阻塞状态,并释放锁(同步监视器)
  2. notify():一旦执行此方法,会唤醒执行了wait的一个线程,如果存在多个,则唤醒优先级高的
  3. notifyAll():一旦执行此方法,会唤醒所有执行了wait的线程
  4. wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中
  5. wait()、notify()、notifyAll()三个方法调用者必须是同步代码块或同步方法中的同步监视器
  6. 生产者消费者模式–>管程法:利用缓冲区
package thread.communication;

/**
 * 概述:生产者消费者模型-->管程法:缓冲区
 * 生产者、消费者、产品、缓冲区
 * 作者:zhujie
 * 创建时间:2021/11/05 14:47
 */
public class TestProducerConsumer {
    public static void main(String[] args) {
        //创建缓冲区对象
        Buffer buffer = new Buffer();
        //创建生产者线程
        Producer producer = new Producer(buffer);
        producer.setName("生产者");
        producer.start();
        //创建消费者线程
        Consumer consumer = new Consumer(buffer);
        consumer.setName("消费者");
        consumer.start();
    }

}

//生产者
class Producer extends Thread{
    Buffer buffer;
    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("生产了"+i+"个产品");
            buffer.add(new Product(i));
        }
    }
}

//消费者
class Consumer extends Thread{
    Buffer buffer;
    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("消费了第"+buffer.consumption().id+"个产品");
        }
    }
}

//产品
class Product{
    int id;
    public Product(int id) {
        this.id = id;
    }
}

//缓冲区
class Buffer{
    //设置产品缓冲区容器大小
    Product[] products = new Product[10];
    //缓冲区容器计数器
    int count = 0;

    //生产者放入产品方法
    public synchronized void add(Product product){
        //判断缓冲区容器是否已满,满了通知消费者消费
        if (count == products.length){
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //容器未满,添加进容器
        products[count] = product;
        count ++;
        //可以通知消费者消费
        this.notifyAll();
    }

    //消费者消费产品方法
    public synchronized Product consumption(){
        //判断缓冲区容器是否存在产品
        if (count == 0){
            //等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //消费
        count --;
        Product product = products[count];

        //通知生产者生产
        this.notifyAll();

        return product;
    }
}

  1. 生产者消费者模式–>信号灯发:利用标识位
package thread.communication;

/**
 * 概述:生产者消费者模型-->信号灯发:标志位
 * 作者:zhujie
 * 创建时间:2021/11/05 15:42
 */
public class TestProducerConsumer2 {
    public static void main(String[] args) {
        Programme programme = new Programme();

        new actor(programme).start();
        new audience(programme).start();
    }

}

//生产者--演员
class actor extends Thread{
    Programme programme;

    public actor(Programme programme) {
        this.programme = programme;
    }

    //录制节目

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2 == 0){
                programme.play("快乐大本营");
            }else {
                programme.play("广告");
            }
        }
    }
}

//消费者--观众
class audience extends Thread{
    Programme programme;

    public audience(Programme programme) {
        this.programme = programme;
    }

    //观看节目

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            programme.watch();
        }
    }
}

//产品--节目
class Programme{

    //节目
    String programme;
    //标识
    //演员录制节目,观众等待T
    //观众观看节目,演员等待F
    boolean flag = true;

    //演员录制节目
    public synchronized void play(String name){

        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //录制节目
        System.out.println("演员:录制了"+name);
        //通知观众观看节目
        this.notifyAll();

        this.programme = name;
        this.flag = !this.flag;
    }

    //观众观看节目
    public synchronized void watch(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("观众:观看了"+this.programme);
        //通知演员录制节目
        this.notifyAll();
        this.flag = !this.flag;
    }
}

八、sleep和wait的区别

  1. 相同点:
    (1)都可使当前线程进入阻塞状态
  2. 不同点
    (1)声明位置不同:sleep在Thread中声明,wait在Object类中声明
    (2)调用要求不同:sleep可以在任何需要的地方调用,而wait只能在同步代码块或同步方法中调用
    (3)都在同步代码块或同步方法中调用时,sleep不会释放锁(同步监视器),wait会释放锁(同步监视器)
    (4)sleep在时间结束后自动唤醒,而wait需要调用notify方法唤醒

九、线程池

1.ExecutorService

  1. 真正的线程池接口,常见子类ThreadPoolExecutore
  2. void execute(Runnable command):执行任务,没有返回值,一般用来执行Runnable
  3. Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
  4. void shutdown():关闭连接池

2.Executors

  1. 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
  2. Executors.newCachedThreadPool():创建一个可根据需要创建新的线程的线程池
  3. Executors.newFixedThreadPool():创建一个可重用固定线程数的线程池
  4. Executors.newSingleThreadPool():创建一个只有一个线程的线程池
  5. Executors.newScheduledThreadPool():创建一个线程池,它可以安排在给定延迟后运行命令或定期执行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值