多线程概述

并行和并发

1. 需求:边打英雄联盟和边听音乐

2. 问题:只能先后关系,并不能同时发生

  • 多进程或者多线程来解决

3. 并行和并发:

  • 并行:多件事情在同一时刻发生

  • 并发:多件事情在同一时间段发生,同一时间段多个程序同时运行

4. 单个CPU:一个时间点只能执行一个程序,多个程序可以交替执行。

5. 线程调度:多个线程以某个顺序执行

进程与线程

1. 进程:内存中的应用程序,每个进程有一块独立的内存空间,一个应用程序可以同时启用多个进程,有独立内存空间,至少有一个线程

2. 线程:进程中的执行任务单元,一个进程里面可以拥有多个线程,栈空间独立,堆内存共享

3. 多线程的优势:因为堆内存是共享的,所以内存的开销是不是较少。

4. 多线程具体运行那个线程,取决于 CPU调度

5. java 程序中最少有几个线程:

  • 主线程

  • GC垃圾回收线程

6. 线程的调度:JVM 采用抢占式的调度方式,没有采用分时概念,谁抢到谁执行。

7. 多线程的应用:

  • 抢票

  • 游戏

  • 多线程下载

8. 宽带带宽:以位(bit)计算的,下载速度以字节(byte),1字节=8位

  • 1024KB/s 代表 1M,下载速度,1024/8 128KB/s

多线程实现方式

1. 继承 Thread 类

  • 重写 run 方法
public class MusicDemo extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("听音乐:"+i);
        }
    }

}
  • 启动线程,调用 start 方法  
MusicDemo musicDemo = new MusicDemo();
musicDemo.start();
  • 注意:不要去调用 run() 方法

2. 实现 Runnable 接口

  • 实现Runnable 接口,实现 Run 方法
public class PlayGame implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("打游戏:" + i);
        }
    }
}
  • 启动(先创建对象,再创建 Thread 对象通过构造器获得线程对象)  
PlayGame playGame = new PlayGame();
Thread thread = new Thread(playGame);
thread.start();

3. 使用匿名内部类来创建线程(只适用于只使用一次的方式)  

new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("统计人头数:" + i);
                }
            }
        }).start();

线程的调度

1. 线程控制操作:Thread.sleep(1000L);

2.线程合并(联合线程):

  • join方法(操作线程先执行完之后,等待线程才执行)

 3. 守护线程:GC 使用

  • 目的:为其它线程服务的—GC—

  • 特点:主线程结束,守护线程就结束

  • 创建线程:默认是前台线程

  • 获取守护线程thread.setDaemon(true),必须再 start() 方法之前

4. 获取当前线程及其名称

  • Thread t = Thread.currentThread();

  • t.getName();

5. 线程设置优先级

  • 注意:优先级高,只不过是获取执行机会更大(取决于CPU调度)
/**
 * The minimum priority that a thread can have.
 */
public final static int MIN_PRIORITY = 1;

/**
 * The default priority that is assigned to a thread.
 */
public final static int NORM_PRIORITY = 5;

/**
 * The maximum priority that a thread can have.
 */
public final static int MAX_PRIORITY = 10;

6. 线程的让位:  

  • yield 方法:当前线程对象去提示CPU调度器自己愿意让出CPU资源(但是调度器可以忽略你的请求)

  • sleep 和 yield 方法的区别

    • 都能使当前处于运行状态的线程暂时放弃 cpu 把机会给其它线程

    • sleep 方法给其它线程运行机会,但是不考虑其它线程的优先级,yield 只会给相同优先级,或者更高优先级的线程运行机会

    • 调用 sleep 方法之后,线程进入计时等待状态,yield 进入就绪状态

线程同步

1. 需求:猴子(3个 A B C)吃香蕉30个,使用多线程的方式实现  

  • 继承 Thread 类的方式

    • 同一个编号的香蕉被多个猴子吃

public class MonkeyDemo extends Thread {
    private int num = 30;//香蕉总数
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            if (num > 0) {
                System.out.println(super.getName() + "吃了编号为:" + num-- + "的香蕉");
            }
        }
    }
}


MonkeyDemo monkeyA = new MonkeyDemo();
monkeyA.setName("A");

MonkeyDemo monkeyB = new MonkeyDemo();
monkeyB.setName("B");

MonkeyDemo monkeyC = new MonkeyDemo();
monkeyC.setName("C");

monkeyA.start();
monkeyB.start();
monkeyC.start();}}
  • 实现 Runnable 接口

    • 是否可以共享同一个资源的原因,实现方式可以做到(当有网络延迟的时候,也不可以做到)

public class MonkeyRunnable implements Runnable{

    private int num = 30;
    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "吃了编号为:" + num-- + "的香蕉");
            }
        }
    }
}

MonkeyRunnable runnable = new MonkeyRunnable();

new Thread(runnable,"A").start();
new Thread(runnable,"B").start();
new Thread(runnable,"C").start();

 2. 解决多个猴子不能同时吃到同一个香蕉的案例

  • 同步代码块

    • 语法

    • 非静态

    • 同步锁是谁?

      • 非静态的来说,同步锁就是 this

      • 静态的来说,类的字节对象(MonkeyRunnable.class)

synchronized(同步锁){
}
    • 任何时候,只能运行一个线程拥有同步锁,谁拿到谁执行,其它的等待

    • 不要使用同步锁去修饰 run 方法,修饰之后,某一个线程执行完了其它的才可以执行

    • 为了保证每个线程都能够正常执行,原子操作,一般的,把当前并发访问的共同资源作为同步监听对象

  •  同步方法
synchronized public void eat() {
    //业务逻辑
}
  • 锁机制(Lock):  
Lock lock = new ReentrantLock();
//上锁
lock.lock();
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "吃了编号为:" + num-- + "的香蕉");
        }
//解锁
lock.unlock();

3. 同步代码块,同步方法,同步锁如何选用?

  • 尽量减少锁的作用域(Synchronized)

  • 建议使用锁机制(很好的去控制锁的作用范围,性能更高)

生产者与消费者模式

1. 线程通信:不同线程执行不同任务,如果这些任务存在关系,线程之间必须能够通信,协调完成工作

2. 需求:实现生产者与消费者模式

  • 通过生产者与消费者操作共同资源

  • 使用多个线程来做生产者(Producer)

  • 使用多个线程来做消费者(Consumer)

3. 生产者以及消费者示意图:(体现面向对象设计原则:低耦合)

  •  主板与集成显卡(高耦合)
//主板
public class Producer{
    private Consumer cu;
}

//集成显卡
public class Consumer{
    private Producer p;
}
  •  板和内存条(方便升级)(低耦合)
//主板
public class Producer{
    //资源
    private ShareResource resource;
}

//内存
public class Consumer{
    //资源
     private ShareResource resource;
}

//内存卡槽
public class ShareResource{
    
}

实现生产者与消费者

public class Producer implements Runnable {

    private ShareResource resource;

    public Producer(ShareResource resource){
        this.resource = resource;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i % 2 == 0){
                resource.push("女士服装","s");
            }else{
                resource.push("男士服装","XXXL");
            }
        }
    }
}
public class Consumer implements Runnable{
    private ShareResource resource;

    public Consumer(ShareResource resource){
        this.resource = resource;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            resource.pop();
        }
    }
}
public class ShareResource {

    private String name;

    private String size;

    /**
     * 给生产者推送数据的
     *
     * @param name
     * @param size
     */
    public void push(String name, String size) {
        this.name = name;
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.size = size;
    }

    public void pop() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("服装名称:" + this.name + " 尺码: " + this.size);
    }
}

生产者与消费者出现数据紊乱的问题

 1. 问题1:出现数据紊乱问题

  • 解决方案:只要保证生产名字和尺码的过程中保持同步,中间不被消费者消费

    • 同步方法/同步代码块/锁来保持同步性

2. 问题2:应该生产一个数据消费一个数据

  • 应该交替出现,男士--XXL 女士--s 男士--xxl 女士--s

    • 使用等待唤醒机制

线程通信

1. 线程通信(wait)和(notify) 方法介绍

  • wait():执行该方法的线程对象要释放同步锁,JVM 会把该线程放到等待池里面,等待其它的线程唤醒它

  • notify():执行该方法的线程唤醒在等待池中的线程,把线程转到锁池中去等待。

  • notifyAll():执行该方法的线程,唤醒在等待池中的所有线程,把这些线程转移到锁池中去等待。

  • 注意点:这些方法,只能被同步监听锁对象锁(同步锁)调用

2. 同步监听锁对象(同步锁):多个线程有共同对象使用时,多个线程之间才会出现互斥现象,共同的这个对象就叫同步监听锁对象

3. 同步监听锁对象(同步锁):多个线程有共同对象使用时,多个线程之间才会出现互斥现象,共同的这个对象就叫同步监听锁对象

4. P线程和C线程S对象(同步锁),P,C线程如何通信(wait notify)

  • P线程执行S对象的同步方法,P线程持有S对象的锁,C线程是不是在S对象的锁池中等待。

  • P线程生产完,执行 wait() ,释放S对象的锁,进入S对象的等待池中去等待生产。

  • S对象锁池中等待的C线程获取S对象的锁,执行S对象的消费同步方法。

  • C线程消费结束,执行完S对象的同步消费方法时,执行notify()唤醒P线程,JVM把P从S对象等待池中移到S对象的锁池中,等待获取锁,C 线程释放锁,P获取锁,继续循环。

public class ShareResource {

    private String name;

    private String size;

    private boolean isEmpty = true;//判断资源对象是否为空

    /**
     * 给生产者推送数据的
     *
     * @param name
     * @param size
     */
    synchronized public void push(String name, String size) {

        try {
            //判断资源对象是否为空,空的时候等待
            while (!isEmpty){
                this.wait();
            }
            this.name = name;
            Thread.sleep(1000L);
            this.size = size;
            isEmpty = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            this.notify();//唤醒一个消费者
        }

    }

    synchronized public void pop() {
        try {
            //如果资源为空,消费者等待
            while (isEmpty){
                this.wait();
            }
            Thread.sleep(1000L);
            System.out.println("服装名称:" + this.name + " 尺码: " + this.size);
            isEmpty = true;
            this.notify();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

5. Lock 和 Condition 接口:

  • wait 和 notify 只能被同步监听锁对象调用,否则报错,但是Lock 机制根本没有同步锁,也没有获取释放锁的逻辑。

  • 使用Condition 接口对象的 await singnal signalAll 方法取代 Object 类中的 wait notify notifyAll 方法

public class ShareResource {

    private String name;

    private String size;

    private boolean isEmpty = true;//判断资源对象是否为空

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    /**
     * 给生产者推送数据的
     *
     * @param name
     * @param size
     */
    public void push(String name, String size) {
        lock.lock();//获取锁
        try {
            //判断资源对象是否为空,空的时候等待
            while (!isEmpty){
                condition.await();
            }
            this.name = name;
            Thread.sleep(1000L);
            this.size = size;
            isEmpty = false;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//释放锁
        }

    }

    public void pop() {
        lock.lock();//上锁
        try {
            //如果资源为空,消费者等待
            while (isEmpty){
                condition.await();
            }
            Thread.sleep(1000L);
            System.out.println("服装名称:" + this.name + " 尺码: " + this.size);
            isEmpty = true;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//释放锁
        }

    }
}

建议:建议使用第二种方式

死锁

1. 多线程通信时,很容易出现死锁现象,死锁并且是不能解决的,只能避免。

  • P线程等待有C线程持有的锁,而我们C线程又去等待P线程持有的锁,发生死锁现象,JVM不检测也不是图避免这种情况

2. 如何避免死锁:当我们多个线程去访问共享资源的时候 A,B,C,保证每一个线程都按照相同的顺序去访问。

3. Thread 类有一些过时的方法:

  • suspend() 使正在运行的线程放弃 CPU 暂停运行。

  • resume() 是暂停线程恢复运行

  • 这两方法很容易导致死锁,不要去使用

    • P线程获取锁对象,去执行同步方法,如果C线程调用P线程的暂停方法,此时P就会放弃CPU但是不释放锁。

  • 如何避免:上锁---释放锁

线程的生命周期

1. 生命周期:一个物品从出生---死亡

2. 线程的生命周期(Thread.State):

  • NEW:使用new 创建线程对象的时候,只分配了空间,调用 start() 方法的时候,线程启动

  • RUNNABLE:(可运行状态)使用了 start 之后,变成了两种状态

    • ready:准备就绪,等待 cpu 的调度

    • running :获取到了 cpu 的调度

  • BLOCKED:(阻塞的状态)运行中的线程,因为某些原因,放弃了 cpu 暂停运行,此时 jvm 不会给线程分配 cpu

    • 当 P线程处于运行状态时,但是,没有获取到同步锁, JVM 就会把 P线程放到同步锁对象的锁池中去,P阻塞

    • 线程运行中发出(IO请求),此时阻塞

  • WAITING:(等待的状态)等待状态只能被其它线程唤醒,

    • 运行中的线程调用了 wait() 方法,此时 jvm 把当前的线程存放在对象等待池中

  • TIMED_WAITING:(计时等待)wait(long),sleep(long),就变成计时等待

    • 线程运行过程中,调用上面方法,此时 JVM把当前线程存在对象等待池中

  • TERMINATED:(停止)表示线程结束了

    • 正常的执行完线程

    • 出现异常

 定时器

1. Timer 里面有一个常用的方法

Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("----");
            }
        },0,1000L);

线程组

1. 可以对线程集中管理,ThreadGroup 

2. 用户创建线程对象时,可以通过构造器指定所属的线程组

ThreadGroup groups = new ThreadGroup("商品上架线程组");
ThreadGroup groups1 = new ThreadGroup("fihfiwehuif");
GoodsThread a = new GoodsThread(groups, "A");
a.start();
new GoodsThread(groups,"B").start();
new GoodsThread(groups1,"C").start();
new GoodsThread("efwefewfewfe").start();

public GoodsThread(ThreadGroup group, String name) {
   super(group,name);
}

3. A线程中创建B线程,B线程默认加入A线程线程组

4. 默认创建的线程,都属于main 线程

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值