Java多线程编程

目录

 

1、概念

1.1 进程

·1.2 线程

1.3 进程与线程的区别和联系

2、生命周期

2.1 线程的生命周期

2.2 生命周期的状态

3、多线程的实现

3.1 继承 Thread 类

3.2 实现 Runnable 接口

3.3 补充

4、临界资源问题

5、解决临界资源问题

5.1 同步代码段

5.2 同步方法

5.3 手动上锁

6、死锁

7 notify 和 notifyAll


1、概念

1.1 进程

狭义定义:是正在运行的程序的实例。

广义定义:是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。一般由程序,数据集合和进程控制块三部分组成

·1.2 线程

定义:程序执行中一个单一的顺序控制流程,是程序执行流的最小单元。

1.3 进程与线程的区别和联系

①、不同进程之间的 资源不共享

②、线程之间的资源 可以共享

③、线程包含在进程之中的,一个进程至少有一个线程,也可以有多个线程

④、线程是程序执行的最小单位

⑤、进程是重量级的,线程是轻量级。CPU在线程之间的切换要比进程之间的切换块

2、生命周期

2.1 线程的生命周期

定义:是线程从初始化完成,到最后的销毁的过程 称之为线程生命周期

2.2 生命周期的状态

新生态: new

线程刚被创建出来,还没进行任何操作。

就绪态: runable

线程已被开启,开始争抢 CPU 的时间片

运行态:run

线程已经抢到 CPU的时间片,开始执行线程中的逻辑代码

堵塞态:Interrupt

一个线程在运行过程中,收到某些操作的影响,放弃已经获取到的CPU时间片。并且不再参与CPU时间片的争夺。此时线程处于挂起

死亡态 :dead

一个线程需要被销毁。

3、多线程的实现

3.1 继承 Thread 类

方式一: 继承 Thread 类,重写 run 方法,使用 start 方法开启线程。代码如下:

/**
 * 自定义线程类
 */
public class MyThread extends Thread {
    /*
     * 重写 run 方法
     * 将需要并发执行的任务写在 run 方法中
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println("子线程中的 "+i);
        }
    }
}
public class ThreadDemo {

    public static void main(String[] args) {
        // 线程实例化
        MyThread thread = new MyThread();
        thread.start();
        System.out.println("main 线程执行完毕");
    }


}

3.2 实现 Runnable 接口

方式二: 通过实现 Runnable 接口,重写 run 方法。将 Runnable 接口的实现类作为参数 传入Thead 类中。

        Runnable r = () -> {
            for(int i = 0; i< 10; i++){
                System.out.println("子线程中执行" + i);
            }
        };
        Thread t = new Thread(r);
        t.start();
        System.out.println("main 线程执行完毕");

3.3 补充

①、继承Thread 类的方式实现多线程比较直观和简单, 但它的弊端是java是单继承的,继承了 Thread 类后就不能继承其他类

②、实现 Runnable 接口方式可以避免单继承问题

③、需要注意的是开启线程的方式是 调用 线程对象的 start 方法,而不是 run 方法

4、临界资源问题

临界资源:被多个线程同时访问的资源(共享资源)

例子:以景区多个售票员同时售票为例

public class ThreadDemo {
    /**
     * 某景点有 4 个售票员, 同时售票
     * @param args
     */
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
            }
        };
        // 4 个线程, 模拟 4 个售票员
        Thread t1 = new Thread(r, "thread - 1");
        Thread t2 = new Thread(r, "thread - 2");
        Thread t3 = new Thread(r, "thread - 3");
        Thread t4 = new Thread(r, "thread - 4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }


}
class TicketCenter{
    // 描述剩余的票的数量
    public static int restCount = 100;
}

运行结果

Java多线程编程_第1张图片

分析

有些票被重复卖了, 例如图上的一张票,分别被 线程1 和 线程4 出售。

原因

线程1进来的时候看到有100张票,此时线程1 没有买票暂时离开了。刚好线程4 进入看到余票也是 100张后,线程4 也没有买票暂时离开; 现在线程1由于之前已经判断了还有余票,所以继续之前的代码,买了1张票剩余99,也暂时离开;线程4进入后因为之前看到100张余票,所以他也买了1张剩余99张

简单理解:

A去买票,此时售票员甲查了一下有100张,但还没直接卖出去时,刚好去上了个厕所。

此时B 也来买票,售票员乙也查了一下有100张, 但也没有直接卖出去的时候,也去上了个厕所。

然后售票员甲回来了,没有再查票的库存,直接登记卖出1张, 剩余99张。

最后售票员乙也回来,也没有查票的库存,直接登记卖了1张,剩余99张。

5、解决临界资源问题

5.1 同步代码段

public class ThreadDemo {
    /**
     * 某景点有 4 个售票员, 同时售票
     *
     * @param args
     */
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                synchronized ("") {
                    // 这里再判断一下, 防止卖出负数票
                    if (TicketCenter.restCount > 0) {
                        System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
                    }
                }
            }
        };
        // 4 个线程, 模拟 4 个售票员
        Thread t1 = new Thread(r, "thread - 1");
        Thread t2 = new Thread(r, "thread - 2");
        Thread t3 = new Thread(r, "thread - 3");
        Thread t4 = new Thread(r, "thread - 4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }


}

class TicketCenter {
    // 描述剩余的票的数量
    public static int restCount = 100;
}

5.2 同步方法

把同步代码段中的代码抽取处理,做成方法,然后方法上使用 synchronized 关键字修饰

public class ThreadDemo {

    private static synchronized void soldTicket() {
        // 这里再判断一下, 防止卖出负数票
        if (TicketCenter.restCount > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
        }
    }

    /**
     * 某景点有 4 个售票员, 同时售票
     *
     * @param args
     */
    public static void main(String[] args) {
        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                soldTicket();
            }
        };
        // 4 个线程, 模拟 4 个售票员
        Thread t1 = new Thread(r, "thread - 1");
        Thread t2 = new Thread(r, "thread - 2");
        Thread t3 = new Thread(r, "thread - 3");
        Thread t4 = new Thread(r, "thread - 4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }


}

class TicketCenter {
    // 描述剩余的票的数量
    public static int restCount = 100;
}

补充

同步方法:如果是静态方法,那加的锁就是 类名.class, 如果是非静态方法,那加的锁是对象锁 this

5.3 手动上锁

import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo {


    /**
     * 某景点有 4 个售票员, 同时售票
     *
     * @param args
     */
    public static void main(String[] args) {
        // 实例化一个锁对象
        ReentrantLock lock = new ReentrantLock();

        Runnable r = () -> {
            while (TicketCenter.restCount > 0) {
                // 对临界资源上锁
                lock.lock();

                // 这里再判断一下, 防止卖出负数票
                if (TicketCenter.restCount > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出了一张票, 剩余 " + --TicketCenter.restCount + "张");
                }
                // 对临界资源解锁
                lock.unlock();
            }
        };
        // 4 个线程, 模拟 4 个售票员
        Thread t1 = new Thread(r, "thread - 1");
        Thread t2 = new Thread(r, "thread - 2");
        Thread t3 = new Thread(r, "thread - 3");
        Thread t4 = new Thread(r, "thread - 4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }


}

class TicketCenter {
    // 描述剩余的票的数量
    public static int restCount = 100;
}

6、死锁

定义:多个线程持有对象的锁对象,而不释放自己锁


public class ThreadDemo {


    public static void main(String[] args) {
        Runnable r1 = () -> {
            synchronized ("A") {
                System.out.println("A 线程持有 A锁, 等待B锁");
                synchronized ("B") {
                    System.out.println("A 线程持同时持有 A锁和 B锁");

                }
            }
        };
        Runnable r2 = () -> {
            synchronized ("B") {
                System.out.println("B 线程持有 B锁, 等待A锁");
                synchronized ("A") {
                    System.out.println("B 线程持同时持有 A锁和 B锁");

                }
            }
        };
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();

    }


}

运行结果:

Java多线程编程_第2张图片

7 notify 和 notifyAll


public class ThreadDemo {


    public static void main(String[] args) {
        // wait : 等待, 是Object 类中的一个方法, 当前的线程释放自己的锁标记, 并且让出CPU 资源, 使得当前线程进入等待队列
        // notify: 通知, 是Object 类中的一个方法, 唤醒等待队列中的一个线程, 使这个线程进入锁池
        // notifyAll : 通知, 是Object类中的一个方法, 唤醒等待队列中所有的线程, 并这个线程进入锁池
        Runnable r1 = () -> {
            synchronized ("A") {
                System.out.println("A 线程持有 A锁, 等待B锁");
                try {
                    // 释放已经持有的 A锁标记, 进入等待队列
                    "A".wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized ("B") {
                    System.out.println("A 线程持同时持有 A锁和 B锁");

                }
            }
        };
        Runnable r2 = () -> {
            synchronized ("B") {
                System.out.println("B 线程持有 B锁, 等待A锁");
                synchronized ("A") {
                    System.out.println("B 线程持同时持有 A锁和 B锁");
                    "A".notifyAll();

                }
            }
        };
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();

    }


}

解决死锁:

Java多线程编程_第3张图片

 

 

 

 

你可能感兴趣的:(Java多线程编程,多线程,死锁,并发)