【Java高级编程】Java多线程学习笔记

简介: 【Java高级编程】Java多线程学习笔记

1.多线程创建


方法1:通过 继承 thread 类

[子线程代码] MyThread.java

package example01_thread;
public class MyThread extends Thread {
    @Override
    public void run() {
        // super.run();
        for (int i = 0; i <= 1000; i++) {
            System.out.println("===> 子线程 " + i);
        }
    }
}

[主线程代码] Test.java

package example01_thread;
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //myThread.run(); //方法调用,单线程。
        myThread.start(); // 启动线程,多线程。 这个.statr() 继承自Thread类
        for (int i = 0; i <= 1000; i++) {
            System.out.println("===> 主线程 " + i);
        }
    }
}

[运行结果]

q2.png

方法2:通过 实现 Runnable 接口

[子线程代码] MyRunnable.java

package example02_runnable;
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 1000; i++) {
            System.out.println("===> 子线程 " + i);
        }
    }
}

[主线程代码] Test2.java

package example02_runnable;
public class Test2 {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        for (int i = 0; i <= 1000; i++) {
            System.out.println("===> 主线程 " + i);
        }
    }
}

[运行结果]

q1.png

2.线程中的相关方法

(1)设置优先级 setPrlorty()

优先级可以用从1到10的范围指定。10表示最高优先级(Thread.MAX_PRIORITY),1表示最低优先级(Thread.MIN_PRIORITY),5是普通优先级(Thread.NORM_PRIORITY,默认)。

优先级不能超出1-10的取值范围,否则抛出IllegalArgumentException

效果受操作系统影响,可能存在无效情况,因此不要有业务逻辑依赖于线程优先级,结果会无法预期

  • Thread.setPriority()用来设定线程的优先级
  • 在线程 start 方法被调用之前,线程的优先级应该被设定
  • 线程的优先级不能超过所属线程组的优先级,即可以通过控制线程组的优先级,来控制线程组下线程的最大优先级
  • 线程的优先级未设定时,默认所属的线程组的优先级
  • 未指定线程优先级时,所有线程都携带普通优先级
  • 优先级最高的线程在执行时被给予优先,最终由 CPU 调度程序决定哪一个线程被执行
  • 与线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级
  • 高优先级线程不一定先于低优先级的线程运行。

[子线程代码] MyThread3.java

package example03_priority;
public class MyThread3 extends  Thread{
    public MyThread3(String name){
        super.setName(name); //设置线程名字
    }
    @Override
    public void run() {
        //super.run();
        for(int i = 0;i<=1000;i++){
            System.out.println("===> 子线程 " +super.getName() + " "+ i);
        }
    }
}

[主线程代码] Test3.java (未设置线程优先级)

package example03_priority;
public class Test3 {
    public static void main(String[] args) {
        MyThread3 mt1 = new MyThread3("A");
        MyThread3 mt2 = new MyThread3("B");
        mt1.start();
        mt2.start();
    }
}

[运行结果]

w3.png

[主线程代码] Test3.java (设置线程优先级)

package example03_priority;
public class Test3 {
    public static void main(String[] args) {
        MyThread3 mt1 = new MyThread3("A");
        MyThread3 mt2 = new MyThread3("B");
        mt1.setPriority(1);
        mt2.setPriority(10);
        mt1.start();
        mt2.start();
    }
}

[运行结果]


w2.png

w1.png

(2)设置睡眠 sleep()

sleep,每隔x时间 去执行一个y操作。参数单位:毫秒

package example04_sleep;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyThread4 extends Thread{
    @Override
    public void run() {
        //让子系统不停的显示系统当前时间
        while(true){
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d = new Date();
            System.out.println(sdf.format(d));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        MyThread4 mt = new MyThread4();
        mt.start();
    }
}

q6.png

等待子线程执行完毕 join()

让主线程等待当前执行中子线程执行完毕。

[子线程代码]MyThread5.java

package example05_join;
public class MyThread5 extends Thread{
    @Override
    public void run() {
        // super.run();
        for(int i = 0;i<=1000;i++){
            System.out.println("===> 子线程 " + i);
        }
    }
}

[主线程代码]Test5.java

package example05_join;
public class Test {
    public static void main(String[] args) {
        MyThread5 myThread5 = new MyThread5();
        myThread5.start();
        for(int i = 0;i<=100;i++){
            System.out.println("===> 主线程 " + i);
        }
        try {
            myThread5.join(); //此时让主线程等待子线程执行完毕,再继续执行   
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("主线程执行完毕");
    }
}

让出CPU资源,让其他线程执行 yield()

让行,不代表不执行。具体谁先进行,依然由CPU决定,不能完全确定

[子线程代码]MyThread6.java

package example06_yield;
public class MyThread6 extends Thread {
    public MyThread6(String name) {
        super.setName(name);
    }
    @Override
    public void run() {
//        super.run();
        for (int i = 0; i <= 500; i++) {
            System.out.println(super.getName() + ":" + i);
            if (i % 10 == 0) {
                Thread.yield();
            }
        }
    }
}

[主线程代码]Test6.java

package example06_yield;
import example04_sleep.yield.MyThread6;
public class Test6 {
    public static void main(String[] args) {
        MyThread6 mt1 = new MyThread6("A");
        MyThread6 mt2 = new MyThread6("B");
        mt1.start();
        mt2.start();
    }
}

[运行结果截图]

q2.png

正如上图所见,让行是开发者设定的希望情况,但是并不代表一定成功让行。最终的决定权还是由CPU来决定

打断线程(尤其是正在睡眠中的线程) interrupt

[子线程代码]MyThread6.java

package example07.interrupt;
public class MyThread7  extends Thread{
    @Override
    public void run() {
//        super.run();
        System.out.println("线程即将进行休眠");
        try{
            Thread.sleep(1000000);
        }catch (InterruptedException e){
//            e.printStackTrace();
            System.out.println("异常:线程休眠被打断");
        }
        System.out.println("线程被激活");
    }
}

[主线程代码]Test6.java

package example07.interrupt;
public class Test7 {
    public static void main(String[] args) {
        MyThread7 myThread7 = new MyThread7();
        myThread7.start();
        for(int i = 0;i<=100;i++){
            System.out.println("===> 主线程 " + i);
        }
        myThread7.interrupt(); // 打断正在休眠中的子线程
    }
}

[运行结果截图]

q4.png

3.线程同步

线程同步:当多个线程共享同一个资源的时候,我们可以在某一个线程访问到这个资源的时候,将这个资源暂时封锁

等待当前线程执行结束,解锁该资源,其他线程才可以来继续执行。

总结:等待其他线程释放锁

目的:让线程更加安全

下面以银行取钱案例说明。

[账户对象]Account.java

package example08_sync;
public class Account {
    private  double balance;
    public Account(double balance){
        this.balance = balance;
    }
        public  void getMoney(){  //在方法声明上添加synchronized关键字 一旦执行到该方法,瞬间锁定对象Account
        if(this.balance<=0){return;}
        System.out.println("即将取走1000,目前金额为:"+this.balance);
        this.balance -= 1000;
        System.out.println("成功取走1000,目前金额为:"+this.balance);
    }
}

[取钱线程]GetMoneyThread.java

package example08_sync;
public class GetMoneyThread extends Thread{
    private Account acc;
    public GetMoneyThread(Account acc){
        this.acc = acc;
    }
    @Override
    public void run() {
        acc.getMoney();
    }
}

[测试类]Test8.java

package example08_sync;
public class Test8 {
    public static void main(String[] args) {
        //创建账户
        Account account  = new Account(1000);
        //创建ATM线程
        GetMoneyThread atm = new GetMoneyThread(account);
        //创建柜台线程
        GetMoneyThread table = new GetMoneyThread(account);
        //取钱
        atm.start();
        table.start();
    }
}

[运行结果]

q3.png

可以发现,设置的余额不足拦截(if(this.balance<=0){return;})没有生效,出现了账户余额为负数情况。

为避免这些情况的出现,因此需要线程同步。即将多个处理统一资源的线程,进行队列化管理(锁)

实现线程同步的方法

方法1:在方法声明上添加一个synchronized关键字

package example08_sync;
public class Account {
    private  double balance;
    public Account(double balance){
        this.balance = balance;
    }
    public synchronized void getMoney(){  //在方法声明上添加synchronized关键字 一旦执行到该方法,瞬间锁定对象Account
        if(this.balance<=0){return;}
        System.out.println("即将取走1000,目前金额为:"+this.balance);
        this.balance -= 1000;
        System.out.println("成功取走1000,目前金额为:"+this.balance);
    }
}

方法2:在方法内部添加一个synchronized关键字

package example08_sync;
public class Account {
    private  double balance;
    public Account(double balance){
        this.balance = balance;
    }
    public synchronized void getMoney(){  //在方法声明上添加synchronized关键字 一旦执行到该方法,瞬间锁定对象Account
        synchronized (this){
            if(this.balance<=0){return;}
            System.out.println("即将取走1000,目前金额为:"+this.balance);
            this.balance -= 1000;
            System.out.println("成功取走1000,目前金额为:"+this.balance);
        }
    }
}

方法3:手动上锁

该方法用的最少,因为容易出现忘记解锁。

package example08_sync;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
    private  double balance;
    private Lock lock = new ReentrantLock(); //创建锁
    public Account(double balance){
        this.balance = balance;
    }
   public  void getMoney(){  //在方法声明上添加synchronized关键字 一旦执行到该方法,瞬间锁定对象Account
        lock.lock(); // 上锁
        if(this.balance<=0){return;}
        System.out.println("即将取走1000,目前金额为:"+this.balance);
        this.balance -= 1000;
        System.out.println("成功取走1000,目前金额为:"+this.balance);
        lock.unlock(); //解锁
    }
}

死锁

线程同步时最容易发生的问题

eg:线程A锁定资源1后,等待访问资源2;线程2锁定资源2后,等待访问资源1。

[资源对象] ResourceObjec.java

package example09_dead;
public class ResourceObject {
    public static Object obj1 = new Object();
    public static Object obj2 = new Object();
}

[线程1] DeadLock1.java

package example09_dead;
public class DeadLock1 extends Thread{
    @Override
    public void run() {
//        super.run();
        synchronized (ResourceObject.obj1){
            System.out.println("线程1 第一个资源锁定");
            try{
                //通过休眠使CPU去执行其他线程
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (ResourceObject.obj2){
                System.out.println("线程1 第二个资源锁定");
                System.out.println("线程1执行完毕");
            }
        }
    }
}

[线程2] DeadLock2.java

package example09_dead;
public class DeadLock2 extends Thread{
    @Override
    public void run() {
//        super.run();
        synchronized (ResourceObject.obj2){
            System.out.println("线程2 第二个资源锁定");
            try{
                //通过休眠使CPU去执行其他线程
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (ResourceObject.obj1){
                System.out.println("线程2 第一个资源锁定");
                System.out.println("线程2执行完毕");
            }
        }
    }
}

[测试] Test9.java

package example09_dead;
public class Test9 {
    public static void main(String[] args) {
        DeadLock1 dl1 = new DeadLock1();
        DeadLock2 dl2 = new DeadLock2();
        dl1.start();
        dl2.start();
    }
}

[运行结果]

q2.png

线程的生命周期

  • [1]创建线程
  • ---[start()]--> [2]就绪状态
  • ---[CPU调度]--> [3]运行状态
  • ---[stop()]--> (跳转到4)
  • ---[IO操作、sleep]--> 阻塞状态 ---[IO结束、sleep()结束]--> (跳转到2)
  • --------------> [4]消亡状态

生产者消费者模型

案例:实现对视频进行审查,如果是不良视频,则对视频进行删除

[视频对象] Video.java

package example10_check;
public class Video {
    private String name;
    public Video (String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

[检查视频] CheckVideo.java

package example10_check;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class CheckVideo extends Thread{
    private static AtomicInteger i = new AtomicInteger(); //AtomicInteger原子型整数。特点:线程安全 效果等同于 int i = 0,但是比int i = 0 更安全。
    //将视频添加到缓冲区(队列)
    private BlockingQueue<Video> videos;
    public  CheckVideo(BlockingQueue<Video> videos){
        this.videos = videos;
    }
    @Override
    public void run() {
        while (true){
            String name = "不良视频"+i.incrementAndGet();// 相当于i++
            Video v = new Video(name);
            //将视频添加到队列
            try {
                System.out.println("发现不良视频"+name);
                videos.put(v);
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

[删除视频] DelVideo.java

package example10_check;
import java.util.TreeMap;
import java.util.concurrent.BlockingQueue;
public class DelVideo extends Thread {
    private BlockingQueue<Video> videos;
    public DelVideo(BlockingQueue<Video> videos){
        this.videos = videos;
    }
    @Override
    public void run() {
        while(true){
            try{
                Video video = videos.take();
                System.out.println("删除不良视频"+video.getName());
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

[测试] Test10.java

package example10_check;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Test10 {
    public static void main(String[] args) {
        BlockingQueue<Video> videos = new LinkedBlockingQueue<Video>();
        //创建3个检查视频的线程
        CheckVideo ck1 = new CheckVideo(videos);
        CheckVideo ck2 = new CheckVideo(videos);
        CheckVideo ck3 = new CheckVideo(videos);
        //创建两个删除视频的线程
        DelVideo d1 = new DelVideo(videos);
        DelVideo d2 = new DelVideo(videos);
        ck1.start();
        ck2.start();
        ck3.start();
        d1.start();
        d2.start();
    }
}

[运行结果]

q1.png

相关文章
|
6天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
5天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
5天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
4天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
7天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
45 1
C++ 多线程之初识多线程
|
28天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
28天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
17 2
|
28天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
29 2
|
28天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
34 1