自学内容网 自学内容网

Java-多线程

目录

一、多线程的定义

1. 多线程

2. 多线程的程序

3. 多线程的应用场景

4. 并发和并行

二、多线程的实现方式

1. 继承Thread的方式进行实现

2. 实现Runnable接口的方式进行实践

3. 利用Callable接口和Future接口方式实现

三、多线程的API

1. API

2. getName和setName

3. sleep

4. setPriority和getPriority

5. setDaemon

6. yield

7. join

四、锁

1. synchronized

2. Lock

3. 死锁

五、线程的生命周期

六、线程的等待和唤醒机制


一、多线程的定义

1. 多线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,一个软件运行之后就是一个进程,软件之中的各个功能可以看作一个线程,进程就是应用软件当中相互独立的,可以同时运行的功能,如果这样的功能比较多就形成了线程

2. 多线程的程序


    把等待的时间运用起来,去做其他的程序操作
    而不是像单线程的程序一样,等着每一步做完才去做下一步

3. 多线程的应用场景


    软件中的耗时操作,比如拷贝迁移大文件,加载大量的资源文件
    聊天软件
    后台服务等

    主要是用来提高效率的

4. 并发和并行

并发
    同一时刻,有多个指令在单个CPU上*交替*执行
    交替执行
并行
    在同一时刻,有多个指令在CPU上同时执行
    同时执行

二、多线程的实现方式

1. 继承Thread的方式进行实现


    编程简单,可以直接用Thread的方法
    可拓展性较差,因为不能再继承其他的类了

public class MyThread extends Thread{

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"hello");
        }
    }
}
    public static void main(String[] args){
        /*
        自己定义一个类继承Thread
        重写run方法
         */
        Thread_Test test1 = new Thread_Test();

        Thread_Test test2 = new Thread_Test();
        test1.setName("线程1");
        test2.setName("线程2");
        test1.start();
        test2.start();
        /*
        就会发现程序交替执行test1和test2
         */
    }


2. 实现Runnable接口的方式进行实践

拓展性强,实现该接口的同时还可以继承其他的类
编程较为复杂,不能直接使用Thread的方法,只能再创建thread的对象

不能直接调用getName,因为压根儿没有继承Thread
我们只能获取当前线程的对象

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

3. 利用Callable接口和Future接口方式实现

1. 创建一个类实现Callable接口
2. 重写call,有返回值,表示多西安城运行的结果
3. MyCallable的对象,表示多线程要执行的任务
4. 创建FutureTask的对象,作用管理多线程运行的结果
5. 创建Thread对象表示线程,用来启动

import java.util.Calendar;
import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum+=i;
        }
        return sum;
    }
}
public class Main {
    /*
    1. 创建一个类实现Callable接口
    2. 重写call,有返回值,表示多西安城运行的结果
    3. MyCallable的对象,表示多线程要执行的任务
    4. 创建FutureTask的对象,作用管理多线程运行的结果
    5. 创建Thread对象表示线程,用来启动
     */

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建对象表示多线程要执行的任务
        MyCallable mc = new MyCallable();
        //创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        //创建线程的对象
        Thread t1 = new Thread(ft);
        //启动线程
        t1.start();


        //获取变量运行的结果
        Integer result = ft.get();
        System.out.println(result);
    }

}


    拓展性强,实现该接口的同时还可以继承其他的类
    编程较为复杂,不能直接使用Thread的方法,只能再创建thread的对象

三、多线程的API

1. API

/*
getName
setName(String name)
static currenThread          获取当前线程对象
static sleep(long time)      让线程休眠指定的时间
setPriority(int newPriority) 设置线程的优先级
getPriority
setDaemon(boolean on)         设置为守护线程
static yield                  礼让线程
static join                   插入线程
 */

2. getName和setName

用于获取和设置线程的名字

public class MyThread extends Thread{

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"hello");
        }
    }
}
    public static void main_1(String[] args) throws InterruptedException {
        //thread也有默认名字
        //Thread-x(序号,从0开始递增)
        MyThread myThread1 = new MyThread("飞机");
        MyThread myThread2 = new MyThread("坦克");


        //谁先执行到currentThread方法,谁就是这个线程
        /*
        当JVM虚拟机启动后,会自动地启动多条线程
        其中有一条线程就叫做main线程
        它的作用就是调用main方法
         */
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
    }

3. sleep

哪条线程执行到了这个方法,哪条线程就会休眠指定的时间

参数时long time 

单位是毫秒

    public static void main_1(String[] args) throws InterruptedException {

        /*
        哪条线程执行到了sleep,就会休眠time时间
         */
        System.out.println(1111111);
        Thread.sleep(5000);
        System.out.println(2222222);
    }

4. setPriority和getPriority

调度分为抢占式调度和非抢占式调度,非抢占式调度中线程轮流执行

java中的是抢占式调度。线程抢夺CPU的执行权,具有随机性,但是哪个线程的优先级越大,哪个线程抢到执行权的概率也就越大

public static void main_2(String[] args) {
        /*
        优先级最大是10,最小是1
        如果没有设置,默认值是54
         */

        MyThread myThread1 = new MyThread("坦克");
        Thread t1 = new Thread(myThread1);


        System.out.println(t1.getPriority());



        MyThread myThread2 = new MyThread("飞机");

        Thread t2 = new Thread(myThread2);

        //但是不是绝对的
        t1.setPriority(1);
        t2.setPriority(10);

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

5. setDaemon

守护线程
    当其他的非守护线程执行完毕之后,守护线程会陆续结束
    非守护线程是与守护线程同时执行的,执行的时候随机
    但当非守护线程结束的时候,守护线程就没有存在的必要,也不一定执行完

使用场景
    聊天是线程1
    传输文件是线程2

setDaemon参数boolean类型,就是是否把这条线程设置为守护线程

    public static void main_3(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread1 t2 = new MyThread1();

        t1.setName("女神");
        t2.setName("备胎");

        t2.setDaemon(true);

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

6. yield

礼让线程
    有的时候两个线程执行的时候,其中一个线程欻一下执行很多次
    有时这样的现象并不符合需求
    我们想让线程执行的更加均匀一些
    就要使用到礼让线程
    public static void main_4(String[] args) {
        /*
        yield是一个静态的方法
        Thread.yield();表示让出这一次的执行权
         */

        MyThread3 t1 = new MyThread3();
        MyThread3 t2 = new MyThread3();

        t1.setName("飞机");
        t2.setName("坦克");

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

7. join

    /*
    插入线程
        想要让某个线程执行完毕之后再执行另一个线程

     */

    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");
        t1.join();
        t1.start();
        t2.start();
    }

四、锁

1. synchronized

假设现在有这样一个需求,一共有100张票,分三个窗口售卖

如果定义一个循环,打印完卖第n张票之后让计数器自增,就有可能导致还没有自增执行权就被其他的线程抢走,导致其他的线程也售卖了第n张票。

解决方法就是,在第n个窗口卖票的时候,其他的窗口不能插手。

这个就叫锁。

synchronized括住了那些代码,就代表这些代码必须由一个线程执行完出来之后,其他的线程才能进入,参数时一个对象,这个对象代表这段锁的钥匙,谁先进入谁拿到这段钥匙,出来后钥匙归还。

public class MyThread extends Thread{

    static int ticket = 0;

    @Override
    public void run() {
        while(true){
            synchronized (MyThread.class){//这里的synchronized就是将这一段代码锁起来
                if(ticket<100){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(getName()+"正在卖第"+ticket+"张");
                }else{
                    break;
                }
            }
        }
    }
}
如果你想把整个线程执行的操作全部锁起来,就没有必要再加synchronized
直接把synchronized关键字加到方法上
格式
    修饰符 synchronized 返回值类型 方法名(参数){}
特点1.
    同步代码块是锁住方法里面所有的代码
特点2
    锁对象不能自己指定
        如果你是非静态方法那锁对象就是this
        如果是静态方法,将会是当前类的字节码文件对象MyThread.class

有人要问了有没有方法自己上个锁,手动上锁手动解锁。

2. Lock

Lock锁
    为了更加清晰地表达哪里加锁哪里释放锁,JDK5之后提供了一个新的锁对象Lock

    void lock() 获得锁
    void unlock() 释放锁

    Lock是接口不能实例化,这里采用他的实现类ReentrantLock
    ReentrantLock构造方法
        ReentrantLock() 创建一个ReentrantLock实例
public class MyThread2 extends Thread{

    static int ticket = 2;

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            lock.lock();
            try {
                if(ticket<100){
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName()+"正在卖第"+ticket+"张");
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }

        }
    }
}

3. 死锁

死锁是一个常见的错误,并不是一个操作方式。

假如,两个人一起吃饭,我们定义的逻辑是这样。

A拿到第一根筷子,A拿到第二根筷子,A吃一口,A归还筷子

这样就会出问题,因为有可能,A拿到第一根筷子之后,B拿到了第二根筷子,这样谁也不会释放自己的筷子,导致程序卡死。

public class DeadLock extends Thread{

    static Object objA = new Object();

    static Object objB = new Object();

    @Override
    public void run() {

        while(true){
            if("线程A".equals(getName())){
                synchronized (objA){
                    System.out.println("线程A拿到了A锁,正在拿B锁");
                    synchronized (objB){
                        System.out.println("线程A拿到了B锁,下一轮");
                    }
                }
            }else if("线程B".equals(getName())){
                if("线程B".equals(getName())){
                    synchronized (objB){
                        System.out.println("线程B拿到了B锁,正在拿A锁");
                        synchronized (objA){
                            System.out.println("线程B拿到了A锁,下一轮");
                        }
                    }
                }
            }
        }
    }
}

五、线程的生命周期

线程的生命周期
    从生到死经历了那些阶段

    新建状态
    start就绪状态  (有执行资格,正在抢执行权)
    运行状态
    运行完毕,死亡状态


    当运行状态的时候执行权被抢走,又回到了就绪状态抢夺执行权
    当遇到了sleep,或者其他的阻塞式方法,就会进入到阻塞状态等待阻塞结束
    阻塞结束不会变为运行状态,而是就绪状态

六、线程的等待和唤醒机制

生产者和消费者(等待唤醒机制)
    生产者消费者模式是一个十分经典的多线程协作的模式


    一条线程为生产者
    另一条为消费者

    我们需要一个东西来控制线程的执行
    比如桌子上有面条,就让吃货执行吃面条
    如果桌子上是空的,就让厨师执行做饭
    这里的桌子就是控制的核心

会出现的情况
    消费者等待
        消费者先抢到了执行权,桌子上没有面,只能等待wait
        执行权转移给生产者
        生产者执行完之后,唤醒消费者notify

    生产者等待
        桌子上有食物,但是生产者又抢到了执行权
        桌子上是否有食物,有则唤醒消费者
        没有则做饭
public class Cook extends Thread{
    @Override
    public void run() {

        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    if(Desk.foodFlag == 1){
                        try {
                            Desk.lock.wait();//等待状态
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{
                        System.out.println("厨师做了一碗面条");
                        Desk.foodFlag = 1;
                        Desk.lock.notifyAll();//唤醒所有相关的线程
                    }
                }
            }
        }
    }
}
public class Foodie extends Thread{
    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    if(Desk.foodFlag == 0){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{
                        Desk.count--;
                        System.out.println("吃货吃面条,还能吃"+Desk.count+"碗");
                        Desk.lock.notifyAll();
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}


原文地址:https://blog.csdn.net/qian__yan/article/details/142898546

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!