多线程基础(一)概念

1.进程和线程基本概念

1.1 进程

  • 运行中的应用程序叫进程,每个进程运行时,进程负责了内存空间的划分。它是系统进行资源分配和调度的一个独立单位。

  • 操作系统都是支持多进程的

  • Windows是多任务的操作系统,那么Windows是同时运行多个应用程序吗?

1.2 线程

1.2.1线程概念和特点

  • 线程是轻重级的进程,是进程中一个负责程序执行的控制单元

  • 线程是由进程创建的(寄生在进程中)

  • 一个进程可以拥有多个线程,至少一个线程

  • 线程有几种状态(新建new,就绪Runnable,运行Running,阻塞Blocked,死亡Dead

  • 开启多个线程是为了同时运行多部分代码,每个线程都 有自已的运行的内容,这个内容可以称线程要执行的任务。

  • 打开360卫士,就是打开一个进程,一个进程里面有很多代码,这些代码就是谁来执行的呢?线程来执行这些代码。

 

1.2.2 多线程

  • 多线程:在一个进程中有多个线程同时在执行不同的任务。

 

  • 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务

  • 如:百度网盘、腾讯会议。

  • 同时执行多个任务的优势:减低CPU的闲置时间,从而提高CPU的利用率

1.2.3 计算机操作系统的进化史

  • 当前操作系统支持多线程

2.多线程的优缺点

2.1 多线程优点

  • 多线程最大的好处在于可以同时并发执行多个任务;

  • 多线程可以最大限度地减低CPU的闲置时间,从而提高CPU的利用率

2.2 多线程缺点

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;

  • 多线程需要协调和管理,所以需要CPU时间跟踪线程;

  • 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;

  • 线程太多会导致控制太复杂,最终可能造成很多Bug

3.主线程

  • Java支持多线程

  • 主线程的特殊之处在于:

    • 任何一个Java程序启动时,一个线程立刻运行,它执行main方法,这个线程称为程序的主线程。

    • 一个Java应用程序至少有两个线程,一个是主线程负责main方法代码执行;一个是垃圾回收器线程,负责了回收垃圾。

4.创建线程

4.1 创建线程的三种方式

  • 方式一:继承Thread类

  • 方式二:实现Runnable接口

  • 方式三:实现Callable接口

4.2 继承Thread类

  • 继承Java.lang.Thread类,并重写run() 方法。

 

案例1:打印输出0-100的数字,创建两个线程交替执行  

package thread;

public class MyPrintThreadTest {
    public static void main(String[] args) throws InterruptedException {
        //创建2个线程对象
        MyPeintTread thread1=new MyPeintTread("线程1");
        MyPeintTread thread2=new MyPeintTread("线程2");
        //start()方法
//        让操作系统启动一个线程执行MyPrintThread对象run方法
//       start方法调用之后,不会立即启动一个线程,线程的启动取决于操作系统
        thread1.start();
        thread2.start();
        //打印main方法执行完毕
        System.out.println("main方法执行完毕");

    }
}

package thread;
//打印1-100数字
public class MyPeintTread extends Thread {
    //构造方法,传入线程名称
    public MyPeintTread(String name) throws InterruptedException {

        super(name);
    }
//    重写run方法,线程会调用run方法
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //this.getName() 获取线程的名称
            System.out.println(this.getName() + ":" + i);

        }
    }
}

 案例2:模拟龟兔赛跑

package thread;

class MyThread3 extends Thread{
    private int s=5;
    @Override
    public void run() {
        while(true){
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            if (s<0){
                System.out.println("乌龟跑完了");
                break;
            }
            System.out.println("乌龟领先了,加油,还剩下"+s+"米");
            s--;
        }
    }
}
package thread;

public class MyThread4 extends Thread{
    private int s=5;

    @Override
    public void run() {
        while(true){
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            if (s<0){
                System.out.println("兔子跑完了");
                break;
            }
            System.out.println("兔子领先了,加油,还剩下"+s+"米");
            s--;
        }
    }
}
package thread;


public class RaceThreadTest {
    public static void main(String[] args) {
        MyThread3 myThread1=new MyThread3();
        MyThread4 myThread2=new MyThread4();
        myThread2.start();
        myThread1.start();
    }
}

案例1中的MyPrintThread要求继承ArrayList,如何创建多线程?继承Thread类就不能继承ArrayList,因为,java是单继承,所以就不合适了,也有的人认为让ArrayList继承Thread,MyPrintThread再继承ArrayList,或者Thread继承ArrayList,MyPrintThread再继承Thread,这两种方案都不可以,因为Thread和ArrayList是jdk提供的不能随便修改。

4.3 实现Runnable接口

  • 实现Java.lang.Runnable接口,并重写run() 方法;

注意:Runnable接口的存在主要是为了解决Java中不允许多继承的问题。

案例1:打印输出0-100的数字,创建两个线程交替执行

package runnable;

import java.util.ArrayList;

//实现Runnable线程类
public class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            try {
                //休眠500毫秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取执行run方法的当前线程的名称
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + ":" + i);
        }
    }
}
package runnable;

public class MyThreadTest {
    public static void main(String[] args) {
        //创建线程类
        MyThread t1 = new MyThread();
        //创建Thread对象包裹t1
        Thread thread1 = new Thread(t1);
        //启动线程
        thread1.start();

        //创建线程类
        MyThread t2 = new MyThread();
        //创建Thread对象包裹t1
        Thread thread2 = new Thread(t2);
        //启动线程
        thread2.start();
    }
}

 案例2:模拟龟兔赛跑

package runnable;

//乌龟数线程
public class MyThread3 implements Runnable {
    private int s = 5;

    @Override
    public void run() {
        while (true) {
            if (s < 0) {
                System.out.println("乌龟跑完了");
                break;
            }
            System.out.println("乌龟加油,还剩下" + s + "米");
            s--;
        }
    }
}
package runnable;

//兔子线程
public class MyThread4 implements Runnable {
    private int s = 5;

    @Override
    public void run() {
        while (true) {
            if (s < 0) {
                System.out.println("兔子跑完了");
                break;
            }
            System.out.println("兔子加油,还剩下" + s + "米");
            s--;
        }
    }
}
package runnable;

public class RaceMyThreadTest {
    public static void main(String[] args) {
        //运行main方法线程主线程

        //创建兔子线程类对象
        MyThread4 myThread1 = new MyThread4();
        //创建Thread对象
        Thread t1 = new Thread(myThread1);
        //启动线程
        t1.start();

        //创建乌龟线程类对象
        MyThread3 myThread2 = new MyThread3();
        //创建Thread对象
        Thread t2 = new Thread(myThread2);
        //启动线程
        t2.start();

        System.out.println("main方法完成");
    }
}

 案例1中MyThread如何实现返回值?

4.4 实现Callable接口

  • 使用Callable和Future创建线程

    使用Callable创建线程和Runnable接口方式创建线程比较相似,不同的是,Callable接口提供了一个call() 方法作为线程执行体,而Runnable接口提供的是run()方法,同时,call()方法可以有返回值,而且需要用FutureTask类来包装Callable对象。

  • 步骤:

    1、定义实现Callable接口的类,实现call() 方法

    2、创建Callable实现类实例,通过FutureTask类来包装Callable对象,该对象封装了Callable对象的call()方法的返回值。

    3、将创建的FutureTask对象作为target参数传入,创建Thread线程实例并启动新线程。

    4、调用FutureTask对象的get方法获取返回值。

案例  

package callable;

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

public class CallableThreadTest {
    public static void main(String[] args) throws ExecutionException,InterruptedException {
        //创建CallableThread对象
        CallableThread ct=new CallableThread();
        //创建FutureTask对象,获取返回值
        FutureTask<Integer> task=new FutureTask<>(ct);
//        创建Tread对象
        Thread t=new Thread(task);
        t.start();
//        FutureTask对象的get()方法获取CallableThread对象的call方法的返回值
        //FutureTask的get方法会阻塞代码,等待t线程运行结束,获取返回值,才会往下运行
        Integer sum=task.get();
        System.out.println(sum);

    }
}
package callable;

import java.util.concurrent.Callable;

public class CallableThread implements Callable<Integer> {


    @Override
    public Integer call() throws Exception {
       int sum=0;
        for (int i = 0; i <=100 ; i++) {
            sum+=i;
        }
        return sum;
    }
}

 案例2:

package user;

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package user;

import java.util.ArrayList;
import java.util.List;

public class UserService {
    public List<User> selectList(){
        List<User> users=new ArrayList<>();
        User u1=new User("jack",20);
        User u2=new User("jim",25);
        users.add(u1);
        users.add(u2);
        return users;
    }
}
package user;

import java.util.List;
import java.util.concurrent.Callable;

public class UserThread implements Callable<List<User>> {
    private UserService userService;

    public UserThread(UserService userService) {
        this.userService = userService;
    }

    @Override
    public List<User> call() throws Exception{
       List<User> users=userService.selectList();
       return  users;
    }
}
package user;

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

public class UserThreadTest {
    public static void main(String[] args) {
        //创建serService对象
        UserService userService = new UserService();
        //创建UserThread对象
        UserThread userThread=new UserThread(userService);
        //创建FutureFask对象
        FutureTask<List<User>> task=new FutureTask<>(userThread);
        //创建Threa对象
        Thread t=new Thread(task);
        //启动线程
        t.start();
        //FutureTack对象get方法获取返回值
        try {
            List<User> users=task.get();
            System.out.println(users);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

4.5 创建线程的三种方式-比较

  • 继承Thread类:

    • 优势:Thread类已实现了Runnable接口,故使用更简单

    • 劣势:无法继承其它父类

  • 实现Runnable接口:

    • 优势:可以继承其它类

    • 劣势:编程方式稍微复杂,多写一行代码

  • 实现Callable接口:

    • 类似于Runnable,方法可以有返回值,并且可以抛出异常。但是Runnable不行。

5.线程的状态

  • Java中线程状态转换:

Java中线程存在以下几种状态 :

  • 新线程:

    • 新创建了一个线程对象,此时它仅仅作为一个对象实例存在, JVM没有为其分配CPU时间片和其他线程运行资源。

  • 就绪状态:

    • 在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会

  • 运行状态:

    • 就绪态的线程获得cpu就进入运行态

  • 等待/阻塞:

    • 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

  • 死亡状态:

    • 线程执行完它的任务时,由JVM收回线程占用的资源

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java多线程编程中,线程同步是一个非常重要的概念。它用来确保多个线程在访问共享资源时不会产生冲突或竞争条件。在Java中,有几种方式可以实现线程同步,其中之一就是使用synchronized关键字。 synchronized关键字可以用于修饰代码块或方法。当我们使用synchronized修饰代码块时,需要指定一个对象作为锁对象。在代码块内部,只有获取了锁对象的线程才能执行代码块中的内容,其他线程则需要等待。 在给定的示例中,synchronized关键字被用于修饰insert方法,这意味着同一时刻只能有一个线程能够执行这个方法。这样就确保了对num变量的访问是安全的,不会出现竞争条件导致数据不一致的情况。 具体来说,当一个线程进入synchronized修饰的insert方法时,它会获取到insertData对象的锁,并执行方法内部的代码。而其他线程则需要等待,直到当前线程释放了锁。这样就保证了对num变量的操作是线程安全的。 需要注意的是,synchronized关键字只能保证同一时刻只有一个线程能够执行被修饰的代码块或方法,但并不能保证线程的执行顺序。所以在多线程编程中,我们还需要考虑到线程的调度和执行顺序的不确定性。 除了synchronized关键字,Java还提供了其他的线程同步机制,比如Lock接口,它提供了更灵活和细粒度的线程同步控制。但是在大部分情况下,synchronized关键字已经能够满足我们的需求,使用它来实现线程同步是一种简单而有效的方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Java高级特性 - 多线程基础(3)线程同步](https://blog.csdn.net/weixin_52034200/article/details/130253687)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [EduCoder Java高级特性 - 多线程基础(3)线程同步](https://blog.csdn.net/weixin_45981481/article/details/114494972)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值