个人笔记--多线程基础

时间:2022-11-19 06:26:32

java运行时最起码有两条线程,主线程(main方法)和垃圾回收线程。

并行:指物理上同时执行。

并发:指能够让多个任务在逻辑上交织执行的程序设计。

 

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

随机性原理:因为cpu的快速切换造成的,线程会争夺cpu的执行权。

 

线程的运行代码统一存放在了run方法中,用start方法开启线程。

直接调用run()方法的话,系统只会把线程对象当成一个普通对象,把run()方法也当成一个普通方法,而不是线程执行体。

 

创建线程的方式有四种:

1.继承Thread类:

public class Test01 {
    public static void main(String[] args) {
        MyThread mt = new MyThread(); // 4,创建Thread类的子类对象
        mt.start(); // 5,开启线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("bb");
        }
    }
}

class MyThread extends Thread { // 1,继承Thread
    public void run() { // 2,重写run方法
        for (int i = 0; i < 1000; i++) { // 3,将要执行的代码写在run方法中
            System.out.println("第一种方式");
        }
    }
}

好处:可以直接使用Thread类中的方法。

弊端:有父类就不能用这种方法(即单继承的弊端)。

 

2.实现Runnable接口:

public class Test02 {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable(); // 4,创建Runnable的子类对象
        Thread t = new Thread(mr); // 5,将其当作参数传递给Thread的构造函数
        t.start(); // 6,开启线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("bb");
        }
    }
}

好处和弊端刚好和继承Thread类相反,两者是互补的。

 

匿名内部类方式:

public class Test03 {
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("AA");
                }
            };
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("BB");
                }
            }
        }).start();
    }
}

 

3.实现Callable:

个人笔记--多线程基础

例子:

个人笔记--多线程基础

 

结果:

个人笔记--多线程基础

横线一直没有输出,在等待线程执行完毕。这说明FutureTask可用于闭锁。

 

补充:

个人笔记--多线程基础

 

 使用场景:

 个人笔记--多线程基础

 

结果:

个人笔记--多线程基础

4.实现线程池:

提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。

例如:常量池中会存储-128~127之间的Integer对象原理一样。

 

线程池体系结构:

java.util.concurrent.Executor : 负责线程的使用与调度的根接口
        |--ExecutorService 子接口: 线程池的主要接口
             |--ThreadPoolExecutor 线程池的实现类
             |--ScheduledExecutorService 子接口:负责线程的调度
                 |--ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现 ScheduledExecutorService

 

分类:

ExecutorService newFixedThreadPool() : 创建固定大小的线程池

ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。

 

 

参考:java线程池的核心线程数与最大的线程数的区别,饱和策略

 

 

例子:

public class TestThreadPool {
    public static void main(String[] args) throws Exception {
        //1. 创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        List<Future<Integer>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<Integer> future = pool.submit(new Callable<Integer>(){
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for (int i = 0; i <= 100; i++) {
                        sum += i;
                    }
                    return sum;
                }
            });
            list.add(future);
        }
        pool.shutdown();
        for (Future<Integer> future : list) {
            System.out.println(future.get());
        }
        /*
ThreadPoolDemo tpd = new ThreadPoolDemo();
 //2. 为线程池中的线程分配任务
        for (int i = 0; i < 10; i++) {
            pool.submit(tpd);
        }
        
        //3. 关闭线程池
        pool.shutdown();*/
    }
    
//    new Thread(tpd).start();
//    new Thread(tpd).start();
}
class ThreadPoolDemo implements Runnable{
    private int i = 0;
    @Override
    public void run() {
        while(i <= 100){
        System.out.println(Thread.currentThread().getName() + " : " + i++);
        }
    }
}
                

 

线程调度:

public class TestScheduledThreadPool {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 5; i++) {
        Future<Integer> result = pool.schedule(new Callable<Integer>(){
          @Override
          public Integer call() throws Exception {
          int num = new Random().nextInt(100);//生成随机数                    
          System.out.println(Thread.currentThread().getName() + " : " + num);
          return num;
        }
}, 1, TimeUnit.SECONDS);
        System.out.println(result.get());
        }
        pool.shutdown();
    }
}

 

控制线程:

设置线程的名字:setName()通过构造方法
获取名字:getName()
获取当前线程的对象:Thread.currentThread();
让线程休眠指定的时间:Thread.sleep(毫秒值)
守护线程setDaemon(true):把一个线程设置为守护线程(当其他线程都结束了,它如果没有执行完毕也会结束.)
让线程插队join():让指定的线程优先执行,然后其他的线程再执行
礼让线程yield():让出CPU执行权.
设置线程的优先级:线程优先级从 1-10  
默认的线程优先级:5  setPriority()

 

线程状态转换:

个人笔记--多线程基础

 

线程的停止:让线程运行的代码结束,也就是结束run方法。

怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。

定义循环的结束标记。
如果线程处于了冻结状态,是不可能读到标记的。

这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

 

问:用户线程和守护线程有什么区别?

答:所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。

因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。

 

Java创建线程之后,直接调用start()方法和run()的区别:

run()方法,在本线程内调用该Runnable对象的run()方法,可以重复多次调用。
start()方法,启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程。

当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。

但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。

 

例子1:

/**
*创建一个Bank银行类,属性有money(初始值为100),生成set/get方法,创建一个Bank银行对象,
*创建三个线程(分别给线程命名为"用户A","用户B","用户C"),
*当用户A线程执行时,通过set方法将money的值增加100,在控制台输出 "用户A线程正在执行第X次,增加了100元,目前money的值为X元"
*当用户B线程执行时,通过set方法将money值随机增加1-100(不含100),在控制台输出"用户B线程正在执行第X次,增加了X元,目前money的值为X元"
*当用户C线程执行时,线程休眠10毫秒,不作任何操作.在控制台输出"用户C线程正在执行第X次,睡眠了10毫秒"
*共执行20次,最后打印输出money的值,如 "增加后的money值为:X元"
*注意:(机子速度太快有可能会出现一条线程全执行完,关键要实现需求)
*/
public class Test01 {
    public static void main(String[] args) {
        BankThread b1 = new BankThread("用户A");
        BankThread b2 = new BankThread("用户B");
        BankThread b3 = new BankThread("用户C");
        b2.start();
        b3.start();
        b1.start();

    }
}

class BankThread extends Thread {
    BankThread(String str) {
        super(str);
    }
    static Bank b = new Bank();
    static int count = 20;
    public void run() {
        while (true) {
            synchronized (BankThread.class) {
                if (count > 20) {
                    break;
                }
                if ("用户A".equals(this.getName())) {
                    b.setMoney(b.getMoney() + 100);
                    System.out.println("用户A线程正在执行第" + count + "次,增加了100元,目前money的值为" + b.getMoney() + "元");
                } else if ("用户B".equals(this.getName())) {
                    Random r = new Random();
                    int num = r.nextInt(100);
                    b.setMoney(b.getMoney() + num);
                    System.out.println("用户B线程正在执行第" + count + "次,增加了" + num + "元,目前money的值为" + b.getMoney() + "元");
                } else if ("用户C".equals(this.getName())) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("用户C线程正在执行第X次,睡眠了10毫秒");
                }
                count++;
            }
        }
    };
}

class Bank {
    private int money = 100;
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
}

 

例子2:

/**
* 模拟3个老师同时发80份笔记, 每个老师相当于一个线程, 分别给三个线程命名为”张老师线程,林老师线程,李老师线程”,
* 要求在控制台输出"xxx老师"在发第"xxx"份笔记 2) 如果要求最后一张试卷必须由”李老师线程”发出,请问应该怎么做?
*/
public class Test02 {
    public static void main(String[] args) {
        ShiJuan shi = new ShiJuan();
        Thread t1 = new Thread(shi);
        Thread t2 = new Thread(shi);
        Thread t3 = new Thread(shi);
        t1.setName("张老师");
        t2.setName("林老师");
        t3.setName("李老师");
        t1.start();
        t2.start();
        t3.start();
    }
}

class ShiJuan implements Runnable {
    int shijuan = 80;
    @Override
    public void run() {
        while (true) {
            synchronized (ShiJuan.class) {
                if (shijuan <= 0) {
                    break;
                }
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (shijuan == 1 && !Thread.currentThread().getName().equals("李老师")) {
                    continue;
                }
                System.out.println(Thread.currentThread().getName() + "正在发" + shijuan);
                System.out.println(shijuan--);
            }
        }
    }
}

 

 例子3:

/* 模拟抢红包过程,生成50个红包(金额是随机的,范围在1-10元之间)
        创建5个线程代表5个人,然后让这5个人去抢这50个红包,每次抢红包需要300ms的时间,
        在控制台打印出(xxx抢了xxx元)(不限定每人抢的次数并且抢到红包后还可以接着抢,每次生成一个红包).
*/
public class Test03 {
    public static void main(String[] args) {
        HongBao_1 nb =  new HongBao_1();
        Thread t1 = new Thread(nb);
        Thread t2 = new Thread(nb);
        Thread t3 = new Thread(nb);
        Thread t4 = new Thread(nb);
        Thread t5 = new Thread(nb);
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t4.setName("线程4");
        t5.setName("线程5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        }}

        class HongBao_1 implements Runnable{
        //这里的变量不需要静态
        int hongbao=50;
        Random r = new Random();
        @Override
        public void run() {
        while(true){
        synchronized(HongBao_1.class){
        if(hongbao<=0){
        break;
        }
        try {
        Thread.sleep(30);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        int num = r.nextInt(10)+1;
        System.out.println(Thread.currentThread().getName()+"抢到了"+num+"元");
        System.out.println(hongbao--);
        }}
    }
}