登录
转载

Java多线程

发布于 2021-04-01 阅读 606
  • 后端
  • Java
转载

提示:该文档仅保留个人学习过程观点,如有不对,请各位大佬指正

文章目录

  • 多线程
    • 基础概念
    • 实现多线程的方式
      • 1.Thread 类
      • 2.Runnable 接口
      • 3.Callable 接口
    • 线程安全问题解决方式
      • 1. synchronized 线程同步 [隐式锁]
      • 2. synchronized 同步方法 [隐式锁]
      • 3. Lock 的子对象 ReentrantLock [显示锁]
      • 公平锁和不公平锁
      • 线程死锁
    • 多线程通信
    • 线程池
      • Java中的四种线程池 . ExecutorService
        • 1. 缓存线程池
        • 2. 定长线程池
        • 3. 单线程线程池
        • 4. 周期性任务定长线程池


多线程

  多个线程并发执行,提高程序运行效率。

基础概念

进程:运行的一个程序或者应用,每个进程都有独立的内存空间 线程:进程的一个任务(执行路径),一个进程至少有一个线程,一个进程里面的多个线程共享一个内存空间

并发:同一时间段内发生 并行:同一时刻发生

同步:排队执行,效率低,数据安全 异步:同时执行,效率高,数据不安全

用户线程:当一个进程不包括任何存活的用户线程时,进程结束 守护线程:守护用户线程的,当最后一个用户线程结束时,所有的守护线程自动死亡

// 设置为守护线程,在线程启动前设置
线程.setDeamon(true);

实现多线程的方式

1.Thread 类

类继承 Thread 类,重写run方法,start()方法调用线程

public class Test {
    public static void main(String[] args) {
        Thread my = new MyThread();
        my.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("执行此线程方法。。");
    }
}

2.Runnable 接口

实现 Runnable 接口,重写run方法(里面写任务方法) 1)创建任务对象 2)创建一个线程,并为其分配一个任务 3)执行线程

public class Test01 {
    public static void main(String[] args) {
        MyRunnable my = new MyRunnable();
        Thread t = new Thread(my);
        t.start();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("执行了此任务方法。。");
    }
}

  • 实现 Runnable 与 继承 Thread 的优势

1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况 2.可以避免单继承所带来的局限性 3.任务与线程本身分离,降低耦合性

3.Callable 接口

实现 Callable 接口,通过 Callable 创建 FutureTask 对象,通过 FutureTask 创建 Thread,启动线程

public class Test02 {
    public static void main(String[] args) {
        // 实现 Callable 接口,通过 Callable 创建 FutureTask 对象,通过 FutureTask 创建 Thread ,启动线程
        MyCallable my = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(my);
        Thread t = new Thread(task);
        t.start();

        // 执行主线程打印
        System.out.println("主线程打印。。");

        // 获取分支线程返回值
        try {
            int x = task.get();
            System.out.println("x = " + x);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<Integer> {
    /* 与run()方法不同的是,call()方法具有返回值*/
    @Override
    public Integer call() throws Exception {
        System.out.println("callbale执行了");
        return 0;
    }
}

java控制台截图

  • Runnable 与 Callable不同点

Runnable没有返回值 Callable可以返回执行结果 Callable接口的call()允许抛出异常 Runnable的run()不能抛出 Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞

  • 获取当前线程
Thread.currentThread();

  • 线程休眠
Thread.sleep(毫秒数);

  • 线程阻塞
耗时操作(例如文件读取)

  • 线程中断

线程是否应该结束应该由其线程自生决定 方式:外部调用 线程.interrupt() 方法,标记线程中断,线程里面try-catch块捕获InterruptedException,在catch块里面结束线程(return;)

线程安全问题解决方式

  • 线程安全问题: 多个线程争抢到的数据,在拿到与使用时不一致导致的一系列问题

1. synchronized 线程同步 [隐式锁]

同步代码块 synchronized 互斥锁     格式:synchronized(锁对象){}     锁对象必须是同一个锁对象

锁对象:

public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        MyRun my = new MyRun();
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        Thread t3 = new Thread(my);
        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRun implements Runnable {
    /**
     * 共享资源
     */
    private int count = 10;
    Object obj = new Object();

    /**
     * synchronized 锁对象
     */
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (count > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + count);
                    count--;
                } else {
                    break;
                }
            }
        }
    }
}

2. synchronized 同步方法 [隐式锁]

格式:public synchronized void 方法名(){}     普通的锁对象是:this     static的锁对象是:类名.class

锁方法:

public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        MyRun my = new MyRun();
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        t1.start();
        t2.start();
    }
}

class MyRun implements Runnable {
    /**
     * 共享资源
     */
    int i = 0;

    /**
     * synchronized 修饰实例方法
     */
    public synchronized void increase() {
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 5; j++) {
            increase();
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

3. Lock 的子对象 ReentrantLock [显示锁]

  • 用法:
// 创建锁
private Lock lock = new ReentrantLock();
// 加锁
lock.lock();
// 解锁
lock.unlock();

// 编程规范:
// 锁【lock()方法】必须紧跟try代码块,且unlock()方法要放到finally第一行。

public class Test04 {
    public static void main(String[] args) throws InterruptedException {
        MyRu my = new MyRu();
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        Thread t3 = new Thread(my);
        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRu implements Runnable {
    /**
     * 共享资源
     */
    private int count = 10;

    /**
     * 隐式锁
     */
    private Lock lo = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lo.lock();
            try {
                if (count > 0) {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ":" + count);
                    count--;
                } else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lo.unlock();
            }


        }
    }
}

控制台最后都是差不多的打印: 控制台截图

  • Java显示锁和隐式锁的区别(点击跳转)

公平锁和不公平锁

公平锁:设置与创建 private Lock lock = new ReentrantLock(true); 不公平锁:Java的三个锁默认都是不公平锁,随机抢锁

非公平锁性能高于公平锁性能。首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。

线程死锁

线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,并且互相等待导致程序执行不下去的情况

死锁详细解释说明【Java多线程产生死锁的4个必要条件?如何避免死锁?】

多线程通信

Object的方法 wait() notify() notifyAll()

【线程间通信的几种实现方式】

线程池

【深入理解线程和线程池(图文详解)】 【详细的线程池讲解】

在一个应用程序中,需要多次创建并销毁线程。创建并销毁过程中耗费大量宝贵的内存资源,所以,就有了线程池的概念。

  • 线程池用处:

方便管理线程,减少内存的消耗。

  • 创建一个线程池:

Java中提供了创建线程池的一个类:Executor 创建时,一般使用它的子类:ThreadPoolExecutor

Java中的四种线程池 . ExecutorService

1. 缓存线程池

/**
 * 缓存线程池.
 * (长度无限制)
 * 执行流程:
 * 1. 判断线程池是否存在空闲线程
 * 2. 存在则使用
 * 3. 不存在,则创建线程 并放入线程池, 然后使用
 */
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
	@Override
	public void run() {
		System.out.println("线程的名称:" + Thread.currentThread().getName());
	}
});

2. 定长线程池

/**
 * 定长线程池.
 * (长度是指定的数值)
 * 执行流程:
 * 1. 判断线程池是否存在空闲线程
 * 2. 存在则使用
 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
 */
ExecutorService service = Executors.newFixedThreadPool(3);
	service.execute(new Runnable() {
	@Override
	public void run() {
		System.out.println("线程的名称:" + Thread.currentThread().getName());
	}
});

3. 单线程线程池

/**
 * 单线程线程池.
 * 执行流程:
 * 1. 判断线程池的哪个线程是否空闲
 * 2. 空闲则使用
 * 4. 不空闲,则等待池中的单个线程空闲后使用
 */
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
	@Override
	public void run() {
		System.out.println("线程的名称:" + Thread.currentThread().getName());
	}
});

4. 周期性任务定长线程池

/**
 * 周期任务 定长线程池.
 * 执行流程:
 * 1. 判断线程池是否存在空闲线程
 * 2. 存在则使用
 * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池, 然后使用
 * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
 */



/**
 * 定时执行, 当某个时机触发时, 自动执行某任务
 */
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
service.schedule(new Runnable() {
	@Override
	public void run() {
		System.out.println("定时执行。。。");
	}
},5,TimeUnit.SECONDS);


/**
 * 周期执行
 * 参数1. runnable类型的任务
 * 参数2. 时长数字(延迟执行的时长)
 * 参数3. 周期时长(每次执行的间隔时间)
 * 参数4. 时长数字的单位
 */
service.scheduleAtFixedRate(new Runnable() {
	@Override
	public void run() {
		System.out.println("周期执行。。。");
	}
},5,2,TimeUnit.SECONDS);

注意:阿里编程规范中不建议使用Executors直接创建线程池,而是建议手动创建线程池,原因是Executors创建的线程池存在OOM的风险。

【Java创建线程池正确方式】

评论区

admin
14粉丝

打江山易,守江山难,负重前行,持续创新。

0

0

0

举报