Java--多线程

Java--多线程

进程

image-20230513091501132

线程

image-20230513091842468

多线程的实现方式

方法1:继承Thread类

  1. 定义一个类继承Thread方法
  2. 再该类中重写run()方法
  3. 创建该类的对象
  4. 启动线程

image-20230513092730780

代码示例

public class demo extends Thread{//继承Thread方法

    @Override
    public void run() {//重写run方法,把代码片断放入其中
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) {
        demo one = new demo();//
        demo two = new demo();//创建两个该类的对对象
//        one.run();
//        two.run();//如果直接调用run方法就和普通方法的调用一样,没有多线程
        one.start();//必须使用start方法调用才能启动多线程
        two.start();
    }
}

设置和获取线程名称

  1. 使用setName方法来修改线程名称
  2. 使用getName方法来返回该线程名称
  3. 还可以通过构造方法来设置线程名称(注意需要重写子类中的构造方法)

image-20230513094914956

示例代码

Demo

public class Demo extends Thread{//继承Thread方法

    public Demo(){

    }
    public Demo(String name)//重写子类构造方法
    {
        super(name);//调用父类构造函数
    }
    @Override
    public void run() {//重写run方法,把代码片断放入其中
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);//获取线程名称

        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) {
        Demo one = new Demo("线1");//
        Demo two = new Demo("线2");//创建两个该类的对对象
        //不设置线程名称默认为Thread-0,1,2
//线程名称只能设置一次?
        one.start();//必须使用start方法调用才能启动多线程
        two.start();
//        one.setName("线程1");//使用set方法设置线程名称
//        two.setName("线程2");//设置线程名称
//        one.start();//必须使用start方法调用才能启动多线程
//        two.start();
    }
}

线程调度

  1. 使用getPriority()返回线程的优先级
  2. 使用setPrioriity()更改线程优先级

线程高只是指获取CPU时间篇的几率高,CPU不是全部被线程优先级高的线程占用

image-20230513154907959

代码示例

Demo类

public class Demo extends Thread{//继承Thread方法

    public Demo(){

    }
    public Demo(String name)//重写子类构造方法
    {
        super(name);//调用父类构造函数
    }
    @Override
    public void run() {//重写run方法,把代码片断放入其中
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);//获取线程名称

        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) {
        Demo one = new Demo();//
        Demo two = new Demo();
        Demo three = new Demo();
        //不设置线程名称默认为Thread-0,1,2
//线程名称只能设置一次?
//        one.start();//必须使用start方法调用才能启动多线程
//        two.start();
        one.setName("飞机");//使用set方法设置线程名称
        two.setName("高铁");//设置线程名称
        three.setName("汽车");
        System.out.println(one.getPriority());
        System.out.println(two.getPriority());
        System.out.println(three.getPriority());//获取线程优先级
        System.out.println(Thread.MAX_PRIORITY);
        System.out.println(Thread.MIN_PRIORITY);
        one.setPriority(10);
        two.setPriority(1);
        three.setPriority(1);
        one.start();//必须使用start方法调用才能启动多线程
        two.start();
        three.start();
    }
}

实现Runable接口的方式进行实现

  1. 自己定义一个类实现Runnable接口
  2. 重写run方法
  3. 创建自己的类的对象
  4. 创建一个Thead类的对象,并开启线程

image-20230514100852583

代码示例

实现run方法

public class Myrun implements Runnable {


    @Override
    public void run() {//实现run方法
        for (int i = 1; i <= 100; i++) {
            //Thread.currentThread()获取当前线程
            System.out.println(Thread.currentThread().getName() + ":" + i);

        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) {

        Myrun one = new Myrun();
        Myrun two = new Myrun();//创建实现接口的类的对象
        Thread t1 = new Thread(one);//创建Thread对象
        t1.setName("线程1");
        Thread t2 = new Thread(two);//创建Thread对象
        t2.setName("线程2");
        //开启线程
        t1.start();
        t2.start();

    }
}

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

该方法可以获取多线程的结果

Future用来管理多线程的运行结果

Callable用来表示多线程要执行的任务

image-20230514102739743

代码实现

Callable实现类

package Demo01;

import java.util.concurrent.Callable;

public class MyCall implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }//创建类是实现Callable接口

//    @Override
//    public Integer call()  {
//
//
//    }
}

Main方法

package Demo01;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCall one =new MyCall();//创建Callable实现类的对象
        FutureTask<Integer> t1 = new FutureTask<>(one);//放入创建Callable实现类的对象
        Thread Th=new Thread(t1);//创建Thread的对象将 FutureTask填入其中
        Th.start();//开启多线程
        System.out.println(t1.get());


    }
}

多线程三种实现方式的比较

不可获取结果:

继承Thread类

实现Runable接口

可获取结果:

实现Callable类

image-20230514102652065

多线程中的常用成员方法

image-20230515113127940

成员方法的使用细节

image-20230515113036113

代码示例

继承Thread类

public class MyThread extends Thread {
    public MyThread() {

    }

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

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}


Main方法

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");//通过构造方法给线程赋值
        t1.start();
        t2.start();
        Thread one = Thread.currentThread();//获取当前线程
        System.out.println("当前线程名称:" + one.getName());
//        System.out.println(111111);
//        Thread.sleep(2000);//休眠2秒
//        System.out.println(222222);


    }
}

线程优先级

Java采用的是抢占式调度(随机性)

关于优先级的方法

image-20230515114555234

继承Thread类

public class MyThread extends Thread {
    public MyThread() {

    }

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

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");//通过构造方法给线程赋值

        //打印默认优先级
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        System.out.println("============");
        //优先级只是概率问题
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();




    }
}

守护线程

当其他非守护线程执行完毕之后,守护线程会陆续结束.(不是立即结束,是陆续结束)

image-20230515115645942

代码示例

MyThread1

public class MyThread1 extends Thread{
    public MyThread1() {
    }

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

    @Override
    public void run() {
        for (int i = 1; i <=10 ; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

MyThread2

public class MyThread2 extends Thread{
    public MyThread2() {
    }

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

    @Override
    public void run() {
        for (int i = 1; i <=100 ; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) {
        MyThread1 one = new MyThread1("女神");
        MyThread1 two = new MyThread1("备胎");
        two.setDaemon(true);//将第二个线程设置为守护线程
        one.start();//女神线程结束之后备胎线程就没有存在的必要了
        two.start();
//运行的时候可以看到备胎线程陆续结束
       
    }
}

礼让线程

不太常用

image-20230515120849379

表示出让当前CPU的执行权

可以让结果均匀一点

重写的Thread方法

public class MyThread extends Thread {
    public MyThread() {

    }

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

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + ":" + i);
            Thread.yield();//表示出让当前CPU的执行权

        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");//通过构造方法给线程赋值



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




    }
}

差入线程

不常用

image-20230515145933854

表示把下面的t线程插入到当前线程之前

当前线程:main线程

代码示例

继承的Thread类

public class MyThread extends Thread {
    public MyThread() {

    }

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

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + ":" + i);

        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread("飞机");
        t.start();//开启多线程
        t.join();//将t线程插入到main线程之前
        for (int i = 1; i <=10 ; i++) {
            System.out.println("main线程:"+i);
        }
        //如何在main线程之前插入t线程?,使用join方法


    }
}

线程的生命周期

image-20230517163146111

不会立即执行下面的代码,可能还没有抢到执行权

线程的安全问题

例题

image-20230517163343982

代码示例

MyThread

package Demo01;

public class MyThread extends Thread{
    static int ticket=1;
    @Override
    public void run() {
        while (true)
        {
            if (ticket<100)
            {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticket++;
                System.out.println(getName()+"正在卖第"+ticket+"张票.");

            }
            else
            {
                break;//票卖完了
            }


        }

    }
}

Main方法

package Demo01;

public class Main {
    public static void main(String[] args) {
        MyThread one= new MyThread();
        one.setName("窗口1");
        MyThread two = new MyThread();
        two.setName("窗口2");
        MyThread three =new MyThread();
        three.setName("窗口3");
        one.start();
        two.start();
        three.start();

    }



}

但是上面的代码有问题,会出现同时卖一个票和买票超过限额的情况

怎么进行优化?

同步代码块

线程执行具有随机性

卖同一个票的问题

image-20230517165349920

卖超出范围票的问题

image-20230517165613524

需要把共享数据的代码锁起来

  1. 锁默认打开,有一个线程进去了,锁自动关闭

  2. 当里面的代码执行完毕,线程出来,锁自动打开

image-20230517170005108

这种方法就叫做同步代码块

只有上一个线程出来,下一个线程才能进去

注意:锁对象,一定要是唯一的

image-20230517170330587

代码示例

MyThread

package Demo01;

public class MyThread extends Thread {
    static int ticket = 0;
    static Object obj = new Object();//保证锁对象是唯一的

    @Override
    public void run() {
        while (true) {
            synchronized (obj)//填入唯一的锁对象
            {
                if (ticket < 100) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票.");

                } else {
                    break;//票卖完了
                }

            }
        }

    }
}

Main

package Demo01;

public class Main {
    public static void main(String[] args) {
        MyThread one= new MyThread();
        one.setName("窗口1");
        MyThread two = new MyThread();
        two.setName("窗口2");
        MyThread three =new MyThread();
        three.setName("窗口3");
        one.start();
        two.start();
        three.start();

    }



}

同步代码块细节

  1. 要将synchronized写在循环里面,否则线程1就会一直执行直到线程结束

image-20230517171846734

  1. 锁对象一定要是唯一的

就是说线程1,2,3要看同一把锁才有意义.否则还会出现上面的问题

image-20230517172203086

经常使用字节码文件对象作为锁对象

package Demo01;

public class MyThread extends Thread {
    static int ticket = 0;
    static Object obj = new Object();//保证锁对象是唯一的

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class)//填入唯一的锁对象
            {
                if (ticket < 100) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票.");

                } else {
                    break;//票卖完了
                }

            }
        }

    }
}

同步方法

就是把synchronized关键字加到方法上

image-20230518094823993

技巧:先写同步代码块,然后改为同步方法就可以了

还是上面的那个卖票的例子

代码示例

Myrunable类

public class Myrunable implements Runnable {

    int ticket = 0;

    @Override
    public void run() {
        while (true) {
            if (extracted()) break;

        }
    }

    private synchronized boolean extracted() {//同步方法
            if (ticket >= 100) {
                return true;
            } else {
                ticket++;
                System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
            }
        return false;
    }
}

Main方法

public class Main {
    public static void main(String[] args) {
        Myrunable mr= new Myrunable();
        Thread one = new Thread(mr);
        Thread two = new Thread(mr);
        Thread three = new Thread(mr);
        one.setName("窗口1");
        two.setName("窗口2");
        three.setName("窗口3");
        one.start();
        two.start();
        three.start();
    }
}

lock锁

image-20230522143034123

这个lock锁就相当于我们可以用两个方法lock()和unlock来手动上锁和开锁,更加灵活.

代码示例

MyThread类

import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread{

    static int ticket=0;

    static ReentrantLock  one= new ReentrantLock();
    @Override
    public void run() {
        while (true)
        {
            one.lock();//在这里锁住
            if (ticket==100)
            {
                break;

            }
            else
            {
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticket++;
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!");
            }
            one.unlock();//在这里开锁


        }
    }
}

Main方法

public class Main {
    public static void main(String[] args) {
        MyThread one = new MyThread();
        MyThread two =new MyThread();
        MyThread three = new MyThread();
        one.setName("窗口1");
        two.setName("窗口2");
        three.setName("窗口3");
        one.start();
        two.start();
        three.start();


    }
}

但是上面的代码有个问题就是卖完100张票之后,程序不会停止运行,所以我们要在每次循环的最后进行一个关锁,可以借助try处理异常的方式来实现.

MyThread

import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread {

    static int ticket = 0;

    static ReentrantLock one = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            one.lock();//在这里锁住
            try {
                if (ticket == 100) {
                    break;

                } else {
                    sleep(100);

                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!!");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {//每次结束finally里面肯定会被执行
                one.unlock();//在这里开锁
            }


        }
    }
}

死锁

死锁是一个错误

image-20230522150534730

死锁不需要代码训练

image-20230522151250526

注意

写代码的时候不要让两个锁嵌套起来

生产者和消费者(等待唤醒机制)

该模式是一个十分经典的多线程协作的模式

image-20230522151441237

消费者和生成者轮流执行

image-20230522151703872

消费者等待

如果吃货发现没有面条就只能等待,而厨师发现没有面条就会开始做面条,当面条做完了之后,厨师就会唤醒吃货.

image-20230522151915215

image-20230522152126905

生产者等待

image-20230522152448080

常见方法

wait()让当前线程等待,直到其他线程被唤醒

notify()随机唤醒单个线程

notifyAll唤醒说有线程

等待唤醒机制(消费者)

吃货类

按照下面的顺序来写多线程

image-20230524160655773

image-20230524161316301

public class Foodie extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.flag == 0)//如果没有面条
                {
                    try {
                        Desk.lock.wait();//就等待
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                } else//如果有面条
                {
                    Desk.count--;//将面条减1
                    System.out.println("吃货正在吃面条,还能再吃" + Desk.count + "碗");
                    Desk.lock.notifyAll();//唤醒所有线程
                    Desk.flag = 0;//修改桌子状态
                }


            }


        }
    }
}

Disk类

作用:控制生产者和消费者的执行.

public class Desk {
    public static int flag = 0;//0代表桌子上面没有面条,1代表桌子上面有面条
    public static int count = 10;//可以吃的面条的碗数
    public static Object lock = new Object();
}

等待唤醒机制(生产者)

Cook类

image-20230524162958805

public class Cook extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {


                if (Desk.count == 0)//已经吃了10碗
                {
                    break;
                } else {
                    if (Desk.flag == 1)//当前有面条,cook应该等待
                    {
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {//如果没有面条
                        System.out.println("厨师正在制作一碗面条!!!!");
                        //修改桌子上的状态
                        Desk.flag = 1;
                        //叫醒吃货开吃
                        Desk.lock.notifyAll();


                    }
                }


            }
        }
    }
}


Main方法

public class Main {
    public static void main(String[] args) {
        Cook one =new Cook();
        one.setName("厨师");
        Foodie two = new Foodie();
        two.setName("吃货");
        one.start();
        two.start();

    }
}

等待唤醒机制(阻塞队列实现)

image-20230528115558747

阻塞队列的继承结构

image-20230528115801686

注意生产者和消费者必须使用同一个阻塞队列.

注意阻塞队列方法内部已经有锁了,不需要再实现锁.

image-20230528121002193

代码示例

Cook类

import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread {
    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }


    @Override
    public void run() {
        while (true) {
            try {
                queue.put("面条");
                System.out.println("放入面条");//因为打印语句在锁的外面,所以打印 会有重复的情况出现,但是数据是没有问题的
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

Foodie类

import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {

    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                String s = queue.take();
                System.out.println("正在吃" + s);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }


        }
    }
}

Main类

import java.util.concurrent.ArrayBlockingQueue;

public class Main {
    public static void main(String[] args) {
        ArrayBlockingQueue <String> q=new ArrayBlockingQueue<>(1);//创建长度为1的阻塞队列
        Cook one =new Cook(q);
        Foodie two =new Foodie(q);
        one.start();
        two.start();

    }
}

多线程的6种状态

image-20230528122632637

image-20230528122607807

posted @ 2023-05-13 10:33  harper886  阅读(17)  评论(0编辑  收藏  举报