java 多线程

2022/2/16 1:11:52

本文主要是介绍java 多线程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

多线程创建方式一

方式一、继承Thread类

1、创建一个继承于Thread的子类

2、重写run()方法

3、创建Thread类的子类对象

4、通过对象调用start()方法

start方法的作用:启动当前线程 调用当前线程的run方法(启动一个新线程)

  • 如果直接调用run()方法不会启动新线程,还是在主进程里执行。

  • 不能让已经start()的线程再次执行第二次,需要再次新建一个

  • 最终结果是交替执行,执行结果和顺序不确定

方式二、匿名子类

public class ThreadDemo {
    public static void main(String[] args) {
        // 创建线程的匿名子类方法
        new Thread() {
            @Override
            public void run() {
                for(int i = 0;i < 100;i++){
                    if(i%2 == 0){
                        System.out.println(Thread.currentThread().getName()+":" + i);
                    }
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                for(int i = 1;i < 100;i++){
                    if(i % 2 != 0){
                        System.out.println(Thread.currentThread().getName()+":" + i);
                    }
                }
            }
        }.start();
    }
}

线程常用方法

  • start():启动当前线程,调用当前线程的run()
  • run():通常需要重写Thread()方法
  • String getName():返回线程名称
  • void setName(String name):设置线程名称
  • currentThread():静态方法,返回执行当前代码的线程

Thread.currentThread().setName("")....

  • yield():释放cpu的执行权,有可能cpu还会执行此线程

  • join():在线程a中调用线程b的join方法,此时线程a进入阻塞状态,b进入运行态直到线程b完全执行完毕,线程a结束阻塞状态

  • sleep(long milltime):休眠阻塞一段时间

线程优先级

  • 等级

    • MAX_PRIORITY: 10
    • MIN_PRIORITY: 1
    • NORM_PRIORITY: 5 默认的优先级
  • 方法

    • getPriority()
    • setPriority()

优先级高的表示被cpu执行的概率高

线程创建二——实现Runnable接口

  • 1、创建一个实现了Runnable接口的类
  • 2、实现类去实现Runnable中的抽象方法run()
  • 3、创建实现类的对象
  • 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 5、通过Thread类的对象调用start()方法
/** 创建多线程Runnable接口
 *  1、创建一个实现了Runnable接口的类
 *  2、实现类去实现Runnable中的抽象方法run()
 *  3、创建实现类的对象
 *  4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 *  5、通过Thread类的对象调用start()方法
 * @author DingJun
 * @date 2021/7/12 19:29
 */
// 1、创建一个实现了Runnable接口的类
class MThread implements Runnable {
    //  2、实现类去实现Runnable中的抽象方法run()
    @Override
    public void run() {
        for(int i = 0;i < 100; i++){
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
        //  3、创建实现类的对象
        MThread mThread = new MThread();
        // 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        // 通过Thread类的对象调用start()
        // 1、启动进程
        // 2、调用当前线程的run()--->调用了Runnable类型target中的run
        t1.start();
    }
}

源码Thread的构造函数中有public Thread(Runnable target)的构造方法,源码中Thread.run()方法如下:

public void run() {
    if(target != null) {
        target.run();
    }
}

比较创建线程的方式

优先选择实现Runnable接口的方式

原因: 1、实现方式没有类的单继承的局限性

2、实现的方式更适合处理多个线程有共享数据的情况

联系: public class Thread implements Runnable

都要重写run方法

线程安全问题

因为线程同步问题,cpu抢占时机不对而造成对共享资源的不正当访问而发生错误的现象。


线程同步机制

方法一:同步代码块 synchronized

synchronized(同步监视器) {

​ // 需要被同步的代码(操作共享数据的代码)

​ // 这里面的代码当有线程进入时,则其他线程不能进入

}

同步监视器 锁:任何一个类的对象都可以充当锁

要求:多个线程必须共用同一把锁

补充:在实现Runnable接口创建多线程的方式中,我们可以使用this充当同步监视器

在继承Thread 类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类(类.class)充当同步监视器。

方法二:同步方法

public synchronized void run(),在函数返回类型前面添加关键字

在同步代码内部是操作共享的代码,注意进行分割和组合

同步监视器:this

当使用同步方法对继承Thread类的多线程时,使用static synchronized void show(),同步监视器是当前的类(唯一的,Window.class)。

  • 同步方法仍然涉及到同步监视器,只是不需要我们声明
  • 非静态的同步方法,同步监视器是:this
  • 静态的同步方法,同步监视器是:当前类本身

方式三:LOCK(显式锁)

  • 手动地启动同步(lock()),手动地关闭锁(unlock()).
private ReentrantLock lock = new ReentrantLock();
lock.lock()
    ------
lock.unlock()

线程的通信

wait、notify、notifyAll三个方法必须使用在同步代码块或同步方法

这三个方法调用都省略了this(也就是当前对象),注意要与同步代码块或同步方法中的同步监视器一致...

这三个方法都是定义在java.lang.Object类中。

/** 线程通信,两个线程交替打印1到100
 * @author DingJun
 * @date 2021/7/13 21:04
 * wait(): 一旦执行此方法:当前线程进入阻塞状态,并释放同步监视器(锁)
 * notify():唤醒wait的一个线程,如果有多个线程被wait,优先唤醒优先级高的线程
 * notifyAll():唤醒所有被wait的线程
 */
class Number implements Runnable {
    private int num = 1;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                // 唤醒线程
                notify();
                if(num <= 100) {
//                    try {
//                        Thread.sleep(1000);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
                    System.out.println(Thread.currentThread().getName() + ":" +num);
                    num++;

                    try {
                        // 使得当前进程进入阻塞状态
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
public class CommuncitionTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.start();
        t2.start();
    }
}

sleep()和wait()的异同

1、同:都可以使得当前的线程进入阻塞状态

2、不同: 1) 两个方法声明位置不同:Thread类中声明sleep(),Object类中声明 wait().

2)调用的范围要求不同:sleep可以在任何需要的场景调用。wait()必须使用在同步代码块或同步方法中.

3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁.

生产者和消费者例题

package com.ding;


/** 消费者和生产者问题
 * 生产者将产品交给店员,消费者取走产品,店员一次只能持有固定数量的产品,如果生产者试图生产更多的产品,如果有空位放产品了再通知生产者
 * 继续生产;如果没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品
 * 共享数据:店员、产品数量
 * @author DingJun
 * @date 2021/7/14 10:59
 */

class Clerk {

    private int productCount = 0;
    // 生产产品
    public synchronized void produceProduct() {
        if(productCount < 20) {
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
            // 唤醒消费者线程,此时店员处有产品
            notify();
        }else {
            // 等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    // 消费产品
    public synchronized void consumerProduct() {
        if(productCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;
            // 唤醒生产者线程,此时产品被消费了一个肯定小于20
            notify();
        }else {
            // 等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 生产者线程
class Producer extends Thread {
    private Clerk clerk = new Clerk();

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":生产者开始生产...");

        while (true) {

            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}
// 消费者线程
class Consumer extends Thread {
    private Clerk clerk = new Clerk();

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() +":消费者正在消费");
        while (true) {

            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumerProduct();

        }
    }
}
public class TransportTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk(); // 声明店员对象
        // 共用同一个店员
        Producer p1 = new Producer(clerk);
        Consumer c1 = new Consumer(clerk);
        p1.setName("生产者");
        c1.setName("消费者");
        p1.start();
        c1.start();
    }
}

线程创建三

实现Callable接口

get返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值

  • 创建一个实现Callable接口的实现类
  • 实现call方法,将此线程需要执行的操作声明在call()中
  • 创建Callable接口实现类的对象
  • 将此Callable接口实现类对象传递到FutureTask构造器中,创建FutureTask对象
  • 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()..

image-20210714115823853

image-20210714201919923

实现Callable接口的方式创建多线程比实现Runnable接口更强大?

  • call()可以有返回值
  • call()可以抛出异常,被外面的操作捕获,获取异常的信息
  • Callable是支持泛型的

线程创建四——线程池

经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响比较大

  • 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用

  • 提高响应速度,降低资源消耗,便于线程管理

    // 1、指定线程数量的线程池,ExecutorService是一个接口
    ExecutorService service = Executors.newFixedThreadPool(10);
    // 设置线程池的属性
    ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
    service1.setCorePoolSize(15);
    service1.setKeepAliveTime();
    maximumPoolSize();
    // 2、执行指定的线程操作,需要提供实现Runnable 接口或Callable接口实现类的对象
    service.execute(new NumThread());
    service.execute(new NumThread1()); // 适用于Runnable接口
    
    // service.submit(Callable callable); // 适用于Callable接口实现类
    // 3、关闭连接池
    service.shutdown();
    

上面的ExecutorService只是一个接口,ThreadPoolExeutor类实现了此接口,该类有设置线程池属性的方法,进行强转。

线程生命周期

image-20210715104956675

  • 暂停/挂起线程(suspend)和恢复线程(resume())---暂停线程意味着线程还可以恢复运行


这篇关于java 多线程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程