Java多线程

多线程

线程与进程区别

进程是所有线程的集合,每一个线程是进程中的一条执行路径。
优点:使用多线程可以提高程序的执行效率。

创建线程的三种方式

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

    package com.cjj.thread;
    
    /**
     * @ClassName ThreadStudy_1
     * @Date 2021/06/27 23:11:25
     * @Created by ccc-j
     * @email ccc-ju@outlook.com
     */
    
    /**
     * 什么是进程?
     * 进程其实就是一个应用程序,进程是所有线程的集合.
     * 什么是线程?
     * 线程就是一条执行路径.
     * main 主线程 自己创建的是子线程
     * gc线程 专门回收垃圾
     */
    public class ThreadStudy_1 {
    
        public static void main(String[] args) {
            System.out.println("开始创建线程:");
            // 1.继承Thread类,重写run方法
            // 2.调用继承的类的start方法,执行线程
            CreateThread createThread = new CreateThread();
            // 启动线程,调用start方法,不是run方法,调用run方法相当于主线程执行
            createThread.start();
            System.out.println("线程已经开始启动,main");
            for(int i = 0; i < 100; i++){
                System.out.println("main() i: " + i);
            }
        }
    
    }
    
    class CreateThread extends Thread{
    
        /**
         * run方法 需要线程的执行代码
         */
        @Override
        public void run() {
            for(int i = 0; i < 100; i++){
                System.out.println("run() i: " + i);
            }
        }
    }
    
    
  • 第二种,实现Runable接口,重写run方法

    package com.cjj.thread;
    
    /**
     * @ClassName ThreadStudy_2
     * @Date 2021/06/27 23:45:14
     * @Created by ccc-j
     * @email ccc-ju@outlook.com
     */
    public class ThreadStudy_2 {
    
        public static void main(String[] args) {
            System.out.println("创建线程开始!main");
            // 1.定义一个类,实现Runnable接口,重写run方法
            CreateRunnable createRunnable = new CreateRunnable();
            // 2.实现Runnable方法时候与继承Thread的区别
            Thread thread = new Thread(createRunnable);
            thread.start();
            System.out.println("创建线程结束!main");
            for(int i = 0; i < 100; i++){
                System.out.println("main() i: " + i);
            }
        }
    
    }
    
    class CreateRunnable implements Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 100; i++){
                System.out.println("run() i: " + i);
            }
        }
    }
    
    
  • 第三种,使用匿名内部类

    package com.cjj.thread;
    
    /**
     * @ClassName ThreadStudy_3
     * @Date 2021/06/27 23:58:49
     * @Created by ccc-j
     * @email ccc-ju@outlook.com
     */
    public class ThreadStudy_3 {
        public static void main(String[] args) {
            System.out.println("创建线程成功!");
            // 匿名内部类方式创建线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i = 0; i < 100; i++){
                        System.out.println("run() i: " + i);
                    }
                }
            }).start();
            for(int i = 0; i < 100; i++){
                System.out.println("main() i: " + i);
            }
            System.out.println("创建线程结束!");
        }
    }
    
    

使用继承Thread类还是使用实现Runnable接口好?

实现接口更好,
java是单继承,多实现,实现了接口还可以继续使用继承,继承了一个类之后就不能再次继承其它类。

启动线程是使用调用start方法还是run方法?

开始执行线程,开启线程不是调用run方法,而是start方法。

多线程运行状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdcAIpV4-1626624724858)(C:\Users\ccc-j\AppData\Roaming\Typora\typora-user-images\image-20210628224338923.png)]

  • 新建状态:
    • 当new 操作符创建一个线程时,线程还没开始运行,此时线程处于新建状态。
  • 就绪状态:
    • 一个新创建的线程并不会立即执行,要执行线程,需要调用start()方法。当线程调用了start方法即启动了线程,start()方法创建线程运行的系统资源并调度线程运行run()方法。当start()方法返回后线程就处于就绪状态。
  • 运行状态:
    • 当线程获得cpu时间后,它才进入运行状态,真正开始执行run()方法。
  • 阻塞状态:
    • 线程运行过程中,可能由于各种原因进入阻塞状态
      1. 线程调用sleep方法进入阻塞状态
      2. 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者。
      3. 线程试图得到一个锁,而该锁正被其它线程持有。
      4. 线程在等待某个出发条件。
  • 死亡状态:
    • 有两个原因会导致线程死亡:
      1. run方法正常退出而自然死亡
      2. 一个未捕获异常终止了run方法而使线程死亡。
    • 为了确定线程在当前是否存活者(要么是可运行的,要么是阻塞状态中的),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true,如果线程是new状态且是不可运行的,或者线程死亡了,则返回false。

多线程分批处理数据

需求:目前有10w个用户,现在需要做活动,给每个用户发送祝福短信。
为了提高程序的效率,使用多线程技术分批发数据。没开一个线程,都会占用CPU资源
package com.cjj.thread.sms_send;

/**
 * @ClassName BatachThread
 * @Date 2021/06/28 23:38:38
 * @Created by ccc-j
 * @email ccc-ju@outlook.com
 */

import com.cjj.thread.ListUtils;
import com.cjj.thread.entity.UserEntity;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

/**
 * 多线程处理数据
 */
public class BatachThread {

    public static void main(String[] args) {
        // 1.初始化用户
        List<UserEntity> userEntities = initUser();
        // 2.定义每个线程最多跑多少数据
        int userCount = 200;
        // 3.计算线程数并且计算每个线程跑多少数据
        List<List<UserEntity>> lists = ListUtils.splitList(initUser(), userCount);
        for(int i = 0; i < lists.size(); i++){
            List<UserEntity> entityList = lists.get(i);
            // 4.让子线程分批异步处理数据
            new UserThread(entityList).start();
            //System.out.println("i:" + i + "--" + entityList.toString());
        }
    }

    public static List<UserEntity> initUser(){
        List<UserEntity> userEntities = new ArrayList<>();
        for(int i = 1; i <= 1000; i++){
            userEntities.add(UserEntity.builder().userId("id:" + i).userName("name:" + i).build());
        }
        return userEntities;
    }

}
@Data
class UserThread extends Thread {

    List<UserEntity> list;

    public UserThread(List<UserEntity> list){
        this.list = list;
    }

    @Override
    public void run() {
        for(UserEntity entity : list){
            /*try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // none
            }*/
            System.out.println("name:" + getName() + "--" + entity.toString());
        }
    }
}

多线程同步

什么是线程安全?

  • 当多个线程同时共享同一个全局变量或静态变量,做写的操作的时,可能会引发数据冲突问题,也就是线程安全问题。读操作不会发生数据冲突问题。

线程安全问题如何解决?

  • 使用同步代码块
  • 同步函数
  • 静态同步函数

Synchronized

使用synchronized关键字包裹起来的代码能保证一次只有一个线程执行
同步函数使用的是什么锁?同步函数使用this锁
package com.cjj.thread_safe;class ThreadTrain3 implements Runnable {    // 车票总数    private int count = 100;    private Object object = new Object();    public boolean flag = true;    @Override    public void run() {        // 线程1 flag为true        if (flag) {            while (count > 0) {                synchronized (this) {                    try {                        Thread.sleep(40);                    } catch (Exception e) {                    }                    if (count > 0) {                        System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票");                        count--;                    }                }            }        } else {            while (count > 0) {                show();            }        }    }    public synchronized void show() {        if (count > 0) {            try {                Thread.sleep(40);            } catch (Exception e) {            }            System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票");            count--;        }    }}/** * 证明同步方法使用this锁 * * @ClassName Thread_01 * @Date 2021/07/17 11:23:43 * @Created by ccc-j * @email ccc-ju@outlook.com */public class Thread_03 {    public static void main(String[] args) throws InterruptedException {        // 创建两个线程, 线程类一定要是一个实例        ThreadTrain3 threadTrain = new ThreadTrain3();        Thread thread_1 = new Thread(threadTrain);        Thread thread_2 = new Thread(threadTrain);        thread_1.start();        Thread.sleep(100);        threadTrain.flag = false;        thread_2.start();    }}

静态同步函数

package com.cjj.thread_safe;class ThreadTrain4 implements Runnable {    // 车票总数    private static int count = 100;    private Object object = new Object();    public boolean flag = true;    @Override    public void run() {        // 线程1 flag为true        if (flag) {            while (count > 0) {                synchronized (ThreadTrain4.class) {                    try {                        Thread.sleep(40);                    } catch (Exception e) {                    }                    if (count > 0) {                        System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票");                        count--;                    }                }            }        } else {            while (count > 0) {                show();            }        }    }    public static synchronized void show() {        if (count > 0) {            try {                Thread.sleep(40);            } catch (Exception e) {            }            System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票");            count--;        }    }}/** * 静态同步函数 * * @ClassName Thread_01 * @Date 2021/07/17 11:23:43 * @Created by ccc-j * @email ccc-ju@outlook.com */public class Thread_04 {    public static void main(String[] args) throws InterruptedException {        // 创建两个线程, 线程类一定要是一个实例        ThreadTrain4 threadTrain = new ThreadTrain4();        Thread thread_1 = new Thread(threadTrain);        Thread thread_2 = new Thread(threadTrain);        thread_1.start();        Thread.sleep(40);        threadTrain.flag = false;        thread_2.start();    }}

多线程死锁

什么是多线程死锁?

同步嵌套同步,导致锁无法释放
package com.cjj.thread_safe;class ThreadTrain5 implements Runnable {    // 车票总数    private static int count = 1000;    private Object object = new Object();    public boolean flag = true;    @Override    public void run() {        // 如果flag 为true 先拿到object这把锁,再拿this锁        if (flag) {            while (count > 0) {                synchronized (object) {                    show();                }            }        } else {            while (count > 0) {                show();            }        }    }    public synchronized void show() {        synchronized (object){            if (count > 0) {                try {                    Thread.sleep(40);                } catch (Exception e) {                }                System.out.println(Thread.currentThread().getName() + "出售第" + (1000 - count + 1) + "张票");                count--;            }        }    }}/** * 死锁模拟 * * @ClassName Thread_01 * @Date 2021/07/17 11:23:43 * @Created by ccc-j * @email ccc-ju@outlook.com */public class Thread_05 {    public static void main(String[] args) throws InterruptedException {        // 创建两个线程, 线程类一定要是一个实例        ThreadTrain5 threadTrain = new ThreadTrain5();        Thread thread_1 = new Thread(threadTrain);        Thread thread_2 = new Thread(threadTrain);        thread_1.start();        Thread.sleep(40);        threadTrain.flag = false;        thread_2.start();    }}

多线程之间通讯

什么是多线程之间通讯?多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同

sleep与wait的区别?

sleep()属于Thread中,而wait()方法,是在Object中的sleep()虽然导致了程序暂停执行指定的时间,让出cpu给其它线程,但是它的监控依然保持,到达指定时间会自动恢复运行状态。在调用sleep方法过程中,线程不会释放对象锁。调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,对此对象调用noify()方法后本线程才会进入对象锁定池准备。为什么wait,notify定义在object类中?在特定场景下,我们要用多线程进行同步,同步的时候是我们自定义,所以把wait,notify放在object中,让任何对象都可以使用。

Synchroized缺点

不能手动开锁解锁

守护线程(后台线程)

主线程或jvm进程挂了,守护线程也会被停止掉。gc其实也是守护线程。

Join

让其它线程等待,只有当前线程执行完毕才会让出资源。

Java内存模型

线程三大特性

  • 原子性:要么全部执行成功,要么全部执行失败(数据库的事务提交即有原子性),保证数据一致,是线程安全的一部分
  • 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程能够立即看到修改的值
  • 有序性:
Java内存模型,共享内存模型指的就是java内存模型(JVM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓存区,寄存器以及其它的硬件和编译器优化。
总结:Java内存模型,简称JMM,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问同一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。

Volatile

  • Volatile关键字的作用是变量在多个线程之间可见
  • Volatile不能保证数据的原子性
package com.cjj.volatile_thread;

class VolatileDemo extends Thread{

    public volatile boolean flag = true;

    @Override
    public void run() {
        System.out.println("子线程开始执行----");
        while (flag){

        }
        System.out.println("子线程结束执行----");
    }

    public void isRun(boolean flag){
        this.flag = flag;
    }

}

/**
 * @ClassName VolatileThread
 * @Date 2021/07/18 19:21:00
 * @Created by ccc-j
 * @email ccc-ju@outlook.com
 */
public class VolatileThread {

    public static void main(String[] args) throws InterruptedException {
        VolatileDemo t1 = new VolatileDemo();
        t1.start();
        Thread.sleep(300);
        t1.isRun(false);
        System.out.println(t1.flag);
    }

}

AtomicInteger

原子类

volatile与synchronized区别

仅靠volatile不能保证线程的安全性。(原子性)
1.volatile轻量级,只能修饰变量。synchronized重量级,还可以修饰方法。
2.volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞
synchronized不仅保证可见性,还能保证原子性,因为,只有获得了锁的线程才能进临界区,从而保证临界区中的所有语句都全部执行,多个线程争抢synchronized锁时,会阻塞

线程安全性
线程安全性包括两方面:可见性,原子性

仅仅使用volatile关键字并不能保证线程的安全性,而synchronized可以实现线程的安全性

ThreadLocal

提高一个线程的局部变量,访问某个线程拥有自己局部变量。
实现原理,Map.put(Thead.getCurrentThread(), xxx);

线程池

为什么要使用线程池?
通过线程池来管理线程,因为启动或停止线程,非常耗资源,所以将线程交给线程池来管理,节约内存。
  • newCachedThreadPool:可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待,
  • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务指定顺序(FIFO,LIFO,优先级)执行
gitee地址:https://gitee.com/ccc-ju/java-study
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值