多线程使用(一)

目录

一、基本概念

1、进程:

2、线程:

3、线程调度

3.1、分时调度

3.2、抢占式调度

3.3、数据库服务器响应

4、同步与异步

5、并发与并行

二、线程使用

1、Thread构造方法与定义

2、内存结构

3、使用线程三种方式:

4、继承Thread

5、实现接口Runnable

6、实现Runnable与继承Thread

7、线程休眠  \ 阻塞 \ 中断

1、线程休眠

2、线程阻塞:

3、线程中断

8、线程状态

9、守护线程和用户线程

三、线程安全和不安全

1、同步代码块 synchronized

2、同步方法

3、显示锁Lock

4、显示锁和隐式锁的区别

5、公平锁和不公平锁

6、线程死锁

四、多线程通信

1、wait()等待、notify()唤醒

2、经典案例:生产者与消费者问题

五、Runnable和Callable

1、接口定义

2、Callable使用步骤

3、Runnable和Callable的异同:

4、Callable获取返回值


一、基本概念

1、进程:

内存中运行的应用程序,每个进程都有独立的内存空间。

2、线程:

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
  • 线程是在进程基础上的进一步划分,一个进程启动后,里面的若干执行路径又可以划分为若干线程。
  • 当一个应用程序一个执行的线程都没有的时候,程序就会被关闭。

3、线程调度

3.1、分时调度

所有线程轮流使用CPU使用权,平均分配每个线程占用CPU的时间。

3.2、抢占式调度

优先让优先级高的线程使用CPU,如果线程优先级相同,随机选择一个线程(线程随机性),java中使用的为抢占式调度。

CPU使用抢占式调度模式在多个线程之间进行高速切换。对CPU的一个核心而言,某个时刻只能执行一个线程。而CPU在多个线程之间的切换速度相对于我们的感觉要快,看上去就像是在同一时刻运行。多线程并不能提高程序的运行速度,但是能提高程序的运行效率,让CPU的使用效率更高。

3.3、数据库服务器响应

上千个应用程序需要使用数据库服务器,服务器八个核心,程序访问时排队效率大于同时使用。

因为如果同时使用,那么每个程序每次只能执行1%或者更少,这样来回切换的次数可能就是1000*100=10W次;而如果采用排队,那么只需要切换1000次。

4、同步与异步

同步:排队执行,效率低但是安全。

异步:同时执行,效率高但是数据不安全。

例子:几个人一起吃一锅饭。

5、并发与并行

并发:两个或多个事件在同一时间段内发生。

并行:两个或多个事件在同一时刻发生。

二、线程使用

1、Thread构造方法与定义

定义:

public class Thread extends Object implements Runnable{}

常用构造方法:

Thread()

Thread(Runnable target)

Thread(String name)

Thread(Runnable target,String name)

常用方法:

Thread.currentThread().getName(); //获取线程名称

Thread.currentThread(); //获取当前正在执行代码的线程对象。

2、内存结构

每个线程都拥有自己的栈内存,共用一份堆内存。

由一个线程所执行的方法,这个方法会执行在线程自己的栈内存中。

3、使用线程三种方式:

  • Thread类
  • Runnable接口
  • Callable接口

4、继承Thread

继承Thread的子类就是一个线程类,需要重写方法:run()。

  1. 方法中就是线程要执行的任务,里面的代码就是一条新的执行路径。
  • run方法中就是线程要执行的任务,里面的代码就是一条新的执行路径。
  • 执行方法不能直接调用run,而是通过Thread实例化对象的start()来启动任务。
  • 最开始执行的线程是主线程,后来创建的都叫做分支线程

5、实现接口Runnable

1、使用:

  • Runnable,实现接口中的方法run()
  • runnable,即创建一个任务
  • Thread类,调用有参构造方法将runnable对象传入;即创建一个线程,并为其分配任务
  • thread的start()方法,即启动线程

6、实现Runnable与继承Thread

实际开发中更多的采用实现Runnable接口。

Runnable有如下优势:

  • 通过创建任务,给线程分配的方式来实现多线程,适合多个线程来执行相同任务的情况。
  • 可以避免单继承带来的局限性。
  • 任务与线程本身是分离的,提高了程序的健壮性。
  • 后续的线程池技术接受Runnable类型的任务,不接受Thread类型的线程。

 

7、线程休眠  \ 阻塞 \ 中断

1、线程休眠

static void sleep(long millis)方法,休眠暂停;

2、线程阻塞:

所有比较消耗时间的操作,线程阻塞。

例如:控制台接收用户输入。

例如:读取文件,耗时5S,即线程阻塞5S。

3、线程中断

一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。

给线程对象打标记,中断线程。

Runnable不能进行异常抛出,只能try...catch处理。

中断方法:

  • thread.interrupted();
  • sleep的时候就会进入异常处理语句块中,我们可以进行return操作,使线程消亡。
public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new MyRunnable());
        t1.start();

        for (int i=0;i<5;i++){
            System.out.println("你快点开始啊:"+i);
            Thread.sleep(500);
        }
        t1.interrupt();
    } 
} 
class MyRunnable implements Runnable{ 
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("我要开始了哈:"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
//                System.out.println("你要干啥,我还不想消亡呢");
                return;
            }
        }
    }
}

 

8、线程状态

Enum Thread.State 枚举类型

9、守护线程和用户线程

线程分类:daemon线程,用户线程;

所有用户线程结束,程序才会结束;

守护线程,守护用户线程,当最后一个用户线程结束时,所有守护线程自动消亡;

设置:thread.setDaemon(true);

三、线程安全和不安全

示例代码,卖票:

public class Test2 {
    public static void main(String[] args) {
        Runnable tick=new Tick();
        new Thread(tick).start();
        new Thread(tick).start();
        new Thread(tick).start();
    }
}
class Tick implements Runnable{
    private int count=10;
    @Override
    public void run() {
        while (count>0){
            System.out.println("开始准备卖票");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count--;
            System.out.println("余票还剩:"+count);
        }
    }
}

 

上述代码出现了逻辑错误,出票错误了。

线程不安全产生原因:多个线程同一个时间去争抢一个数据。

解决办法:排队执行,采用加锁的办法。

三种加锁机制:

  1. 同步代码块 synchronized
  2. 同步方法
  3. 显示锁Lock

1、同步代码块 synchronized

粒度较细,可以一行代码加锁;

synchronized(锁对象){

//排队执行的代码块

}

锁对象:java中任何对象都可以作为锁存在。

所有排队线程都需要使用相同的一个锁对象,不能每一个线程都有一个锁对象,这样就不能实现排队效果了。

class Tick implements Runnable{
    private int count=10;
    private Object obj=new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){
                if(count<=0){
                    break;
                }
                System.out.println("开始准备卖票");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"卖票,余票还剩:"+count);
            }
        }
    }
}

 

2、同步方法

以方法为单位加锁。

在方法,返回值类型 前面加一个 synchronized

锁对象:非静态方法,是 this;  静态方法,是 类名.class

如果是不同的任务,就不会上锁,因为每个任务的this是不一样的。

同一个任务中有多个同步方法执行,那么其中一个方法执行,其他任何方法都执行不了

因为所有同步方法的锁都是this,是同一个锁。

3、显示锁Lock

同步代码块和同步方法都属于隐式锁,Lock是隐式锁。

使用子类:ReentrantLock

  • new ReentrantLock();
  • lock()方法。
  • unlock()方法。

有参构造方法中,ReentrantLock(true) 表示公平锁,默认是false不公平锁。

4、显示锁和隐式锁的区别

5、公平锁和不公平锁

公平锁:排队的时候,谁先来谁先使用锁;

不公平锁:谁抢到谁使用;

同步代码块、同步方法、显示锁都是不公平锁。

6、线程死锁

A等待B,B等待A;

解决办法:在任何有可能导致锁产生的方法里,不要调用另外一个方法,让另一个锁产生;

即一个方法目前已经产生锁了,不要再去使用其他任何有可能导致锁的方法。

public static void main(String[] args) {
    Customer customer = new Customer();
    Shopkeeper shopkeeper = new Shopkeeper();
    new TestThread(customer, shopkeeper).start();
    customer.say(shopkeeper);
}
static class TestThread extends Thread {
    private Customer customer;
    private Shopkeeper shopkeeper;

    public TestThread(Customer customer, Shopkeeper shopkeeper) {
        this.customer = customer;
        this.shopkeeper = shopkeeper;
    }
    @Override
    public void run() {
        shopkeeper.say(customer);
    }
}
//顾客
static class Customer {
    public synchronized void say(Shopkeeper shopkeeper) {
        System.out.println("顾客:你先给我货,我给你钱");
        shopkeeper.respond();
    }
    public synchronized void respond() {
        System.out.println("顾客:我拿到了货,你拿到了钱");
    }
}
//店老板
static class Shopkeeper {
    public synchronized void say(Customer customer) {
        System.out.println("店老板:你先给我钱,我给你货");
        customer.respond();
    }
    public synchronized void respond() {
        System.out.println("店老板:我拿到了钱,你拿到了货");
    }
}

 

四、多线程通信

1、wait()等待、notify()唤醒

Object类中的wait和notify方法。

2、经典案例:生产者与消费者问题

生产者在生产时,消费者没有消费;消费者在消费时,生产者没有生产;

假设:一个厨师,一个服务员,一份食物;

案例中出现了三种情况:

  • 当不存在同步方法设置,也不存在等待唤醒机制的时候,数据会出现错乱。因为生产者还没生产成功,消费者就来消费数据,数据是错误的。
  • 当只有同步方法设置,没有等待唤醒机制的时候,会出现一直消费或者一直生产的情况。因为同步方法是不公平锁,会出现争抢,可能出现某一时间段一直消费或者一直生产情况。
  • 设置了同步方法,也设置了等待唤醒机制的时候,生产者执行完成后,会唤醒this对象上等待的消费者,同时使自己处于等待状态;同样的,消费者消费完成后,会唤醒this对象上等待的生产者,同时使自己处于等待状态;这样保证了一次生产,一次消费有序进行。

/**
 * 生产者与消费者问题
 * @param args
 */
public static void main(String[] args) {
    Food food=new Food();
    new Thread(new Cook(food)).start();
    new Thread(new Waiter(food)).start();
} 
/**
 * 厨师线程,生产食物
 */
static class Cook implements Runnable{
    private Food food;

    public Cook(Food food){
        this.food=food;
    }

    @Override
    public void run() {
        //生成两种食物
        for (int i=0;i<10;i++){
            if(i%2==0){
                food.setNameAndTaste("青椒肉丝","酸甜味");
            }else{
                food.setNameAndTaste("毛血旺","麻辣味");
            }
        }
    }
}

/**
 * 服务员线程,消费食物
 */
static class Waiter implements Runnable{
    private Food food;

    public Waiter(Food food){
        this.food=food;
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            food.get();
        }
    }
}

/**
 * 食物
 */
static class Food{
    //名称
    private String name;
    //口味
    private String taste;
    //flag true:生产  false:消费
    private boolean flag=true;

    /**
     * 设置食物名称和口味
     * @param name
     * @param taste
     */
    public synchronized void setNameAndTaste(String name,String taste){
        if(flag){
            this.name=name;
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste=taste;
            //1、生产结束,改变标志量
            flag=false;
            //2、唤起消费进程
            this.notify();
            //3、生产进程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void get(){
        if(!flag){
            System.out.println("服务生端走了食物:"+name+",口味:"+taste);
            //1、更新标志量
            flag=true;
            //2、唤起生产线程
            this.notify();
            //3、消费线程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
} 

 

五、Runnable和Callable

Callable:主线程等待返回值,可以安排上千个线程去执行,主线程等待拿结果。

1、接口定义

public interface Callable<V>{

      V call() throws Exception;

}

public interface Runnable{

    public abstract void run();

}

2、Callable使用步骤

1、编写类实现Callable接口,实现方法:call()

Class XXX implements Callable<T>{

  @Override

   public <T> call() throws Exception{

       return T;

  }

}

2、创建FutureTask对象,并传入第一步编写的类对象callable:

FutureTask<Integer> future=new FutureTask<>(callable);

3、通过Thread启动线程。

new Thread(future).start();

3、Runnable和Callable的异同:

相同点:

  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动程序

不同点:

  • Runnable没有返回值;Callable可以返回执行结果
  • Runnable的run()不能抛出;Callable的call()允许抛出异常

4、Callable获取返回值

Callable支持获取返回结果,调用FutureTask.get()方法,此方法会阻塞主进程继续往下执行,如果不执行不会阻塞。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑谈子云亭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值