多线程

创建线程

  1. 线程是进程中的执行单元,一个进程可以有多个线程。

    Java VM 启动的时候会有一个进程—-java.exe。
    该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。

    扩展:其实更细节说明jvm,jvm启动不止一个线程,至少还有一个负责垃圾回收机制的线程。

  2. 创建线程的第一种方式:继承Thread类
    步骤:

    • 定义类继承Thread。
    • 复写Thread类中的run方法。run方法用于存储线程要运行的代码
    • 调用线程的start方法。只有调用start方法,线程才执行

    link1
    发现运行结果每一次都不同。
    因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以把多线程的运行行为想象成在互相抢夺cpu的执行权。
    这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。

  3. 线程都有自己的名称,默认是 Thread-编号 该编号从0开始。

    获取当前线程对象: static Thread currentThread()
    获取线程名称:getName()
    设置线程名称:setName()或者构造函数。

  4. 如果想要实现一个多窗口同时卖票的应用,为了使多个线程使用一个票数变量,可以使用定义静态变量的方法 link2
    但是静态变量生命周期太长,十分浪费资源。所以就需要一种新的创建线程的方法。

  5. 创建线程的第一种方式:实现Runable接口

    步骤:

    1. 定义类实现Runnable接口
    2. 覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中。
    3. 通过Thread类建立线程对象。
    4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

    实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。

    两种方式区别

    • 继承Thread : 线程代码存放Thread子类run方法中。
    • 实现Runnable,线程代码存在接口的子类的run方法。

    link3

多线程的安全问题

  1. 同步代码块解决安全问题

    当多条语句在操作同一个线程 共享数据 时,一个线程对多条语句只执行了一部分,还没有执行完,此时 另一个线程参与进来执行就会导致共享数据的错误。例如将买票问题 link1
    为了解决这个问题,可以采用 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行 的方法解决。

    在java中,可以使用同步代码块解决

    synchronized(对象)
    {
        //code
    }
    

    link2
    加入同步锁后比较消耗资源。

  2. 以一个银行存款的例子分析同步代码块。link3

  3. 同步函数解决安全问题

    当整个函数中的代码都需要同步时,可以使用同步函数。
    修改link3中的银行存款的例子 link4
    修改卖票的例子 link5

    同步函数中没有定义Object对象作为锁,因为同步函数中使用的锁是this。
    静态同步函数中使用的锁是Class(类名.class),因为静态方法中不可以定义this。

  4. 单例设计模式
    懒汉式:

    class Single{
        private static final Single s = new Single();
        private Single(){}
        public static Single getInstance(){
            return s;   //只有一句代码,没有同步问题
        }
    }

    饿汉式:

    class Single{
        private static Single s = null;   //s是共享数据,可能产生安全问题
        private Single(){}
    
        public static  Single getInstance(){
            if(s==null){    //双重判断
                synchronized(Single.class){
                    if(s==null) 
                        s = new Single();
                }
            }
            return s;
        }
    }

    懒汉式和饿汉式的不同:

    • 懒汉式的特点在于实例的延迟加载,多线程访问时会出现安全问题,所以要加同步解决,使用双重判断的方法解决效率问题,使用的锁是该类的字节码文件对象,就是类名.class 文件
  5. 死锁
    死锁产生的原因是 同步中嵌套同步,但是用的是不同的锁。
    例如 link6

多线程之间的通信

  1. 假设现在有一个程序,一个线程输入数据,一个线程输出数据。可能出现不同步的现象发生。link1
    为了解决这个现象,需要加入同步。link2
    现在打印的输出依然不是期望中的交替打印,可以使用 等待唤醒机制 来实现。link3

    wait()notify()notifyAll() 都要使用在同步中,因为要对持有监视器(锁)的线程进行操作。
    这几个方法是定义在Object类中的,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

    对以上代码的优化。link4

  2. 生产者消费者模型

    假设需求:一个生产者生产商品,一个消费者购买商品,使两个线程能够交替执行,跟以上的代码类似 link5

    但是当有多个线程时,如有4个线程(2个消费者,2个生产者)就会出现同一个商品被消费2次,或生产2个商品只有一个被消费的情况 link6

    修改上面的代码,只需要

    • 27行和39行的if改为while,多次判断标志
    • 35行和46行的notify()修改为notifyAll(),唤醒全部线程(即将对方线程也唤醒)。即可
    • link7
  3. JDK 1.5对于同步进行了升级,用ConditionLock取代了原来的synchronized。所以,对于上述link7的修改就是 link8

    一个Lock可以绑定多个Condition,所以对于生产者消费者模型,可以让生产者只唤醒消费者,消费者只唤醒生产者 link9

  4. 停止线程

    JDK中有stop()方法停止线程,但是已经过时。
    现在想要停止线程,方法就是要让 run()方法结束
    如 利用flag控制循环 link10

    但是如果有wait()方法,程序进入冻结状态,无法读取标记,程序始终无法终止。此时就要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
    为此,Thread类提供了interrupt()方法。
    调用interrupt()方法,会将程序强制恢复到运行状态,但是会抛出异常,可以直接在catch中更改flag标记。link11

  5. 守护线程

    守护线程可以理解为后台线程,当前台线程运行时,守护线程也运行,当前台线程结束时,守护线程自动结束。link12

  6. Join()线程

    当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
    join可以用来临时加入线程执行。
    link13

  7. 设置优先级
    setPriority(),一般使用常量MAX_PRIORITY(最高优先级),
    MIN_PRIORITY (最低优先级),NORM_PRIORITY (默认优先级)

  8. 多种不同的创建线程的方法 link14

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值