多线程基础入门学习(带示例代码)

多线程基础入门学习

  • 概念
    • 并发与并行
    • 线程与进程
  • 创建线程的方法
    • 继承Thread类
    • 实现Runnable接口
    • Thread与Runnable的区别
  • 线程的常用方法
    • 设置优先级(setPriority)
    • 休眠(sleep)
    • 设置让步(yield)
    • 合并(join)
    • 守护线程(setDaemon(true))
  • 线程的生命周期
    • 新建状态(New)
    • 就绪状态(Runnable)
    • 运行状态(Running)
    • 阻塞状态(Blocked)
    • 死亡状态(Dead)
  • 线程通信
    • 等待唤醒机制
      • 场景案例
      • 代码示例
  • 死锁

概念

并发与并行

  1. 并发:多个事件在同一时间段内发生;
  2. 并行:多个事件在同一时间发生(同时发生,一起执行);

线程与进程

  1. 进程

    • 指内存中运行的一个程序,每个进程都有一个独立的内存空间;

    • 一个应用程序可以同时运行多个进程;

    • 进程是程序的一次执行过程,是系统运行程序的一个基本单位;

    • 系统运行一个程序即一个进程从创建、运行到消亡的过程;

  2. 线程

    • 线程是进程的一个执行单元,负责当前进程中程序的执行;

    • 一个进程至少有一个线程;

    • 一个进程可以有多个线程;

    线程的调度:

    • 分时调度:所有线程轮流使用CPU,平均分配每个线程占用CPU的时间;

    • 抢占式调度:优先级高的线程优先使用CPU;

创建线程的方法

继承Thread类

public class Demo1 {
    public static void main(String[] args) {
        MyThread1 myThread = new MyThread1();
        myThread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main thread: " + i);
        }
    }
}


class MyThread1 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("new thread: " + i);
        }
    }
}

实现Runnable接口

public class Demo2 {
    public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2();
        Thread thread = new Thread(myThread2);
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main thread: " + i);
        }
    }
}

class MyThread2 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("new thread: " + i);
        }
    }
}

Thread与Runnable的区别

实现Runnable接口比继承Thread类的优势:

  • 适合多个相同程序代码的线程去共享一个资源(如抢票功能的票数):

    Runnable比Thread共享一个资源要方便一些(如下段代码所示);

  • 可以避免java中的单继承的局限性:

    java是单继承多实现的,继承了一个类后,就不能再继承其他类,有较大的局限性;

  • 实现解耦操作,代码可以被多个线程共享,代码和线程独立:

    一段代码可以被多个线程同时使用,每新启动一个线程,都会在JVM里新开一个栈空间,但是每个栈的执行体,所执行的都是同一段代码;

暂时抛开线程安全问题执行下面两段代码后,会发现继承Thread的线程所操作的ticket票数属性,是每个售票口10张票,开的两个线程之间资源没有共享(如果给ticket加上static,也是可以共享的),而实现Runnable的线程,则是所有售票口共同销售10张票;

Thread:

public class Demo3 {
    public static void main(String[] args) {
        MyThread3 thread1 = new MyThread3("售票口1");
        thread1.start();
        MyThread3 thread2 = new MyThread3("售票口2");
        thread2.start();
    }
}

class MyThread3 extends Thread {

    private Integer ticket = 10;

    public MyThread3(String name) {
        super(name);
    }

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

Runnable:

public class Demo4 {
    public static void main(String[] args) {
        MyThread4 runnable = new MyThread4();
        Thread thread1 = new Thread(runnable, "售票口1");
        thread1.start();
        Thread thread2 = new Thread(runnable, "售票口2");
        thread2.start();
    }
}

class MyThread4 implements Runnable {

    private Integer ticket = 10;

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

线程的常用方法

方法名 说明
void setPriority(int) 线程优先级为1-10,默认5,值越大,获取CPU机会越高
static void sleep(long millis) 当前线程主动休眠millis毫秒
static void yield() 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
final void join() 允许其他线程加入到当前线程中
void setDaemon(boolean) 设置为守护线程有两类:false用户线程(前台线程),true守护线程(后台线程),默认为用户线程

设置优先级(setPriority)

调整java线程的优先级后,优先级高的线程会获得较多的运行机会,只能反应线程的紧急程度,不表示优先级高的就一定先执行;优先级为1到10,默认为5,值越大,获取CPU机会越高;

通过setPriority(int)方法来设置线程的优先级别,代码如下:

public class Demo5 {
    public static void main(String[] args) {
        MyThread4 runnable = new MyThread4();
        Thread thread1 = new Thread(runnable, "thread1");
        thread1.setPriority(1);
        Thread thread2 = new Thread(runnable, "thread2");
        thread2.setPriority(10);

        thread1.start();
        thread2.start();
    }
}

休眠(sleep)

使用线程的sleep(long)可以使线程休眠指定的毫秒数,然后再继续执行执行线程;

代码示例:

当线程里for循环的i循环到5时休眠5秒。

public class SleepThread {
    public static void main(String[] args) {
        MyThread6 thread6 = new MyThread6();
        thread6.start();
    }
}

class MyThread6 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                try {
                    Thread.sleep(1000 * 5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(i);
        }
    }
}

设置让步(yield)

Thread.yield()方法的作用是暂停当前正在执行的线程对象,放弃当前拥有的CPU资源,并执行其他线程,暂停的线程回到可运行状态,参与下一次的CPU资源竞争;

实际中无法保证yield()达到让步的目的,因为让步的线程可能在下一次竞争中再次被线程调度程序选中;

sleep()和yield()的区别:

sleep()是使当前的线程进入停滞状态,所以在指定时间内线程是肯定不会再继续执行的;

yield()是使当前线程回到可执行状态,进行下一次的资源竞争,是有可能在进入可执行状态后又获得了CPU的占有权,从而又被执行;

合并(join)

线程调用join()方法后,主线程的执行会被打断,知道加入的线程被执行完,主线程才会继续执行;

什么时候用join()方法

主线程启动了一个子线程,如果子线程的执行时间比较长,且主线程需要子线程的处理结果,也就是主线程需要等子线程执行完毕之后,主线程才能结束,这个时候就可以使用join()方法。

public class JoinThread {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("----------main thread start----------");
        Thread thread = new Thread(new MyThread7());
        thread.start();
        thread.join();
        System.out.println("----------main thread end----------");
    }
}

class MyThread7 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("child thread: " + i);
        }
    }
}

守护线程(setDaemon(true))

设置为守护线程之后,如果主线程执行完毕,则不管守护线程有没有执行完,都会立马结束;

示例代码如下:

MyThread8守护线程里的1000次循环还没执行完,就会随着主线程10次循环的执行完毕而结束;

public class DaemonThread {
    public static void main(String[] args) {
        MyThread8 thread = new MyThread8();
        thread.setDaemon(true);
        thread.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main thread: " + i);
        }
    }
}

class MyThread8 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("daemon thread: " + i);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程的生命周期

当线程被创建,并启动之后,它并不是立刻就进入了执行状态,也不是一直处于执行状态,而是进入了就绪状态(Runnable),等待CPU调度执行;

线程有5种状态:New、Runnable、Running、Blocked、Dead

如下图:

多线程基础入门学习(带示例代码)_第1张图片

新建状态(New)

当线程对象创建后,即进入了新建状态,如:

Thread thread = new MyThread();

就绪状态(Runnable)

当调用了线程的start()方法之后,线程即进入了就绪状态,并不是说执行了thread.start(),thread线程就会立即执行,而是说明该线程做好了准备,随时等待CPU的调度执行;

运行状态(Running)

当CPU调度处于就绪状态的线程时,该线程才会真正执行,即进入到运行状态;

线程想进入运行状态必须先进入就绪状态,就绪状态时进入运行状态的唯一入口;

阻塞状态(Blocked)

处于运行状态中的线程由于某一原因,暂时放弃了对CPU的使用权,停止执行任务,就会进入到阻塞状态,直到该线程重新进入到就绪状态,才会有机会被CPU重新调用,从而再次进入运行状态;

阻塞状态可分为3种:

  1. 等待阻塞:运行状态的线程执行wait()方法,使本线程进入到等待阻塞状态;

  2. 同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程占用),会进入到同步阻塞状态;

  3. 其他阻塞:通过调用sleep()或join()或I/O请求时,线程会进入阻塞状态。当sleep状态超时,join等待线程终止或超时,或IO处理完毕,线程会重新进入就绪状态;

死亡状态(Dead)

线程执行完成,或因为异常退出了run()方法,该线程就结束了生命周期。

线程通信

等待唤醒机制

保证多个线程协同有序的一起完成一件事情;

当A线程需要等待B线程完成一个任务,才会继续接着执行后面任务时,使用Object.wait()方法,让A线程进入等待,然后等B线程执行完这个任务后,使用Object.notify()方法,通知A线程,将A线程从WAITING状态中唤醒,继续执行A线程后面的任务;

方法 说明
public final void wait() 释放锁,进入等待队列
public final void wait(long timeout) 释放锁,进入等待序列,但超过设定时间之后,会自动唤醒
public final void notify() 随机唤醒,通知所有线程
public final void notifyAll() 唤醒、通知所有线程

场景案例

现有三个线程,一个老板,一个顾客,一起完成一个商品的交易;

交易过程如下(顺序不能乱):

1.顾客挑选货物,耗时2秒

2.顾客付钱

3.老板计算需要找零多少,耗时2秒

4.老板找零

5.顾客拿回零钱走人

代码示例

public class Demo6 {
    public static void main(String[] args) {
        // 锁
        Object lock1 = new Object();    // 顾客付钱通知老板的锁
        Object lock2 = new Object();    // 老板找零通知顾客的锁

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "==>1. 挑选货物");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "==>2. 付钱");
                synchronized (lock1) {
                    lock1.notify();
                }
                synchronized (lock2) {
                    try {
                        lock2.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println(Thread.currentThread().getName() + "==>5. 拿回零钱走人");
            }
        }, "顾客").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1) {
                    try {
                        lock1.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                System.out.println(Thread.currentThread().getName() + "==>3. 计算需要找零多少");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "==>4. 找零");

                synchronized (lock2) {
                    // 通知顾客找零了,可以走人了
                    lock2.notify();
                }
            }
        }, "老板").start();
    }
}

睡眠(sleep)和等待(wait)的区别

wait:线程不再活动,不再参与调度,进入到wait set(锁池)中,会释放锁,也不会去竞争锁,因此不会浪费cpu的资源,此时线程的状态为WAITING,需要等其他线程执行“通知(notify)”,才能将在这个对象上等待的线程从wait set中释放出来,重新进入调度队列(ready queue);

sleep:不会释放锁,会一直占用cpu资源;

wait与notify是Object类的方法,所以任意对象都有这两个方法;

死锁

两个或多个线程同时被阻塞,都在等待某一个锁资源的释放,由于线程被无限期的阻塞,所以程序不可能正常终止;

如两个线程A和B,两个锁资源lock1和lock2,线程A已经持有lock1,线程B已经持有lock2,但是现在线程A需要获取lock2才能继续往下执行,而线程B需要获取lock1才能继续往下执行,所以两个线程都需要对方已经持有的锁资源,此时两个线程就会因为互相等待对方释放资源,而进入到死锁的状态;

代码示例:

public class Demo7 {
    public static void main(String[] args) {
        Object lock1 = new Object();    // 锁资源1
        Object lock2 = new Object();    // 锁资源2

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + "==>获取到锁1");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "==>等待获取锁2");
                    synchronized (lock2) {
                        System.out.println(Thread.currentThread().getName() + "==>获取到锁2");
                    }
                }
            }
        }, "线程A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + "==>获取到锁2");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "==>等待获取锁1");
                    synchronized (lock1) {
                        System.out.println(Thread.currentThread().getName() + "==>获取到锁1");
                    }
                }
            }
        }, "线程B").start();
    }
}

你可能感兴趣的:(JAVA基础,java,多线程)