【Java】第三十二节 多线程

程序、进程与线程:

程序:为完成特定功能,用某种计算机语言编写的一组指令的集合;
进程:运行中的程序;
线程:进程中细分出来,用于完成程序众多功能中的某个功能的执行分支。
例:程序类似于没有开启的金山毒霸,打开后的金山毒霸类似一个进程,在金山毒霸里同时进行木马查杀、垃圾清理等操作相当于开启了多个线程。

多进程与多线程:

一个进程至少有一个线程,即主线程。
多进程:同时运行多个程序;
多线程:多个线程同时执行。

创建线程的方式一:继承Thread类

1、创建一个继承了Thread类的子类;
2、重写Thread类中的run()方法;
3、在main方法中创建该类的实例化对象;
4、调用该实例化对象的start()方法:①开启了该线程;②调用了该对象的run()方法。

package cn.jingpengchong.thread;

//1、创建一个继承了Thread类的子类;
public class MyThread extends Thread {
	//2、重写Thread类中的run()方法;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 3 == 0){
                System.out.println(i);
            }
        }
    }
}
package cn.jingpengchong.test;

import cn.jingpengchong.thread.MyThread;

public class Test {
    public static void main(String[] args) {
    	//3、在main方法中创建该类的实例化对象;
        MyThread thread = new MyThread();
        //4、调用该实例化对象的start()方法
        thread.run();
    }
}

注意
1、一个Thread类子类的实例化对象在一个程序中只能调用一次start()方法;
2、不能直接调用run()方法开启线程。

创建线程的方式二:实现Runnable接口

1、创建一个Runnable接口的实现类;
2、实现Runnable接口的run()方法;
3、创建一个Runnable接口实现类的实例化对象;
4、将Runnable接口实现类的实例化对象作为实参传给Thread类的有参构造方法,以创建一个Thread类的实例化对象;
5、调用Thread类的实例化对象的start()方法:①开启了该线程;②调用了该对象的run()方法。

package cn.jingpengchong.thread;

//1、创建一个Runnable接口的实现类;
public class MyRunnable implements Runnable {
	//2、实现Runnable接口的run()方法;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 3 == 0){
                System.out.println(i);
            }
        }
    }
}
package cn.jingpengchong.test;

import cn.jingpengchong.thread.MyRunnable;

public class Test {
    public static void main(String[] args) {
    	//3、创建一个Runnable接口实现类的实例化对象;
        MyRunnable runnable = new MyRunnable();
        //4、创建一个Thread类的实例化对象;
        Thread thread = new Thread(runnable);
        //5、调用Thread类的实例化对象的start()方法
        thread.run();
    }
}

相比方法一的优点
1、避免了单继承的局限;
2、可以实现多个线程之间的数据共享。

创建线程的方式三:实现Callable接口

1、创建一个Callable接口的实现类,泛型为call()方法的返回值类型;
2、实现Callable接口的call()方法,call()方法的返回值类型为Callable接口的泛型;
3、创建一个Callable接口的实现类的实例化对象;
4、将Callable接口的实现类的实例化对象作为实参传递给FutureTask的构造方法中,以创建一个FutureTask类的实例化对象,泛型为call()方法的返回值类型;
5、将FutureTask类的实例化对象作为实参传递给Thread类的构造方法中,以创建一个Thread类的实例化对象;
6、调用Thread类的实例化对象的start()方法;
7、调用FutureTask类的实例化对象的get()方法,可以获得call()方法的返回值。

package cn.jingpengchong.thread;

import java.util.concurrent.Callable;

//1、创建一个Callable接口的实现类;
public class MyCallable implements Callable<Integer> {

	//2、实现Callable接口的call()方法;
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 10; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
package cn.jingpengchong.test;

import cn.jingpengchong.thread.MyCallable;

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

public class Test {

    public static void main(String[] args) {
    	//3、创建一个Callable接口的实现类的实例化对象;
        MyCallable mc = new MyCallable();
        //4、创建一个FutureTask类的实例化对象;
        FutureTask<Integer> task = new FutureTask<Integer>(mc);
        //5、创建一个Thread类的实例化对象;
        Thread thread = new Thread(task);
        //6、调用Thread类的实例化对象的start()方法;
        thread.start();
        try {
        	//7、调用FutureTask类的实例化对象的get()方法获得call()方法的返回值。
            Integer sum = task.get();
            System.out.println("总和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

相比方法二的优点
1、可以抛出异常;
2、可以有返回值。

创建线程的方式四:使用线程池

1、创建指定数量的线程池;
2、执行指定的线程操作,需要提供Runnable或Callable接口实现类的实例化对象

  • execute()方法用于启动Runnable方式创建的线程,没有返回值;
  • submit()方法用于启动Callable方式创建的线程,有返回值。

3、关闭连接池。

package cn.jingpengchong.test;

import cn.jingpengchong.thread.MyCallable;
import cn.jingpengchong.thread.MyRunnable;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(10);

        pool.execute(new MyRunnable());
        pool.submit(new MyCallable());

        pool.shutdown();
    }
}

相对于其他方式的优点
1、提高响应速度,减少了创建新线程的时间;
2、降低资源消耗,重复利用线程池中线程,不需要每次都创建;
3、便于线程管理。

线程的常用方法:

  • 1、Thread():空参构造方法;
  • 2、Thread(String name):直接为线程命名的构造方法;
  • 3、currentThread():获取执行当前代码的线程;
  • 4、getName():获取当前线程的名称;
  • 5、setName():设置当前线程的名称;
  • 6、yield():释放当前线程的cpu执行权;
  • 7、join():执行当前代码的线程停止,直到该方法的调用者线程执行完毕才执行;
  • 8、sleep():让当前线程停止指定的毫秒后再执行;
  • 9、isAlive():判断当前线程是否存活;

线程的优先级:

getPriority():获取当前线程的优先级;
setPriority():设置当前线程的优先级:

  • 优先级从低到高依次为:1-10
  • 特定的几个优先级:MIN_PRIORITY(1)、MAX_PRIORITY(10)、NORM_PRIORITY(5)

某个线程优先级越高,越有可能先执行该线程,而并非一定先执行该线程。

线程的生命周期:

在这里插入图片描述

多线程小练习:多窗口卖票

package cn.jingpengchong.thread;

public class Tickets implements Runnable {

    private int tickets = 100;
    @Override
    public void run() {
        while (tickets>0){
            System.out.println(Thread.currentThread().getName()+":卖票-->"+tickets);
            tickets --;
        }
    }
}
package cn.jingpengchong.test;

import cn.jingpengchong.thread.Tickets;

public class Test {

    public static void main(String[] args) {
        Tickets tickets = new Tickets();
        Thread window1 = new Thread(tickets);
        Thread window2 = new Thread(tickets);
        Thread window3 = new Thread(tickets);
        window1.setName("窗口①");
        window2.setName("窗口②");
        window3.setName("窗口③");
        window1.start();
        window2.start();
        window3.start();
    }
}

运行测试类结果如下:
在这里插入图片描述
问题:从结果看,有两个问题:

  • 输出了3个100;
  • 输出没有严格按照递减顺序。

对于上述问题,是由于出现了线程安全性问题,即某个线程在刚刚输出tickets,尚未操作tickets的时候,其他线程也进来了,并且开始输出尚未改变的tickets。为了解决这个问题,我们引入了同步机制与Lock锁,即某个线程在操作tickets的时候,其他线程要等待该线程处理完毕才能操作tickets。

发布了128 篇原创文章 · 获赞 17 · 访问量 2752

猜你喜欢

转载自blog.csdn.net/qq_43705275/article/details/103924046