Java 多线程 -- 从入门到精通

持续更新中,欢迎收藏,关注,以便查看后续

Java线程与线程的区别

  • 所有与进程相关的资源,都被记录在PCB(进程控制模块)中。
  • 进程是抢占处理机的调度单位。
  • 线程属于某个进程,共享进程的资源。
  • 线程由堆栈寄存器、程序计数器和TCB(线程控制模块)组成。
  • 线程是CPU调度的最小单位,进程是资源分配的最小单位。
  • 线程不能看做独立应用,而进程可看做独立应用
  • 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
  • 线程没有独立的地址空间,多进程的程序比多线程程序健壮
  • 进程的切换比线程的切换开销大

多线程的实现方法

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法
  • 通过Callable和FutureTask创建线程
  • 通过线程池创建线程

Thread中start和run方法的区别

  • run方法只是thread的一个普通方法调用,还是在主线程里执行,是不会开启多线程的

直接调用Run方法,程序中只有主线程这一个线程,执行路径只有一条,还是要顺序执行,需要run方法体执行完毕,才可执行下面的代码。(相当与普通的方法)

  • start方法可启动多线程

start方法启动线程,无需等待run方法体代码执行完毕,可以直接继续执行下面的代码。(真正的实现多线程)

  • 代码示例

创建一个MyThread方法继承Thread

public class MyThread extends Thread {

    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    public void run(){
        for (int i = 0 ; i < 100 ; i++){
            System.out.println(this.name +"--"+ i);
        }
    }

}

使用run方法:结果四个方法按照顺序运行,得出结论(直接使用run方法不是多线程)

public class demo {

    public static void main(String[] args) {
        MyThread my1 = new MyThread("第1个线程");
        MyThread my2 = new MyThread("第2个线程");
        MyThread my3 = new MyThread("第3个线程");
        MyThread my4 = new MyThread("第4个线程");

        my1.run();
        my2.run();
        my3.run();
        my4.run();
    }

}

使用start方法:结果发现四个方法交替输出,得出结论(start真正的实现多线程)

public class demo {

    public static void main(String[] args) {
        MyThread my1 = new MyThread("第1个线程");
        MyThread my2 = new MyThread("第2个线程");
        MyThread my3 = new MyThread("第3个线程");
        MyThread my4 = new MyThread("第4个线程");

        my1.start();
        my2.start();
        my3.start();
        my4.start();
    }

}

Thread和Runnable的关系

  • Thread是实现了Runnable接口的类,是Runnable的具体实现,使得run支持多线程;
  • 因类的单一继承原则,推荐多使用Runnable接口;

创建一个MyThread方法继承Thread

public class MyRunnable implements Runnable {

    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    public void run(){
        for (int i = 0 ; i < 100 ; i++){
            System.out.println(this.name +"--"+ i);
        }
    }

}

Runnable的代码实现

public static void main(String[] args) {
        MyRunnable my1 = new MyRunnable("第1个线程");
        MyRunnable my2 = new MyRunnable("第2个线程");
        MyRunnable my3 = new MyRunnable("第3个线程");
        MyRunnable my4 = new MyRunnable("第4个线程");

        Thread t1 = new Thread(my1);
        Thread t2 = new Thread(my2);
        Thread t3 = new Thread(my3);
        Thread t4 = new Thread(my4);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

使用Callable和Future创建线程

获取返回值示例

  1. 创建实现Callable接口的类
public class MyCallable implements Callable {

    public String call() throws Exception {
        return "hello world";
    }

}
  1. 获取多线程返回值
@Test
public void testThread1(){
    // 1.获取FutureTask对象
    MyCallable myCallable = new MyCallable();
    FutureTask futureTask = new FutureTask(myCallable);
    // 2.开启线程
    new Thread(futureTask).start();
    try{
        String s = (String) futureTask.get();
        System.out.println(s);
    }catch (InterruptedException e){
        e.printStackTrace();
    }catch (ExecutionException e){
        e.printStackTrace();
    }
}

线程返回值的处理方法

  • 主线程等待法:循环–检测–睡眠 —》 检测要获取值不为空 停止睡眠
  • 使用Thread类的join()方法:阻塞当前线程以等待子线程处理完毕
  • 实现Callable接口:通过FutureTask或线程池获取

线程池的创建使用

阻塞队列

容量有限
基于数组的先进先出队列
BlockingQueue< Runnable > workQueue = new ArrayBlockingQueue<>(5);

容量无限
基于链表的先进先出队列
弊端:如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了
BlockingQueue< Runnable > workQueue = new LinkedBlockingQueue<>();

拒绝策略

默认
队列满了之后它将抛出 RejectedExecutionException 异常
RejectedExecutionHandler rejected = new ThreadPoolExecutor.AbortPolicy();

队列满了丢任务不异常,但是线程池将丢弃被拒绝的任务。
RejectedExecutionHandler rejected = new ThreadPoolExecutor.DiscardPolicy();

将最早进入队列的任务删除,然后将被拒绝的任务添加到等待队列中。
RejectedExecutionHandler rejected = new ThreadPoolExecutor.DiscardOldestPolicy();

如果添加到线程池失败,那么主线程会自己去执行该任务
RejectedExecutionHandler rejected = new ThreadPoolExecutor.CallerRunsPolicy();

创建多线程

四种构造方法:

/**
*  corePoolSize    核心线程数
*  maximumPoolSize 最大线程数
*  keepAliveTime   idle线程存活时间
*  unit            上个参数的单位
*  workQueue       线程对象的缓冲队列
*  threadFactory   生成线程的工厂
*  handler         达到容量后的回调
*/

//1.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue)

//2.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, 
        RejectedExecutionHandler handler)

//3.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, 
        ThreadFactory threadFactory)

//4.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, 
        ThreadFactory threadFactory, 
        RejectedExecutionHandler handler)

线程不安全

示例代码

public class Demo implements Runnable {

    private static int sum = 0;

    public void run() {
        for (int i = 0;i<50000;i++) {
            System.out.println(Thread.currentThread().getName() + ":" + (sum++));
        }
    }

    public static void main(String[] args) {
        Demo syncThread = new Demo();
        Thread thread1 = new Thread(syncThread,"线程1");
        Thread thread2 = new Thread(syncThread,"线程2");
        Thread thread3 = new Thread(syncThread,"线程3");
        Thread thread4 = new Thread(syncThread,"线程4");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
    
}

运行截图如下:多次运行结果不一致,这就是线程不安全。正常最后一个值应该为:50000*4-1=199999

在这里插入图片描述
在这里插入图片描述

解决线程不安全(synchronized)

示例代码

public class Demo implements Runnable {

    private static int sum = 0;

    public void run() {
        synchronized (this) {
            for (int i = 0;i<50000;i++) {
                System.out.println(Thread.currentThread().getName() + ":" + (sum++));
            }
        }
    }

    public static void main(String[] args) {
        Demo syncThread = new Demo();
        Thread thread1 = new Thread(syncThread,"线程1");
        Thread thread2 = new Thread(syncThread,"线程2");
        Thread thread3 = new Thread(syncThread,"线程3");
        Thread thread4 = new Thread(syncThread,"线程4");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

运行截图如下:最后一个值多次测试都为199999

在这里插入图片描述

sleep和wait的区别

  • 基本差别
  1. sleep是thread类的方法,wait是Object类中定义的方法。
  2. sleep()方法可以在任何地方使用。
  3. wait()方法只能在synchronized方法或者synchronized块中使用。
  • 本质差别
  1. Thread.sleep只会让出CPU,不会导致锁行为的改变。
  2. Object.wait不仅仅让出CPU,还会让出已经占有的同步资源锁。

示例代码:

public void run() {
        synchronized (this) {
            try {
                //不仅仅让出CPU,还会让出已经占有的同步资源锁。
                this.wait(1000);
                for (int i = 0;i<50000;i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + (sum++));
                }
                //只会让出CPU,不会导致锁行为的改变。
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

notify与notifyAll的区别

  • notify:只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。(随机)
  • notifyAll:所有等待该锁的所有线程都会被唤醒,所有被唤醒的线程都将争夺锁,如果某一个线程获得了锁,其他线程将会进入线程等待。

生活小案例:
notify:
好比你在上厕所,外面有很多人在等,但是有一个人是厕所管理员。等你出来的时候,由管理员随机找一个人去上厕所。
notifyAll:
好比你还在上厕所,外面依然有很多人在等待,但是没有厕所管理员。等你出来的时候,大家一起去抢厕所的使用权,当有一个人抢到厕所的时候,其他人重新等待空余的厕所。

线程的六个状态

  1. 初始(NEW):
  2. 运行(RUNNABLE):
  3. 阻塞(BLOCKED):
  4. 等待(WAITING):
  5. 超时等待(TIMED_WAITING):
  6. 终止(TERMINATED):shang

第三步到第五步都属于阻塞状态
查看进程状态代码示例如下

public class Demo{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyRunnable());
        System.out.println(thread.getState());//线程状态:初始(NEW)
        thread.start();
        System.out.println(thread.getState());//线程状态:运行(RUNNABLE)
        //为了保证下方方法体执行完毕,让当前主线程休眠0.1s
        Thread.sleep(100);
        System.out.println(thread.getState());//线程状态:终止(terminated)
    }
}
class MyRunnable implements Runnable{
    public void run() {
        for (int i = 0; i < 100; i++) {
        }
        System.out.println("循环完成~");
    }
}

补充:

  • 进入synchronized时,且没有获取到锁,线程状态 ---- blocked
    直到锁被释放。线程状态 ---- runnable
  • 线程调用wait()或join时,线程状态 ---- waiting
    调用notify或notifyAll时,或join的线程执行结束后,线程状态 ---- runnable
  • 线程调用sleep(time),或wait(time)时,线程状态 ---- timed waiting
    当休眠时间结束后,或者调用notify或notifyAll时。线程状态 ---- runnable
  • 程序执行结束,线程状态 ---- terminated

Thread.yield

使用yield线程会把CPU时间让掉,让所有线程在竞争一次,也就是说也就是谁先抢到谁执行。
代码示例如下:

public class Demo1 extends Thread {

    private String name;

    public Demo1(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(this.name+"-------"+i);
            // 当i为5时,该线程就会把CPU时间让掉,让所有线程在竞争一次,也就是说也就是谁先抢到谁执行。
            if (i == 5) {
                this.yield();
            }
        }
    }

    public static void main(String[] args) {
        Demo1 d1 = new Demo1("张三");
        Demo1 d2 = new Demo1("李四");
        Demo1 d3 = new Demo1("王五");
        d1.start();
        d2.start();
        d3.start();
    }
}

什么是线程安全

在多条线程访问的时候,我们在主程序中不需要去做任何的同步,的程序还能按照我们预期的行为去执行,那么我们就可以说这个类是线程安全的。

我们什么时候需要考虑线程安全呢?:多个线程访问同一个资源
· 如果是多个线程访问同一个资源,那么就需要上锁,才能保证数据的安全性。
· 如果每个线程访问的是各自的资源,那么就不需要考虑线程安全的问题,所以这个时候,我们可以放心使用非线程安全的对象

如何实现线程安全

一、synchronized

采用synchronized关键字给代码块或方法加锁

二、Lock

在java 5之后,java.util.concurrent.locks包下提供了另外一种方式来实现线程同步,就是Lock。

三、synchronized和Lock的区别:

  • Lock是接口,synchronized是关键字
  • Lock可以提高多个线程进行读操作的效率。
  • Lock可以让等待锁的线程响应中断,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
  • 发生异常时:Lock需要在finally块中释放锁,否则很可能造成死锁现象。synchronized会自动释放线程占有的锁,不会导致死锁现象发生。
  • 69
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
### 回答1: 《21天学通Java-第7版 入门到精通》是由林信良编著的一本Java编程教材。这本书主要适合初学者学习Java编程语言。 首先,本书介绍了Java编程的基础知识,包括Java环境的安装和配置、Java基本语法、面向对象编程等。读者可以通过丰富的实例和案例,掌握Java编程中的基本概念和技巧。 其次,本书还详细介绍了Java的高级特性,如异常处理、IO操作、多线程编程、网络编程等。这些内容不仅能帮助读者进一步提高Java编程的能力,还能够应用于实际项目开发中。 此外,书中还介绍了Java的GUI编程、数据库编程以及JavaWeb开发等内容。这使得读者可以在学习完基础知识后,进一步深入学习Java编程的各个方向。 这本书的特点是结构清晰,内容丰富,既适合作为Java编程的入门教材,也适合作为进阶学习的参考。书中还提供了大量的练习题和案例,读者可以通过实际动手编写代码来巩固所学知识。 总之,《21天学通Java-第7版 入门到精通》是一本很好的Java编程教材,无论是初学者还是有一定编程基础的读者,都可以通过学习这本书快速掌握Java编程技能。 ### 回答2: 《21天学通Java-第7版 入门到精通PDF》是一本专门介绍Java编程语言的书籍。Java是一种跨平台的高级编程语言,广泛应用于各种类型的软件开发中。 这本书的第7版涵盖了从入门到精通的内容,适合初学者和有一定基础的读者。书中的内容分为21个章节,每天学习一个章节,需要大约三周的时间。通过阅读本书,读者可以系统地掌握Java的语法、基本概念和编程技巧。 本书的特点是结合理论和实践,通过丰富的实例和练习,帮助读者加深对Java的理解。每个章节都有清晰的学习目标和总结,讲解内容简洁明了,易于理解。此外,书中还介绍了Java的常见开发工具和常用的第三方库,帮助读者提高编程效率。 值得一提的是,本书提供了附带学习资源,包括源代码、习题答案和额外的学习资料。读者可以通过这些资源进行实践和进一步学习,巩固所学知识。 总的来说,《21天学通Java-第7版 入门到精通PDF》是一本适合初学者入门学习Java的好书。通过认真阅读和实践,读者可以逐步掌握Java编程的基本技能,并逐渐提升到精通的水平。这本书是学习Java编程的良好起点,也是提升编程能力的重要工具。 ### 回答3: 《21天学通Java-第7版 入门到精通》是一本针对Java编程语言入门学习的教材。它是以21天为单位进行组织的,每天学习一个特定的主题,帮助读者逐步掌握Java编程的基础知识和技术。 这本书以简明易懂的方式介绍了Java的基本语法、数据类型、运算符、控制结构、方法等基础知识,并通过实例解析帮助读者理解和掌握。每天的学习内容都有相应的练习题,读者可以通过动手实践加深对知识点的理解,并培养编程能力。 《21天学通Java-第7版 入门到精通》不仅涵盖了Java基础知识,还介绍了面向对象编程、异常处理、多线程编程、GUI编程、数据库编程等高级主题。通过学习这本书,读者可以系统地了解Java的核心特性和常用类库,为进一步深入学习和应用Java打下坚实的基础。 此外,这本书还涵盖了一些实际应用和项目实践,帮助读者将学到的知识应用到实际开发中。它介绍了如何使用Eclipse和Java开发工具进行开发,以及如何使用Java编写Web应用程序和Android应用程序。 总之,如果你想从零开始学习Java编程,这本《21天学通Java-第7版 入门到精通》是一个很好的选择。它不仅具备全面的内容,而且以简单易懂的方式呈现,适合初学者快速上手。无论是自学还是作为教材,这本书都能帮助你掌握Java编程技能,成为一名优秀的Java开发人员。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值