java进阶学习——7 多线程

11 篇文章 0 订阅


大数据学习路线,从零开始学大数据(点击跳转,学习不迷路)

Java最详细教程 (点击跳转,学习不迷路)

七、多线程

1. 什么是进程?什么是线程?

进程是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景 / 执行单元。
一个进程可以启动多个线程。

对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车后,会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收站负责看护,回收垃圾。
最起码, 现在的java程序中至少有两个线程并发,
一个是垃圾回收线程,一个是执行main方法的主线程。

线程A和线程B:
堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。
假如有10个线程,会有10个栈空间,每个栈和每个栈之间
互不干扰,各自执行各自的,这就是多线程并发。

java中之所以有多线程机制,目的是为了提高程序的执行效率。

一个线程一个栈:
在这里插入图片描述

在这里插入图片描述

java语言中实现线程有两种方式
java支持多线程机制,并且java已经将多线程实现了,只需要继承就可以了。
第一种方式: 编写一个类,直接继承java.lang.Thread ,重写run方法。
在这里插入图片描述

第二种方式: 编写一个类,实现java.lang.Runnable接口,实现run方法。

在这里插入图片描述
注意:第二种方式比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。

2. 线程的生命周期:

新建状态–> 就绪状态–>运行状态(–>阻塞状态–>就绪状态 )–>死亡状态

在这里插入图片描述

 获取线程的对象: 
 			Thread t = Thread.currentThread();
 			返回值 t 就是当前线程对象。
 						
 设置线程的名字:   线程对象.setName("线程名字");
 获取线程的名字:  线程对象.getName();

3. 线程的Sleep()方法

Static void sleep(long millis)

  1. 静态方法 :Thread.sleep(1000)
  2. 参数是毫秒
  3. 作用:让当前线程进入休眠,进入”阻塞状态“,放弃占有CPU时间片,让给其他线程使用
    这行代码出现在A线程中,A线程就会进入休眠。
    这行代码出现在B线程中,B线程就会进入休眠。

线程名.interrupt() : 睡眠终断 (终断t线程的睡眠:这种终断睡眠的方式依靠了Java的异常处理机制)

强行终止线程的进行:

public class ThreadSleepTest04 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable3());

        t.setName("t");
        t.start();

//        模拟5秒
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        5秒之后强行终止t线程
        t.stop();  // 已过时,不建议使用

    }
}

class MyRunnable3 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

合理的终止进程方式:

public class ThreadSleepTest05 {
    public static void main(String[] args) {
        myRunnable5 r5 = new myRunnable5();
        Thread t = new Thread(r5);

        t.setName("t");
        t.start();

//        模拟5秒
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
//            e.printStackTrace();
        }

//        终止线程

//        r5.run = false;
    }
}

class myRunnable5 implements Runnable {
    //    打印一个布尔印记
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (run) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000); 
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
//                终止当前进程
//                在结束前可以保存数据
//                save......

                return;
            }

        }
    }
}

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

在这里插入图片描述

让位 :让当前线程暂停,回到就绪状态,让给其他线程。
静态方法:Thread.yield();

合并: join()方法
如:分支线程合并到主线程中,主线程受阻碍,分支线程执行直到结束,再继续执行主线程。

4. 线程安全

4.1 synchronized同步机制(锁)

在这里插入图片描述

什么环境下会存在安全问题?
在这里插入图片描述

怎么解决线程安全问题?

 使用 ” 线程同步机制 “ 

在这里插入图片描述

线程同步,有两个编程模型:

在这里插入图片描述
线程同步机制的语法是:

    synchronized (多线程共享的对象){
                 // 线程同步代码块

               }
                 synchronized后买你小括号中传的”数据“是相当关键的
                 这个数据必须是多线程共享的数据,才能达到多线程排队。

在这里插入图片描述
在java语言中,任何一个对象都有”一把锁“其实这把锁就是标记。(只是把它叫做锁)
100个对象,100把锁。1个对象1把锁。

在这里插入图片描述

运行过程:
进入锁池,可以理解为是一种阻塞状态

在这里插入图片描述

在这里插入图片描述

在实例方法上使用synchronizatized
在这里插入图片描述
synchronizatized使用在实例方法上有什么优点?
代码写的少乐,简约代码。

如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。

在这里插入图片描述
总结synchronizatized的写法:

在这里插入图片描述

4.2 死锁

synchronizatized在开发中最好不要嵌套使用,否则可能会导致死锁现象的发生。

在这里插入图片描述

死锁的代码例子:

public class deadlock01 {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new MyThread1(o1, o2);
        Thread t2 = new MyThread2(o1, o2);
        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread {
    Object o1;
    Object o2;
    public MyThread1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2) {
            }
        }
    }
}
class MyThread2 extends Thread {
    Object o1;
    Object o2;

    public MyThread2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized (o2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1) {
            }
        }
    }
}

5. 开发中怎么解决线程安全问题

在这里插入图片描述

6. 守护线程

6.1 守护线程的介绍

java语言中线程分为两大类:
一类是:用户线程
一类是: 守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。

6.2 守护线程的特点:

	一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意: 主线程main方法是一个用户线程。

守护线程用在什么地方?

在这里插入图片描述
实现守护线程:

     线程名.setDaemon(true);
public class ThreadTest07 {
    public static void main(String[] args) {
        Thread t = new BakDataThread();

        t.setName("备份数据的线程");
//       启动线程前,将线程设置为守护线程。
        t.setDaemon(true);
        t.start();
//        主线程:主线程是用户线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "------>" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class BakDataThread extends Thread {
//   即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
    public void run() {
        int i = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + "----->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

当主线程(用户线程)结束后,即使线程是死循环,也会自动终止线程。
在这里插入图片描述

7. 定时器

定时器的作用:
间隔特定的时间,执行特定的程序。

	 每周要进行银行账户的总帐操作。
	 每天要进行数据的备份操作。
	 
	 在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,
	 那么在java中其实可以采用多种方式实现:
	 	 可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行
	 	 任务。这种方式是最原始的定时器。(比较low)

		在java的类库中已经写好了一个定时器:java.util.Timer ,可以直接拿来用。
		不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
		
		在实际的开发中,目前使用的较多的是spring框架中提供的springTask框架,这个框架
		只要进行简单的配置,就可以完成定时的任务。

实现定时器:

public class TimerTest {
    public static void main(String[] args) throws Exception{

//        创建定时器对象
        Timer timer = new Timer();
//        Timer timer = new Timer(true);   守护线程的方式

//        指定定时任务
//        time.schedule(定时任务,第一次执行时间,间隔多少执行一次)
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date fisrtTime =sdf.parse("2023-01-07 11:25:30");
        timer.schedule(new LogTimerTask(),fisrtTime,1000*10);
    }
}
// 编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{
    @Override
    public void run() {
//        编写执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime+ "完成了一次数据备份!");
    }
}

运行结果:

每隔10秒自动进行数据备份

在这里插入图片描述

8. 实现线程的第三种方式:FutureTask方式,实现callable接口(JDK8新特性)

在这里插入图片描述

实现线程的第三种方式: 实现callable接口

     这种方式的优点:可以获取到线程的执行结果
     这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程阻塞,效率较低。
public class ThreadTest08 {
    public static void main(String[] args) {
//        第一步: 创建一个“未来任务类” 对象
//        参数非常重要,需要给一个callable接口实现类对象.
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
//                call() 方法就相当于run方法。只不过这个有返回值
//                线程模拟一个任务,执行之后可能会有一个执行官结果
//                模拟执行
                System.out.println("call method begin!");
                Thread.sleep(1000 * 10);
                System.out.println("call method over!");
                int a = 100;
                int b = 200;
                return a + b;  // 自动装箱(300结果变成个Interger)
            }
        });
//        创建线程对象
        Thread t = new Thread(task);
//        启动线程
        t.start();
//        这里是main方法,这是在主线程种
//        在主线程种,怎么获取t线程的返回结果?
//        get()方法的执行会导致“当前线程阻塞”
        try {
            Object obj = task.get();
            System.out.println("线程执行结果:" + obj);
        } catch (InterruptedException e) {
//            e.printStackTrace();
        } catch (ExecutionException e) {
//            e.printStackTrace();
        }
        /* main 方法这里的程序想要执行必须等待get()方法的结束
         *  而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果
         *  而另一个线程执行是需要时间的
         * */
        System.out.println(" hello world!");
    }
}

运行结果:

在这里插入图片描述

9. Object类中的wait 和 notify方法。(生产者和消费者模式!)

在这里插入图片描述

用图表示:

在这里插入图片描述

生产者和消费者模式:

在这里插入图片描述
实现生产者于消费者模式:

在这里插入图片描述
在这里插入图片描述
例子:

public class ThreadTest09 {
    public static void main(String[] args) {
//        创建一个仓库对象,共享的
        List list = new ArrayList();
//        创建两个线程对象
//        生产者对象
        Thread t1 = new Thread(new producer(list));
//        消费者对象
        Thread t2 = new Thread(new consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        t1.start();
        t2.start();
    }
}

class producer implements Runnable {
    //    仓库
    private List list;

    public producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        synchronized (list) {
            while (true) {
                synchronized (list) {
                    if (list.size() > 0) {  // 大于0 ,说明仓库中已经有一个元素了
                        try {
                            // 当前线程进行等待状态,并且释放producer之前占有的list集合的锁
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 程序能执行到这里说明仓库是空的,可以生产
                    Object obj = new Object();
                    list.add(obj);
                    System.out.println(Thread.currentThread().getName() + "----->" + obj);

                    try {
                        Thread.sleep(1000 * 3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 换醒消费者进行消费
                    list.notify();
                }
            }
        }
    }
}
class consumer implements Runnable {
    private List list;
    public consumer(List list) {
        this.list = list;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (list) {
                if (list.size() == 0) {
                    try {
                        //  仓库已经空了
                        //  消费者线程等待,释放掉list集合的锁
                        list.wait();
                    } catch (InterruptedException e) { 
                        e.printStackTrace();
                    }
                }
                // 程序能执行到此处,说明仓库中有数据,进行消费.
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "------->" + obj);

                try {
                    Thread.sleep(1000 * 3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 唤醒生产者生产
                list.notify();
            }
        }
    }
}

运行结果:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海码儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值