Java多线程

让不同的程序块一起运行,如此一来可让程序运行更为顺畅,同时也可达到多任务处理的目的

程序、线程、进程的概念

程序:使用某种语言编写的一组指令(代码)的集合,静态

进程:运行的程序,表示程序一次的动态执行过程,经历了从代码加载、执行到执行完毕的一个完整过程,当程序运行完成,进程结束。

CPU的时间分片:抢占式

进程是进行系统资源分配、调度和管理的最小单位。

进程是每个独立执行的程序,每个进程都有自己独立的内存单元。因此进程间的信息交互比较麻烦。

线程是CPU调度和分派的基本单位

进程中,可以有多条执行链路,这些执行链路称为线程。同一个进程中,多个线程共享这个进程的内存资源。

每个线程独立拥有:虚拟机栈、程序计数器

所有线程共享:方法区static、堆 并发

多线程是是指一个进程在执行过程中可以产生多个线程

进程与线程的关系:

进程包含线程,一个进程包含多个线程,一个进程最小必须包含一个线程(主线程 main方法)

main方法创建过程,就是线程创建过程

一个进程死亡,这个进程中的所有线程死亡

线程销毁,进程未必会关闭

进程与线程的区别:

进程:每个进程都有自己独立的代码和数据空间,进程间的切换开销大

线程:一个进程内的多个线程,共享代码和数据空间,线程间的切换开销比较小

多线程是实现并发机制的

并发:一个CPU执行不同的任务 多个任务多个线程 (秒杀iphone14 多个用户进入程序占到 谁抢到是谁的

并行:多个CPU执行各种不同的任务 (你在扫地我在洗碗 不相关

多线程速度快吗?

不一定 没有直接联系 资源占用也会导致 CPU程序切换 由CPU核数决定 多个同步

Java实现多线程

多线程就是一个进程在执行过程中可以产生多个线程

jdk1.5前创建线程的方式:1. 继承Thread类 2. 实现Runnable接口

jdk1.5之后:1. 实现Callable接口 2.线程池

继承Thread

  1. 编写一个类继承Thread,该类是线程类

  2. 重写run(),编写该线程需要完成的任务

  3. 创建线程类对象

  4. 调用start()方法,启动线程

package Example;
​
public class MyThread extends Thread{//1.
    public MyThread(String name) {
    super(name);
    }
​
    @Override //2.重写run方法
    public void run(){
        System.out.println("执行了线程的run方法");
        for (int i = 0; i < 10; i++) {
            System.out.print(Thread.currentThread().getName());//得到线程的名字
            System.out.println("输出"+i);
        }
    }
}
​

package Example;
​
public class Test {
    public static void main(String[] args) {
        //启动线程
​
        //3.创建线程对象
        MyThread t1 = new MyThread("t1");
        MyThread t2 = new MyThread("t1");
​
        t1.start();//4.启动线程
        // 线程与其他线程抢占CPU资源  谁抢到运行谁
        t2.start();
        
        t2.run();//run方法 是main线程
        System.out.println("main线程执行完成...");
    }
}

注意:

  1. 线程启动,一定是调用start(),不是调用run() ;如果调用run() 只是调用方法,没有创建线程。

  2. 一个线程一旦启动,就不能重复启动

    对处于新建状态的线程两次调用start()方法会报IllegalThreadStateException异常,线程启动后,会调用 native start0() 方法,使得状态不为0

    当线程死亡以后,不能再次调用start()方法来启动该线程,调用也会返回IllegalThreadStateException异常

模拟使用多线程从网络下载多个图片

使用第三方依赖:commons-io.jar 下载

java项目添加jar包

  1. 项目下创建lib文件夹

  2. 拷贝jar包到lib下

  3. File- Projects Structure-Libraries- + - Java 设置依赖 设置lib目录为库

  4. 该jar添加给模块

package Download;
​
import org.apache.commons.io.FileUtils;
​
import java.io.File;
import java.io.IOException;
import java.net.URL;
​
public class MyThread extends Thread{
    private String  url;//网址
    private String filename;//保存文件名
​
    public MyThread(String url, String filename,String name) {
        super(name);//线程名
        this.url = url;
        this.filename = filename;
​
    }
​
    @Override
    public void run() {
        downloadFile();
        System.out.println(Thread.currentThread().getName()+"线程下载完成");
    }
​
    public void downloadFile(){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(filename));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package Download;
​
public class Test {
    public static void main(String[] args) {
        String url1 ="https://tse3-mm.cn.bing.net/th/id/OIP-C.LhlYOqLOYH08l-mpprGFSAAAAA?pid=ImgDet&rs=1";
        String url2 ="https://pic3.zhimg.com/v2-fa1fa693bc0a7f631f87d442366fdd0e_r.jpg?source=1940ef5c";
​
        MyThread t1 = new MyThread(url1,"1.jpg","t1");
        MyThread t2 = new MyThread(url2,"2.jpg","t2");
​
        t1.start();
        t2.start();
    }
}

实现Runnable接口

启动线程:都必须借助Thread的start()

Runnable实现类:一个线程的任务类

package RunnableDemo;
​
public class MyRunnable implements Runnable{//1.实现Runnable接口
​
    @Override
    public void run() { //2.重写run方法
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

package RunnableDemo;
​
public class Test {
    public static void main(String[] args) {
        //3.创建任务对象
        MyRunnable task = new MyRunnable();
        //4.创建线程
        Thread t1 = new Thread(task, "t1");
        Thread t2 = new Thread(task, "t2");
        //5.启动线程
        t1.start();
        t2.start();
​
    }
}

继承Thread类与实现Runnable接口的区别

  1. 继承Thread类不能继承别的类(java单继承);实现Runnable接口可以继承别的类

  2. 实现Runnable接口,可以让多个线程共享这个实现类对象

  3. 继承Thread类,启动简单;实现Runnable接口,必须依赖Thread类的start()方法

推荐使用Runnable接口

实现Callable接口

  1. 编写实现Callable接口的类,重写call()方法

  2. 启动线程

  3. 创建Callable实现类对象

  4. 创建一个FutureTask对象,传递Callable接口实现类对象, FutureTask异步得到Callable执行结果, (使用FutureTask类来包装Callable对象)提供get() FutureTask 实现Future接口,调用get方法实现Runnable接口

  5. 创建一个Thread对象, 把FutureTask对象传递给Thread, 调用start()启动线程

package CallableDemo;
​
import java.util.concurrent.Callable;
​
public class MyCallable implements Callable<String> {
    
    @Override
    public String call() throws Exception {//1.编写实现Callable接口的类,重写call()方法
        //线程睡眠 单位: 毫秒
        Thread.sleep(10000);
        System.out.println(Thread.currentThread().getName()+"执行完成");
        return "Callable";
    }
}

package CallableDemo;
​
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
​
public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        MyCallable myCallable = new MyCallable();//3.创建Callable实现类对象
        //4.封装成FutureTask
        FutureTask<String> task = new FutureTask<>(myCallable);
        Thread t1 = new Thread(task,"t1");//5.创建一个Thread对象
        //启动线程
        t1.start();
        //获取结果
      //  String rs = task.get();//阻塞 是main执行
​
        //设置超时时间 一旦到达,停止执行,抛出异常
          String rs = task.get(1, TimeUnit.SECONDS);
        //取消执行
        task.cancel(true);
     //   System.out.println(rs);
        System.out.println("main执行完成");
    }
}

线程的生命周期

五个状态:

新生状态:线程new出来,没有调用start()

就绪状态:线程调用start();

运行状态:线程抢到cpu资源,执行run()方法

阻塞状态:调用sleep(),没有获取到锁,线程挂起

死亡状态:正常死亡、非自然死亡

拿到线程状态:getState()

线程优先级

范围:1-10  10优先级最高  正常设置为5

 setPriority(数字):给线程设置优先级 getPriority():获取线程的优先级,

线程优先级高的与低的一起抢占资源 优先级高的抢占成功的几率大 

礼让:yield()

A线程调用yield(),A线程暂停运行后与B一起抢占资源,如果还是A抢到,还是A继续执行;如果B抢到,给B执行。 (我礼貌一下,如果我厉害还是我抢到)

join():类似插队

在执行A线程过程中准备B线程,并让B线程就绪,A执行一半之后,B调用Join方法,使得A线程阻塞,B线程启动执行,B执行完后才到A。(野蛮人:我让你停你就得停)

线程的终止

通过给定属性,然后通过控制属性值来破坏继续执行的条件

设置boolean值或通过数值判断是否相等

线程的同步

用关键字synchronized给对象加互斥锁.

同步代码块

Object obj = new Object();
...
synchronized(this){//this被加锁,任何其他要锁this的方法和代码块被阻塞.
 需要同步的代码;
}
...
synchronized(obj){//obj被加锁,任何其他要锁obj的代码块被阻塞.
 需要同步的代码;
}

同步方法

public synchronized void method1(){
 …
}

如果有一个线程进入了该方法,其他线程要想使用当前this对象的任何同步方法,都必须等待前一个线程执行完该同步方法之后

package Test2;

public class DeadLockTest implements Runnable{

    private int flag;

    public DeadLockTest(int flag) {
        this.flag = flag;
    }

    static Object lock1 = new Object();
    static Object lock2 = new Object();


    @Override
    public void run() {
        System.out.println("当前锁标志:"+flag);
        if (flag == 0){
            synchronized (lock1){
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock2);
            synchronized (lock2){
                System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2);
            }
        }
        if (flag == 1){
            synchronized (lock2){
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock1);
            synchronized (lock1){
                System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1);
            }
        }
    }
}

线程死锁

互斥使用,即当前资源被一个线程使用(占有)时,别的线程不能使用

例子:实例化了两个实现了Runnable接口的DeadLockTest对象test1和test2,test1的flag等于0,所以在thread1线程执行的时候执行的是run()方法的前半部分的代码,test2的flag等于1,所以在thread2线程启动的时候执行的是run()方法后半部分的代码,此时,出现了下列现象:thread1线程占有了lock1对象并等待lock2对象,而thread2线程占有了lock2对象并等待lock1对象,而lock1和lock2又被这俩个线程所共享,所以就出现了死锁的问题了。

package Test2;

public class DeadLockTest implements Runnable{

    private int flag;

    public DeadLockTest(int flag) {
        this.flag = flag;
    }

    static Object lock1 = new Object();
    static Object lock2 = new Object();


    @Override
    public void run() {
        System.out.println("当前锁标志:"+flag);
        if (flag == 0){
            synchronized (lock1){
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock2);
            synchronized (lock2){
                System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2);
            }
        }
        if (flag == 1){
            synchronized (lock2){
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock1);
            synchronized (lock1){
                System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1);
            }
        }
    }
}
package Test2;

public class Test {
    public static void main(String[] args) {
        DeadLockTest test1 = new DeadLockTest(0);
        DeadLockTest test2 = new DeadLockTest(1);

        Thread thread1 = new Thread(test1);
        Thread thread2 = new Thread(test2);

        thread1.start();
        thread2.start();


    }
}

补充

  • 为什么启动线程用run()不用start() ?

run() 是一种方法

start() 使得线程由创建态到就绪状态

 

通过start()调用run()方法


当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行;但是这并不意味着线程就会立即运行,只有当cpu分配时间片时,这个线程获得时间片时,才开始执行run()方法;start()方法去调用run(),而run()方法则是需要去重写的,其包含的是线程的主体(真正的逻辑)。

Java 中实现真正的多线程是 start 中的 start0() 方法,run() 方法只是一个包含业务逻辑普通方法;start是启动多线程的唯一方式,其使得线程由创建态到就绪态而这个线程是否被运行是由系统调度所决定的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值