1. 多线程 基础概念及方法

1. 多线程 基础概念及方法

简介

并发编程的目的是为了让程序运行的更快,但是,并不是启动更多的线程就能让程序最大限度的并发执行。让程序变快,会有很多挑战,比如:死锁、上下文切换

1. 上下文切换

CPU是通过给每隔线程分配CPU时间片来实现这个机制。时间片就是CPU分配给每个线程的时间。因为时间非常短,一般就是几十毫秒。
CPU是通过时间片的分配来循环执行任务的,当前任务执行一个时间片会自动切换到下一个任务,但是,在切换前会保存上一个任务的状态,以便下次切回来这个任务时,可以加在这个任务的状态。这个任务从保存到切换在加载回来的过程就是上下文切换。

2. 多线程不一定快

当并发累加操作不超过百万时,速度会比串行执行的累加要慢,因为线程有创建和切换上下文的开销

3. 如何减少上下文

  • 无锁并发编程:多线程竞争锁时,会引起上下文的切换。多用些不同线程处理不同不同数据段的方法
  • CAS算法: java的atomic包使用CAS算法来更新数据的,不需要锁
  • 避免创建不需要的线程,任务少,线程多,这样大部分线程处于等待状态

4. 死锁

public class Test3 {    private static String A = "a";    private static String B = "b";    public static void main(String[] args) {        Thread t1 = new Thread(new Runnable() {            @Override            public void run() {                synchronized (A) {                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println("t1 sync A");                    synchronized (B) {                        System.out.println("t1 sync B");                    }                }            }        });        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {                synchronized (B) {                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println("t2 sync B");                    synchronized (A) {                        System.out.println("t2 sync A");                    }                }            }        });        t1.start();        t2.start();    }}

5. 避免死锁

  • 避免一个线程使用多个锁
  • 避免一个线程在所内同事占用多个资源,尽量保证在锁期间就占用一个资源

6. 资源限制

  • 服务器的带宽只有2Mb/s,某个资源的下载速度是1Mb/s每秒,系统启动10线程,速度一定是到不了10MB/s

多线程状态和方法

1. 状态

线程共包括以下 5 种状态:

  1. 新建状态(New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

  2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

  3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

  4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

  1. 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

2. 方法

  • void join() 等待该线程终止 相当于插队

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。主线程本来要比thread2线程先执行完成,但加入join后,主线程会让thread2先执行完成,然后自己执行完成。

    MyThread2 myThread2 = new MyThread2();    Thread t2 = new Thread(myThread2, "join");    t2.start();    try {        t2.join();    } catch (InterruptedException e) {        e.printStackTrace();    }    System.out.println("join end");        class MyThread2 implements Runnable { // 实现Runnable接口    @Override    public void run() {  // 覆写run()方法        for (int i = 0; i < 10; i++) {            System.out.println(i);        }    }}
  • static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行
  • static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
  • wait(), notify() 两个方法是暂停和唤醒。用于生产者和消费者模式

3. 三种创建多线程

  • 1.Thread 类也是 Runnable 接口的子类
  • 2.使用Runnable,代码可以被多个线程共享,代码和数据独立
  • 3.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
public static void main(String[] args) {    new Thread(new MyThread1(),"Runnable").start();    new MyThread2().start();    new Thread(() -> {        System.out.println("Lambda");    }).start();}static class MyThread1 implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName());    }}static class MyThread2 extends Thread {    @Override    public void run() {        System.out.println(Thread.currentThread().getName());    }}

推荐阅读