一、多线程基础

1、什么是进程

由操作系统执行的计算机程序的实例,是操作系统分配资源的基本单位,每个应用程序都运行在一个进程中。

进程

2、什么是线程

进程内部按照单一顺序执行的控制流,是进程的一项子任务,线程是CPU调度的基本单元。

进程与线程的关系:

一个进程可以包含一个或多个线程,一个线程一定属于某个进程。

程序一旦运行会占用相当一部分资源,所有的线程占用的资源总和==进程占用资源总和。

进程与线程的关系

单线程与多线程的区别:

单线程所有的任务按照顺序一个接一个进行,效率较低

多线程允许程序同时执行多个任务,每个任务由一个独立的线程负责执行,如果一个线程阻塞,其他线程可以继续允许,效率高

3、java中的多线程

(1)Thread类

  • 是java中代表线程的类,位于java.lang包下
  • 类中包含封装线程具体信息的属性,以及对线程进行操作的方法
  • 每个thread类的对象代表一个具体的线程

(2)Thread类的方法

  • long getId():返回该线程的默认id
  • String getName():返回该线程的名称
  • int getPriority():返回线程的优先级
  • Thread.state getState():获取线程的状态
  • boolean isAlive():测试线程是否处于活动状态
  • boolean isDaemon:测试线程是否为守护线程
  • boolean isInterrupted():测试线程是否已经中断

(3)创建线程

构造方法:
  • public Thread():分配一个新的线程对象
  • public Thread(String name):分配一个指定名称的新的线程对象
  • public Thread (Runnable target):分配一个带指定目标的新的线程对象
其他方法:
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用
  • public String getName():获取当前线程名称
  • **public void start():此线程开始执行
/**
 * @document: 创建并启动线程
 * @Author:SmallG
 * @CreateTime:2023/8/9+15:42
 */

public class Demo01 {
    public static void main(String[] args) {
        //获取主线程,获取当前正在执行的线程对象
        Thread thread = Thread.currentThread();//----主线程
        //输出线程的名字
        System.out.println(thread.getId() + "--" + thread.getName());

        //创建线程对象
        Thread t1 = new Thread();
        System.out.println(t1.getId() + "--" + t1.getName());

        //创建线程并指定名字
        Thread t2 = new Thread("线程1");
        System.out.println(t2.getId() + "--" + t2.getName());
        t2.start(); //启动线程,没有任何效果
    }
} 

创建线程还可以继承Thread类来实现线程(不推荐)

/**
 * @document: 创建线程
 * @Author:SmallG
 * @CreateTime:2023/8/9+16:23
 */

public class Demo03 {
    public static void main(String[] args) {
        //创建线程对象
        MyRun2 myRun1 = new MyRun2("线程1");
        myRun1.start();
        /*
        线程1:1
        线程1:2
        线程1:3
        线程1:4
        线程1:5
         */
    }
}

class MyRun2 extends Thread {

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

    @Override
    public void run() {
        //获取当前线程对象
        Thread thread = Thread.currentThread();
        String threadName = thread.getName();
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + ":" + i);
        }
    }
}

(4)指定线程执行的内容

/**
 * @document: 执行线程
 * @Author:SmallG
 * @CreateTime:2023/8/9+15:52
 */

public class Demo02 {
    public static void main(String[] args) {
        MyRun1 run1 = new MyRun1();
        //创建线程对象
        Thread t1 = new Thread(run1, "线程1"); //给线程一个目标
        Thread t2 = new Thread(run1, "线程2"); //给线程一个目标

        //启动线程,Jvm会自动调用run()方法
        t1.start();
        t2.start();

        //主线程打印
        for (int i = 1; i <= 5; i++) {
            System.out.println("main:" + i);
        }

    }

}

class MyRun1 implements Runnable {

    @Override
    public void run() {
        //获取当前线程对象
        Thread thread = Thread.currentThread();
        String threadName = thread.getName();
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + ":" + i);
        }
    }
}

二、线程的状态

1、什么是线程的状态

新建状态

一个线程对象被创建出来时,该线程对象处于新建状态

就绪状态

调用了一个线程对象的start()方法

运行状态

CPU执行了一个线程对象的run()方法时处于运行状态

阻塞状态

分为:等待阻塞、同步阻塞、其他阻塞,当一个运行中的线程暂停运行时,该线程处于阻塞状态

终止状态

线程完成了任务或被终止,处于终止状态

线程的状态

2、线程的优先级

  • Thread.MIN_PRIORITY(1)
  • Thread.MAX_PRIORITY(10)
  • Thread.NORM_PRIORITY(5)
/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/8/9+16:50
 */

public class Demo05 {
    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                System.out.println("Hello world " + Thread.currentThread().getName());
            }
        }, "线程1");
        Thread t2 = new Thread(() -> {
            for (int i = 1; i <= 10000; i++) {
                System.out.println(i + " : " + Thread.currentThread().getName());
            }
        }, "线程2");

        //设置线程的优先级
        t1.setPriority(Thread.MIN_PRIORITY); //优先级设为1
        t2.setPriority(Thread.MAX_PRIORITY); //优先级设为10

        t1.start();
        t2.start();
    }
}

3、线程的状态操作

  • sleep():控制某个线程休眠一段时间
  • join():控制某个线程等待该线程执行完成后再执行
  • yield():控制一个线程从运行状态切换到就绪状态,用于事当前线程主动让出当次CPU时间片回到Runnable状态,等待分配时间片
  • interrupt():向线程发送一个中断通知

sleep():

/**
 * @document: 改变线程状态:sleep()---让进程休眠
 * @Author:SmallG
 * @CreateTime:2023/8/9+17:35
 */

public class Demo06 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new Thread(() -> {
            System.out.println("正在运行的线程名称:"
                    + Thread.currentThread().getName()
                    + " 开始时间:" + LocalTime.now());
            //休眠两秒
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("正在运行的线程名称:"
                    + Thread.currentThread().getName()
                    + " 结束时间:" + LocalTime.now());
        });
        t.start();
    }
}

join():

/**
 * @document: 线程状态----join()等待另一线程完成
 * @Author:SmallG
 * @CreateTime:2023/8/9+17:48
 */

public class Demo07 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MyRun3(), "t1");
        Thread t2 = new Thread(new MyRun3(), "t2");
        Thread t3 = new Thread(new MyRun3(), "t3");
        t1.start();
        t1.join(2000);
        t2.start();
        TimeUnit.SECONDS.timedJoin(t2, 2000);
        t3.start();

        System.out.println("所有线程均死亡");
    }
}

class MyRun3 implements Runnable {

    @Override
    public void run() {
        System.out.println("线程开始执行:" + Thread.currentThread().getName());
        try {
            //休眠2秒
            Thread.sleep(2000); //休眠2秒,可读性不好
            //休眠2秒
            TimeUnit.SECONDS.sleep(2); //java5后的新特性
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程结束执行:" + Thread.currentThread().getName());
    }
}

yieId():

/**
 * @document: 线程就绪状态--yield
 * @Author:SmallG
 * @CreateTime:2023/8/10+9:07
 */

public class Demo08 {
    public static void main(String[] args) {
        Runnable run = () -> {
            //获取线程名字
            String name = Thread.currentThread().getName();
            for (int i = 0; i < 8; i++) {
                System.out.println(name + ":" + i);
                if (i == 5) {
                    //让当前线程主动释放CPU的使用权,回到就绪状态
                    Thread.yield();
                }
            }
        };
        //创建线程对象
        Thread t1 = new Thread(run, "线程1");
        Thread t2 = new Thread(run, "线程2");
        t1.start();
        t2.start();
    }
}

interrupt():

中断通知

/**
 * @document: 线程中断 interrupt
 * @Author:SmallG
 * @CreateTime:2023/8/10+9:31
 */

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
            Thread t = Thread.currentThread();
            System.out.println("线程启动了");
            //循环持续观察线程是否中断
            while (!t.isInterrupted()) {
                System.out.println("线程仍在活动");
            }

            System.out.println("线程结束了");
        };
        Thread t = new Thread(run, "子线程");
        t.start();
        TimeUnit.MILLISECONDS.sleep(1);

        //向子线程发送中断通知
        t.interrupt();
    }
}

4、其他阻塞状态

  • 等待阻塞:与线程的同步相关
  • 同步阻塞:与线程的同步相关
  • 其他阻塞:因调用线程的sleep方法或join方法而导致的线程阻塞

其他阻塞状态

5、后台线程

  • 指的是程序运行时在后台提供的一种通用服务的线程,也就是为其他线程服务的,也称守护线程
  • 后台线程不属于程序不可或缺的部分,当所有的非后台线程结束时,程序也就终止了,同时也会终止所有后台线程
  • 垃圾回收就是后台线程
  • 通过setDaemon(true)来设置线程为后台线程,注意必须在start()方法之前设定,否则会抛出IIegalThreadStateException异常
  • 可以使用isDaemon()方法来判断一个线程是否为后台线程
/**
 * @document: 守护线程
 * @Author:SmallG
 * @CreateTime:2023/8/10+10:07
 */

public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        Runnable run = () -> {
            while (true) {
                System.out.println(Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread t = new Thread(run, "子线程");

        //设置子线程为后天线程,必须在子线程之前设置
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println("主线程结束了");
    }
}

三、线程并发安全

1、线程并发安全概述

线程并发安全问题

一个进程下的多个线程共享该进程被分配到的内存空间,也就是一个进程下的数据可能被多个线程同时访问,称为数据共享。

常见的共享数据有:

  • 对象的属性
  • 类的静态变量

常见的非共享数据有:

  • 方法中声明的局部变化
  • 方法形参

多个线程并发访问可能会导致意外或错误的行为,称为线程并发安全问题。

并发访问

/**
 * @document: 线程安全问题
 * @Author:SmallG
 * @CreateTime:2023/8/10+10:20
 */

public class Demo12 {
    public static void main(String[] args) {
        Account account = new Account();
        account.withDraw();
    }
}

//银行账户
class Account {
    public static double balance = 2000;

    /**
     * 取钱的方法
     */
    public void withDraw() {
        //创建线程
        MyThread t1 = new MyThread("丈夫", 1500);
        MyThread t2 = new MyThread("妻子", 1500);
        t1.start();
        t2.start();

    }

    /**
     * 生成一个取钱操作的内部类
     */
    class MyThread extends Thread {
        private String name; //名字
        private double money; //取钱金额

        public MyThread(String name, double money) {
            this.name = name;
            this.money = money;
        }

        @Override
        public void run() {
            //判断余额是否充足
            if (Account.balance > money) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //修改账户余额
                Account.balance = Account.balance - money;
                System.out.println(name + "取钱成功!余额还剩" + Account.balance);
            }
        }
    }
}

2、线程互斥

(1)临界区:一个受保护的区域

  • 限定一次不能有多个线程同时进入这个区域
  • 仅在临界区中的线程可以对共享资源进行操作
  • 当已经有一个线程进入临界区时,其他线程必须等待该线程离开临界区后,再进入临界区

(2)线程互斥

指不同线程通过竞争进入临界区(共享的数据和硬件资源)

  • 将异步的操作变为同步操作
  • 异步操作:多线程并发的操作,相当于各自执行
  • 同步操作:有先后顺序的操作,相当于你执行完我再执行

线程互斥

(3)synchronized关键字

用于在程序中设置临界区,以实现线程的互斥,也称为同步锁。

/**
 * @document: 线程同步synchronized关键字实现线程同步
 * @Author:SmallG
 * @CreateTime:2023/8/10+10:44
 */

public class Demo13 {
    public static void main(String[] args) {
        Account2 account2 = new Account2();
        account2.withDraw();
    }
}


class Account2 {
    public static double balance = 2000;
    public static Object lock = new Object(); // 定义一把锁

    /**
     * 取钱方法
     */
    public void withDraw() {
        MyThread t1 = new MyThread("SmallG", 1500);
        MyThread t2 = new MyThread("SmallY", 1500);
        t2.start();
        t1.start();

    }

    class MyThread extends Thread {
        private String name;
        private double money;

        public MyThread(String name, double money) {
            this.name = name;
            this.money = money;
        }

        @Override
        public void run() {
            //加同步锁
            //声明一个同步代码块,其中的代码同时只能被一个线程访问
            synchronized (Account2.lock) {
                if (Account2.balance > money) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Account2.balance -= money;
                    System.out.println(name + "取钱成功!当前余额为" + Account2.balance);

                } else {
                    System.out.println(name + "取钱失败!余额为" + Account2.balance);
                }
            }
        }
    }
}

(4)同步阻塞状态

当一个线程因synchronized关键字进入阻塞状态时,该线程处于同步阻塞状态

同步阻塞状态

3、synchronized方法

  • 在使用时需要确认多线程访问的是同一个实例的同步方法,才能实现同步效果
  • synchronized也可以修饰静态方法,即静态同步方法,锁定的是类对象
  • 每个类都有唯一的一个类对象,可以通过类名.class获取

synchronized方法

同步锁方法

/**
 * @document: synchronized同步方法
 * @Author:SmallG
 * @CreateTime:2023/8/10+11:29
 */

public class Demo01 {
    public static void main(String[] args) {
        MyRun1 myRun1 = new MyRun1();
        Thread t1 = new Thread(myRun1, "线程1");
        Thread t2 = new Thread(myRun1, "线程2");
        t1.start();
        t2.start();
    }
}

class MyRun1 implements Runnable {

    int num = 0;

    //同步方法
    public synchronized void printNum() throws InterruptedException {
        if (num > 10) {
            return; // 程序结束
        }
        System.out.println(Thread.currentThread().getName() + " : " + num);
        num++;
        TimeUnit.SECONDS.sleep(1);
    }

    @Override
    public void run() {
        while (num < 11) {
            try {
                printNum();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
synchronized实现原理

通过对象的锁来实现

同步锁实现原理

4、死锁

相互等待造成死锁,产生死锁有四个因素:互斥条件、不可被剥夺、请求保持、环路等待。

死锁案例一:

/**
 * @document: 线程死锁问题
 * @Author:SmallG
 * @CreateTime:2023/8/10+14:06
 */

public class DeadLockDemo01 {
    public static void main(String[] args) {
        DeadDemo t1 = new DeadDemo();
        DeadDemo t2 = new DeadDemo();

        t1.flag = 1;
        t2.flag = 0;
        new Thread(t1, "线程1").start();
        new Thread(t2, "线程2").start();
    }
}

class DeadDemo implements Runnable {

    public int flag = 1;

    //定义两个静态成员,静态成员被所有对象共享
    private static Object o1 = new Object();
    private static Object o2 = new Object();

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + ": flag =" + flag);
        if (flag == 1) {
            synchronized (o1) {
                System.out.println(name + "获取了o1锁");
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + ": 线程想获取o2锁");
                synchronized (o2) {
                    System.out.println("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (o2) {
                System.out.println(name + "获取了o2锁");
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("想获取o1锁");
                synchronized (o1) {
                    System.out.println("0");
                }
            }
        }
    }
}

死锁案例二:

/**
 * @document: 线程死锁问题 卖家与买家问题
 * @Author:SmallG
 * @CreateTime:2023/8/10+14:21
 */

public class DeadLockDemo02 {
    public static void main(String[] args) {
        ThreadDeadLock t = new ThreadDeadLock();
        new Thread(t).start();
        new Thread(t).start();

    }
}

//定义卖家
class Seller {
    //卖东西的方法
    public void sell() {
        System.out.println("卖家说:“你给我钱我就把东西给你”");
    }

    //拿到钱
    public void get() {
        System.out.println("卖家拿到钱了");
    }
}

//定义买家
class Buyer {
    //买东西的方法
    public void buy() {
        System.out.println("买家说:“你给我东西我就给你钱”");
    }

    //得到东西了
    public void get() {
        System.out.println("买家得到东西了");
    }
}

class ThreadDeadLock implements Runnable {

    public int flag = 1;
    private Seller seller = new Seller(); //创建卖家
    private Buyer buyer = new Buyer(); //创建买家

    @Override
    public void run() {
        //卖家说
        if (flag == 1) {
            flag = 0;
            //卖家说话
            synchronized (seller) {
                seller.sell();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //卖家得到买家的钱
                synchronized (buyer) {
                    seller.get(); //卖家得到钱了
                }
            }
        }
        if (flag == 0) {
            flag = 1;
            //买家说话
            synchronized (buyer) {
                buyer.buy();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (seller) {
                    buyer.get();
                }
            }
        }
    }
}

哲学家就餐问题

五个哲学家五双筷子,

哲学家就餐问题

/**
 * @document: 哲学家就餐问题
 * @Author:SmallG
 * @CreateTime:2023/8/10+15:00
 */

public class PhilosopherThread extends Thread {
    private Chopsticks left; //左手边的筷子
    private Chopsticks right; //右手边的筷子
    private int index; //为哲学家编号

    //定义一个构造方法

    public PhilosopherThread(Chopsticks left, Chopsticks right, int index) {
        this.left = left;
        this.right = right;
        this.index = index;
    }

    @Override
    public void run() {
        synchronized (left) {
            //每人拿起左手边的筷子
            try {
                //程序暂停一秒
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(index + "等待拿起右手边的筷子");
            synchronized (right) {
                System.out.println(index + "拿到了右手边的筷子");
                System.out.println("开始吃饭");
            }
        }
    }

    public static void main(String[] args) {
        //创建5根筷子
        Chopsticks chopsticks1 = new Chopsticks();
        Chopsticks chopsticks2 = new Chopsticks();
        Chopsticks chopsticks3 = new Chopsticks();
        Chopsticks chopsticks4 = new Chopsticks();
        Chopsticks chopsticks5 = new Chopsticks();
        //创建五名哲学家
        PhilosopherThread t1 = new PhilosopherThread(chopsticks1, chopsticks2, 1);
        PhilosopherThread t2 = new PhilosopherThread(chopsticks2, chopsticks3, 2);
        PhilosopherThread t3 = new PhilosopherThread(chopsticks3, chopsticks4, 3);
        PhilosopherThread t4 = new PhilosopherThread(chopsticks4, chopsticks5, 4);
        PhilosopherThread t5 = new PhilosopherThread(chopsticks5, chopsticks1, 5);

        //启动线程,哲学家开始就餐
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

//筷子类
class Chopsticks {
}

**解决死锁问题的三种方案

哲学家就餐问题解决:

最少保证

/**
 * @document: 哲学家就餐问题(死锁解决)
 * 1、最少保证
 * 最多只有四位哲学家拿起左手边的筷子,最后一名哲学家先拿起右手边的筷子
 * 这样能保证每一次有一个科学家和别人拿起的筷子是相反的(循环等待条件不成立)
 * @Author:SmallG
 * @CreateTime:2023/8/10+15:13
 */

public class PhilosopherThread01 extends Thread {

    private Chopsticks left;
    private Chopsticks right;
    private int index;

    public PhilosopherThread01(Chopsticks left, Chopsticks right, int index) {
        this.left = left;
        this.right = right;
        this.index = index;
    }

    @Override
    public void run() {
        if (index == 5) {
            //最后一名科学家拿起右手边的筷子
            synchronized (right) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(index + "等待拿起左手边的筷子");
                synchronized (left){
                    System.out.println(index+"拿到了左手边的筷子开始吃饭!");
                }
            }
        }else{
            synchronized (left){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(index + "等待拿起右手边的筷子");
                synchronized (right){
                    System.out.println(index+"拿到了右手边的筷子开始吃饭!");
                }
            }
        }
    }

    public static void main(String[] args) {
        //创建5根筷子
        Chopsticks chopsticks1 = new Chopsticks();
        Chopsticks chopsticks2 = new Chopsticks();
        Chopsticks chopsticks3 = new Chopsticks();
        Chopsticks chopsticks4 = new Chopsticks();
        Chopsticks chopsticks5 = new Chopsticks();
        //创建五名哲学家
        PhilosopherThread01 t1 = new PhilosopherThread01(chopsticks1, chopsticks2, 1);
        PhilosopherThread01 t2 = new PhilosopherThread01(chopsticks2, chopsticks3, 2);
        PhilosopherThread01 t3 = new PhilosopherThread01(chopsticks3, chopsticks4, 3);
        PhilosopherThread01 t4 = new PhilosopherThread01(chopsticks4, chopsticks5, 4);
        PhilosopherThread01 t5 = new PhilosopherThread01(chopsticks5, chopsticks1, 5);

        //启动线程,哲学家开始就餐
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

奇偶互反:

奇偶互反

/**
 * @document: 哲学家就餐问题解决方案二
 * 奇偶互反
 * 奇偶数获取相反的顺序,哲学家的编号从1到5,奇数的哲学家先拿起左手边的筷子,
 * 偶数哲学家先拿起右手边的筷子
 * @Author:SmallG
 * @CreateTime:2023/8/10+15:28
 */

public class PhilosopherThread02 extends Thread {
    private Chopsticks left;
    private Chopsticks right;
    private int index;

    public PhilosopherThread02(Chopsticks left, Chopsticks right, int index) {
        this.left = left;
        this.right = right;
        this.index = index;
    }

    public void run() {
        if (index % 2 == 0) {
            //偶数科学家拿起右手边的筷子
            synchronized (right) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(index + "等待拿起左手边的筷子");
                synchronized (left) {
                    System.out.println(index + "拿到了左手边的筷子开始吃饭!");
                }
            }
        } else {
            synchronized (left) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(index + "等待拿起右手边的筷子");
                synchronized (right) {
                    System.out.println(index + "拿到了右手边的筷子开始吃饭!");
                }
            }
        }
    }

    public static void main(String[] args) {
        //创建5根筷子
        Chopsticks chopsticks1 = new Chopsticks();
        Chopsticks chopsticks2 = new Chopsticks();
        Chopsticks chopsticks3 = new Chopsticks();
        Chopsticks chopsticks4 = new Chopsticks();
        Chopsticks chopsticks5 = new Chopsticks();
        //创建五名哲学家
        PhilosopherThread02 t1 = new PhilosopherThread02(chopsticks1, chopsticks2, 1);
        PhilosopherThread02 t2 = new PhilosopherThread02(chopsticks2, chopsticks3, 2);
        PhilosopherThread02 t3 = new PhilosopherThread02(chopsticks3, chopsticks4, 3);
        PhilosopherThread02 t4 = new PhilosopherThread02(chopsticks4, chopsticks5, 4);
        PhilosopherThread02 t5 = new PhilosopherThread02(chopsticks5, chopsticks1, 5);

        //启动线程,哲学家开始就餐
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

线程粗放:

/**
 * @document: 哲学家就餐问题解决方案三
 * 线程粗放:所有科学家同时拿起左右手的筷子
 * @Author:SmallG
 * @CreateTime:2023/8/10+15:41
 */

public class PhilosopherThread03 extends Thread {
    private Chopsticks left;
    private Chopsticks right;
    private int index;
    private String flag; //标记

    public PhilosopherThread03(Chopsticks left, Chopsticks right, int index, String flag) {
        this.left = left;
        this.right = right;
        this.index = index;
        this.flag = flag;
    }

    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (flag) {
            System.out.println(index + "同时拿起左右手的筷子,开始吃饭");
        }
    }

    public static void main(String[] args) {
        //创建5根筷子
        Chopsticks chopsticks1 = new Chopsticks();
        Chopsticks chopsticks2 = new Chopsticks();
        Chopsticks chopsticks3 = new Chopsticks();
        Chopsticks chopsticks4 = new Chopsticks();
        Chopsticks chopsticks5 = new Chopsticks();
        //创建五名哲学家
        PhilosopherThread03 t1 = new PhilosopherThread03(chopsticks1, chopsticks2, 1,"hello");
        PhilosopherThread03 t2 = new PhilosopherThread03(chopsticks2, chopsticks3, 2,"hello");
        PhilosopherThread03 t3 = new PhilosopherThread03(chopsticks3, chopsticks4, 3,"hello");
        PhilosopherThread03 t4 = new PhilosopherThread03(chopsticks4, chopsticks5, 4,"hello");
        PhilosopherThread03 t5 = new PhilosopherThread03(chopsticks5, chopsticks1, 5,"hello");

        //启动线程,哲学家开始就餐
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

5、API的线程安全

StringBuilder是非线程安全的API,在多线程操作StringBuilder的时候会出现问题。

/**
 * @document: 非线程安全的API StringBuilder
 * @Author:SmallG
 * @CreateTime:2023/8/10+16:13
 */

public class Demo03 {
    public static void main(String[] args) {
        StringBuilder builder = new StringBuilder();
        int number = 3;
        //创建一个线程对象
        Runnable run = () -> {
            for (int i = 0; i < 10000; i++) {
                builder.append("A");
            }
        };
        //创建一个集合
        List<Thread> threadList = new ArrayList<>();
        for (int i = 0; i < number; i++) {
            Thread t = new Thread(run);
            threadList.add(t);
            t.start();
        }

        //等待所有线程完成
        for (Thread thread : threadList) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("程序执行完成,字符串的长度是" + builder.length());
    }
}

StringBuffer是线程安全的,他的关键方法都使用了synchronized关键字进行同步控制,但是他在性能上没有StringBuilder好。

/**
 * @document: StringBuffer和StringBuilder的对比
 * @Author:SmallG
 * @CreateTime:2023/8/10+16:33
 */

public class Demo05 {
    public static void main(String[] args) {
        StringBuffer buffer = new StringBuffer();
        StringBuilder builder = new StringBuilder();
        int num = 100000000;
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            buffer.append("a");
        }
        long endTime = System.currentTimeMillis();
        System.out.println("StringBuffer耗时:" + (endTime - startTime)+"毫秒");

        startTime =System.currentTimeMillis();
        for (int i = 0; i < num; i++) {
            builder.append("a");
        }
        endTime =System.currentTimeMillis();
        System.out.println("StringBuilder耗时:" + (endTime - startTime)+"毫秒");
    }
}

四、练习

1 编写计时线程

编写一个守护线程程序,每隔5秒钟输出当前的时间,主线程结束后计时完毕。

程序运行效果如下所示:

练习1

/**
 * @document: 编写计时线程
 * @Author:SmallG
 * @CreateTime:2023/8/10+16:42
 */

public class DaemonDemo {
    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.CHINA);
        Runnable run = () -> {
            LocalTime localTime = LocalTime.now();
            String data01 = localTime.format(formatter);
            System.out.println(data01);
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            localTime = LocalTime.now();
            String data02 = localTime.format(formatter);
            System.out.println(data02);
            System.out.println("main线程结束了");
        };
        Thread t = new Thread(run);
        t.start();
    }
}

2 【简答题】线程的创建方式

请介绍一下在Java中创建线程的方式有几种,它们有什么区别?

1、继承 Thread 类:

  • 创建一个类,继承 Thread 类,并重写 run() 方法,定义线程的执行逻辑。
  • 调用子类的 start() 方法启动线程。
  • 优点:简单直观,适用于简单的线程任务。
  • 缺点:由于Java不支持多重继承,如果一个类已经继承了其他类,就不能再继承 Thread 类。

2、实现 Runnable 接口:

  • 创建一个类,实现 Runnable 接口,并实现 run() 方法,定义线程的执行逻辑。
  • 创建一个 Thread 对象,将实现了 Runnable 接口的对象作为参数传入。
  • 调用 Thread 对象的 start() 方法启动线程。
  • 优点:避免了Java单继承的限制,可以更灵活地处理线程任务。
  • 缺点:相对于继承 Thread 类,稍微复杂一些。

3 【简答题】run方法和start方法的区别

请介绍一下Java中Thread类的run方法和start方法有什么区别?

在Java中, Thread 类中的 run() 方法和 start() 方法是创建和启动线程的两个重要方法,它们的区别如下:

1、run() 方法:

  • run() 方法是 Thread 类中的一个普通方法,用于定义线程的执行逻辑。
  • 在使用继承 Thread 类创建线程时,需要重写 run() 方法,将线程要执行的代码逻辑写在 run() 方法中。
  • 当调用 Thread 对象的 start() 方法时,会启动新的线程,并在新的线程中调用 run() 方法执行线程的逻辑。
  • 在单线程环境中,直接调用 run() 方法并不会启动一个新线程,而是在当前线程中执行 run() 方法的逻辑。

2、start() 方法:

  • start() 方法是 Thread 类中的一个特殊方法,用于启动一个新的线程。
  • 当调用 Thread 对象的 start() 方法时,会创建一个新的线程,并在新的线程中调用 run() 方法执行线程的逻辑。
  • 在多线程环境中,start() 方法会启动新线程并与当前线程并行执行,而不会阻塞当前线程。

综上,run() 方法是用于定义线程的执行逻辑,而 start() 方法是用于启动一个新线程并执行 run() 方法的逻辑。只有通过 start() 方法启动的线程才能真正实现多线程并发执行。

4 两线程打印奇偶数

请设计一个程序,程序开始后会启动2个子线程,一个子线程负责输出10以内的奇数,另一个子线程负责输出10以内的偶数,要求两个线程是按照先后顺序逐步输出。

提示:

1、可以通过sleep()方法和interrupt()方法控制线程执行顺序

2、可以通过继承Thread类的方式,为线程类添加新的属性,来保存该线程对象需要用到的属性

程序运行效果如下所示:

练习2

/**
 * @document: 两线程打印奇偶数
 * @Author:SmallG
 * @CreateTime:2023/8/10+19:01
 */

public class OddAndEven {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 1; i < 10; i += 2) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    TimeUnit.MILLISECONDS.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "奇数线程");

        Thread t2 = new Thread(() -> {
            for (int i = 2; i <= 10; i += 2) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                try {
                    TimeUnit.MILLISECONDS.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, "偶数线程");
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t2.start();
    }
}
最后修改:2023 年 08 月 10 日
如果觉得我的文章对你有用,请随意赞赏