JAVA-多线程

多线程概述

进程与线程

  • 进程

是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间

  • 线程

是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少
有一个线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分
成若干个线程

线程调度

  • 分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间

  • 抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,
只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时
刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使
用率更高。

同步与异步

  • 同步

排队执行 , 效率低但是安全.

  • 异步

同时执行 , 效率高但是数据不安全

多线程内存图

在这里插入图片描述

- 一个线程一个栈空间,共享堆内存和方法区

实现线程的方式

第一种方式:继承Thread类,重写run方法

public class MyThread {
    public static void main(String[] args) {
        ThreadTest01 test01 = new ThreadTest01();
        test01.start();//start()方法的作用:启动一个分支线程,在JVM中开辟新的栈空间
        //这里的代码运行在主线程main方法中
        for (int i=0;i<100;i++){
            System.out.println("主线程->"+i);
        }
    }
}
class ThreadTest01 extends Thread{
    @Override
    public void run() {//run()方法和main方法平级,都在栈的底部.
        for (int i=0;i<100;i++){
            System.out.println("分支线程->"+i);
        }
    }
}
  • 注意:start()方法的作用:启动一个分支线程,在JVM中开辟新的栈空间,任务完成后,这个方法瞬间就结束了,启动线程成功后会自动调用run()方法,并且run()方法在分支栈的底部(压栈)。

第二种方式:实现Runnable接口,实现run方法

public class ThreadTest02 {
    public static void main(String[] args) {
       /* MyRunnable mr = new MyRunnable();
        Thread tr = new Thread();*/
       //上面两行代码合并如下
       Thread tr = new Thread(new MyRunnable());
        tr.start();
        for (int i=0;i<100;i++){
            System.out.println("主线程->"+i);
        }

    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println("分支线程->"+i);
        }
    }
}

  • 匿名内部类创建线程:
public class ThreadTest02 {
    public static void main(String[] args) {
        //匿名内部类的方式
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<100;i++){
                    System.out.println("分支线程->"+i);
                }
            }
        });
        t.start();
        for (int i=0;i<100;i++){
            System.out.println("主线程->"+i);
        }
    }
}

线程的常用方法

public class ThreadTest03 implements Runnable {
    public static void main(String[] args) {
        Thread r = new Thread(new ThreadTest03());
        r.start();//创建线程
        r.setName("分支线程一:");//修改线程名字
        String name = r.getName();//获取线程名字
        //获取当前线程,在mian方法中,当前线程就是main
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());//获取当前线程的名字。
        try {
            /*静态方法,单位是毫秒。
            让当前(main)线程进去休眠,进去“阻塞状态”,
            放弃占用CPU时间片,让给其他线程使用*/
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断线程睡眠
        r.interrupt();

    }
    @Override
    public void run() {

    }
}

线程的生命周期

在这里插入图片描述

线程同步机制

synchronized语法

  • synchronized的()写什么?

假设 t1, t2, t3, t4,t5,有5个线程。你只希望t1,t2,t3排队,t4,t5不排队。
()里面一定要写t1,t2,t3的共享对象。而这个共享对象对t4,t5不共享。

//第一种:同步代码块越小效率越高
synchronized (线程共享对象){     
    }
    }
    //第二种:实体在实体方法上使用synchronized,
    表示共享对象一定是This,并且同步代码块是整个方法体
   
    第三种:在静态方法上使用synchronized,表示类锁,
    就算创建100个对象,类也只有一把锁。
    对象锁:1个对象一把锁,100个对象100把锁。
    类锁:100个对象也可能只是一把类锁

那些变量存在线程安全问题:

  • JAVA三大变量:
    1:实例变量:在堆区
    2:静态变量:在方法区
    3:局部变量:在栈中
    以上三大变量中:
    局部变量永远都不会存在线程安全问题。因为局部变量不共享。
    一个线程一个栈,局部变量在栈中,不共享。
    实例变量在堆区,堆区只有一个。
    静态变量在方法区,方法区只有一个。
    堆区和方法区都是多线程共享的,所以存在线程安全问题。

死锁

  • 死锁指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
  • 生活中的一个实例,两个人面对面过独木桥,甲和乙都已经在桥上走了一段距离,即占用了桥的资源,甲如果想通过独木桥的话,乙必须退出桥面让出桥的资源,让甲通过,但是乙不服,为什么让我先退出去,我还想先过去呢,于是就僵持不下,导致谁也过不了桥,这就是死锁。

开发中应该如何解决线程安全问题?

  • synchronized会让程序执行效率降低,用户体验不好。系统用户的吞吐量降低,用户体验差。在不得已的情况下选择synchronized。
  • 尽量使用局部变量代替“实例变量”和“静态变量”。
  • 如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对象一个对象,100个线程对象100个对象,对象不共享就没有数据安全问题)
  • 如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

守护线程

  • JAVA线程分为两大类:
    1:用户线程
    2:守护线程(后台线程)
    其中具有代表性的就是:垃圾回收线程(守护线程)。

  • 收获线程的特点:
    一般守护线程是一个死循环,所以的用户线程只要结束,守护线程自动结束。
    注意:主线程main方法是一个用户的线程。

  • 守护线程可以用到什么地方呢?
    比如每天00:00的时候系统数据自动备份。
    这个需要使用到定时器,并且我们可以将定时器设置为守护线程。所以用户线程结束,守护线程自动退出。

  • 定时器
    间隔特定的时间,执行特定的程序。比如:每周要进行银行账户总账操作。每天要进行数据的备份。
    在实际开发中,每隔多久执行一段特定的程序,这种需求很常见,那么在JAVA中其实可以采用多种方式实现:
    可以使用sleep()方法,设置睡眠时间,这种方式是最原始的定时器(比较LOW)
    在JAVA类库中已经写好了一个定时器:util .Timer,可直接拿来使用。不过这种方式目前开发也很少用。因为有许多框架支持定时任务。
    在实际开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只需要进行简单的配置,就可以完成定时任务。

关于Object类中的wait和notify方法(生产者与消费者模式)

  • wait和notify方法不是线程对象的方法,是JAVA中任何一个对象都用的方法,因为这两个方式是Object类中自带的。
  • wait和notify方法不是通过线程对象调用的。

wait()方法的作用:

Object 0 = new Object
0.wait():
表示:让正在O对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止。

notity()方法的作用

Object 0 = new Object
0.notity():
表示:唤醒正在O对象上等待的线程.
0.notityAll():唤醒正在O对象上等待的所以线程.

生产者与消费者模式

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值