Java多线程(线程创建、线程安全问题解决、线程状态)

一.基础概念

1.并发和并行
在这里插入图片描述
2.进程与线程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创 建、运行到消亡的过程。
在这里插入图片描述
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程 中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
在这里插入图片描述
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

3.线程调度
分时调度: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度: 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为 抢占式调度

二.主线程

在这里插入图片描述

三.多线程原理

1.创建多线程程序的**第一种方式:**创建Thread类的子类

java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类

实现步骤:
    1.创建一个Thread类的子类
    2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    3.创建Thread类的子类对象
    4.调用Thread类中的方法start方法,开启新的线程,执行run方法
         void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
         结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其 run 方法)。
         多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行

例子:
MyThread
在这里插入图片描述
domo01Thread
在这里插入图片描述
多线程随机性打印结果:
在这里插入图片描述
多线程内存图解:
在这里插入图片描述

2.创建多线程程序第二种方式: 实现Runnable接口(尽量多使用,线程任务与线程开启相解耦)

java.lang.Runnable
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
java.lang.Thread类的构造方法
Thread(Runnable target) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。

实现步骤:
    1.创建一个Runnable接口的实现类
    2.在实现类中重写Runnable接口的run方法,设置线程任务
    3.创建一个Runnable接口的实现类对象
    4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    5.调用Thread类中的start方法,开启新的线程执行run方法

实现Runnable接口创建多线程程序的好处:
    1.避免了单继承的局限性
        一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
        实现了Runnable接口,还可以继承其他的类,实现其他的接口
    2.增强了程序的扩展性,降低了程序的耦合性(解耦)
        实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
        实现类中,重写了run方法:用来设置线程任务
        创建Thread类对象,调用start方法:用来开启新线程 。如下例子:

在这里插入图片描述

3.用匿名内部类方式创建线程

匿名内部类方式实现线程的创建:

匿名:没有名字
内部类:写在其他类内部的类

匿名内部类作用:简化代码
    把子类继承父类,重写父类的方法,创建子类对象合一步完成
    把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

格式:
    new 父类/接口(){
        重复父类/接口中的方法
    };
例:
1.线程的父类是Thread
    // new MyThread().start();

    new Thread(){
        //重写run方法,设置线程任务
        @Override
        public void run() {
            for (int i = 0; i <20 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+"黑马");
            }
        }
    }.start();
    
    2.线程的接口Runnable
    //Runnable r = new RunnableImpl();//多态
    // new Thread(r).start()
    
    Runnable r = new Runnable(){
        //重写run方法,设置线程任务
        @Override
        public void run() {
            for (int i = 0; i <20 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+"程序员");
            }
        }
    };
    new Thread(r).start();
    
3.线程的接口简化:
 new Thread( new Runnable(){
        //重写run方法,设置线程任务
        @Override
        public void run() {
            for (int i = 0; i <20 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+"传智播客");
            }
        }
    }).start();

四.多线程安全问题

在这里插入图片描述

1.产生原因:

单线程不会产生线程安全问题,多线程不共享资源也不会产生线程安全问题,多线程共享资源会产生线程安全问题

分析原因:
在这里插入图片描述

2.解决线程安全问题

卖票案例出现了线程安全问题

问题: 卖出了不存在的票和重复的票
解决线程安全问题的第一方案:使用同步代码块

 格式:
    synchronized(锁对象){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

注意:
    1.通过代码块中的锁对象,可以使用任意的对象
    2.但是必须保证多个线程使用的锁对象是同一个
    3.锁对象作用:
        把同步代码块锁住,只让一个线程在同步代码块中执行

例子:
main方法
在这里插入图片描述
接口实现类
在这里插入图片描述
同步原理:
在这里插入图片描述
解决线程安全问题第二方案:使用同步方法

 使用步骤:
    1.把访问了共享数据的代码抽取出来,放到一个方法中
    2.在方法上添加synchronized修饰符

  格式:定义方法的格式
  修饰符 synchronized 返回值类型 方法名(参数列表){
    可能会出现线程安全问题的代码(访问了共享数据的代码)
 }
 
 1./*
    定义一个同步方法
    同步方法也会把方法内部的代码锁住
    只让一个线程执行
    同步方法的锁对象是谁?
    就是实现类对象 new RunnableImpl()
    也是就是this
 */
 
public /*synchronized*/ void payTicket(){
    synchronized (this){
        //先判断票是否存在
        if(ticket>0){
            //提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

2./*
    静态的同步方法
    锁对象是谁?
    不能是this
    this是创建对象之后产生的,静态方法优先于对象
    静态方法的锁对象是本类的class属性-->class文件对象(反射)
 */

public static /*synchronized*/ void payTicketStatic(){
    synchronized (RunnableImpl.class){
        //先判断票是否存在
        if(ticket>0){
            //提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

解决线程安全问题第三方案:使用Lock方法
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock接口

使用步骤:
    1.在成员位置创建一个ReentrantLock对象
    2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
    3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

在这里插入图片描述

五.线程状态

在这里插入图片描述

发布了6 篇原创文章 · 获赞 4 · 访问量 2531
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览