JAVA多线程_2021学习理解_SXL


引言

做个俗人,贪财好色,一身正气。


一、多线程理解(基本概念整理)

1.1 线程和进程

进程
一个内存中运行的应用程序,每个进程都有一个独立的内存空间。

线程
进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。

线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

1.2 线程调度

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

抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

1.3 同步与异步

同步:排队执行 , 效率低但是安全
异步:同时执行 , 效率高但是数据不安全


二、线程的实现方式

每个线程都有自己的一份栈空间,共用一份堆内存。

2.1 继承Thread

public class Test {

    public static void main(String[] args) {
        MyThread m = new MyThread();
        // 启动m线程
        m.start();

		// 以匿名内部类实现的线程
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("匿名内部类实现的线程" + i);
            }
        }).start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程" + i);
        }

    }

}

class MyThread extends Thread {

    /**
     * run方法是线程要执行的方法
     */
    @Override
    public void run() {
        // 此处是一条新的执行路径
        // 执行此处不是调用run()方法,而是调用Thread对象的start()方法来启动任务线程
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread线程" + i);
        }
    }
}

2.2 实现Runnable(频率多)

实现Runnable 和 继承Thread相比有如下优势:

  1. 通过创建任务,然后给线程分配的方式实现的多线程,更适合多个线程执行相同任务的情况
  2. 可以避免单继承所带来的局限性,接口是可以多实现的
  3. 任务和程序是分离的,提高了程序的健壮性
  4. 线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
public class Test{

    public static void main(String[] args) {
        // 实现Runnable
        // 创建一个任务对象
        MyRunnable r = new MyRunnable();
        // 创建一个线程,分配一个任务
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
    }

}

class MyRunnable implements Runnable {

    /**
     * run方法是线程要执行的方法
     */
    @Override
    public void run() {
        // 此处是一条新的执行路径
        // 执行此处不是调用run()方法,而是调用Thread对象的start()方法来启动任务线程
        for (int i = 0; i < 10; i++) {
            System.out.println("MyRunnable线程" + i);
        }
    }
}

2.3 实现Callable(频率少)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class test1 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Callable写法实现
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(c);
        Thread t = new Thread(task, "子线程");
        t.start();
        // 主线程不进行参与直到完成
        Integer j = task.get();
        System.out.println("分割线===================" + j);

        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + i);
        }
    }

}

class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        // Thread.sleep(3000);
        for (int i = 0; i < 10; i++) {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + i);
        }
        return 100;
    }

}

三、Thread类

线程分为守护线程和用户线程

  • 用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
  • 守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡
public class Test2 {

    public static void main(String[] args) throws InterruptedException {
        // 线程的休眠 sleep
        // 输出 每隔一秒循环输出数字
        /*for (int i = 0; i < 10; i++) {
            System.out.println(i);
            Thread.sleep(1000);
        }*/

        Thread t0 = new Thread(new MyRunnable(), "守护线程");
        // 设置守护线程
        t0.setDaemon(true);
        t0.start();

        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        // 添加中断标记
        t1.interrupt();

        // 获取线程名称
        // currentThread() 获取当前线程对象
        // 输出main
        System.out.println(Thread.currentThread().getName());
        // 输出MyRunnable子线程 Thread-num等形式
        // new Thread(new MyRunnable(), "MyRunnable子线程").start();

        for (int i = 0; i < 3; i++) {
            System.out.println(i);
            Thread.sleep(1000);
        }
    }

}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("又是风评梁静的一天。==");
                // 结束run方法,等于线程执行完成。
                return;
            }
        }
    }
}



四、线程安全问题

同步代码块和同步方法都属于隐式锁。

Java默认的是非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。(实现方法:Lock l = new ReentrantLock(true); 显示锁构造方法中参数为true表示为公平锁)
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

4.1 同步代码块和同步方法

public class Test {

    public static void main(String[] args) {
        Runnable runnable = new Ticket();
        Thread t1 = new Thread(runnable,"窗口1");
        Thread t2 = new Thread(runnable,"窗口2");
        Thread t3 = new Thread(runnable,"窗口3");

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

}

class Ticket implements Runnable {

    // 票数
    private int count = 1000;

    @Override
    public void run() {

        // 解决方案2:同步方法
        /*while (true) {
            Boolean flag = sale();
            if (!flag) {
                break;
            }
        }*/

        // 解决方案1:同步代码块
        // 格式: synchronized(锁对象) {}
        while (true) {
            synchronized (this) {
                if (count > 0) {
                    // 票数
                    count--;
                    System.out.println(Thread.currentThread().getName() + "余票:" + count);
                } else {
                    break;
                }
            }
        }
    }

    // synchronized 锁住对象 非静态this 静态为类.class
    public synchronized boolean sale() {
        if (count > 0) {
        // 票数
        count--;
        System.out.println(Thread.currentThread().getName() + "余票:" + count);
        return true;
        }
        return false;
    }
}

4.2 显式锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

    public static void main(String[] args) {
        // 线程不安全
        // 解决方案1:同步代码块
        // 格式: synchronized(锁对象) {}
        Runnable runnable = new Ticket();
        Thread t1 = new Thread(runnable,"窗口1");
        Thread t2 = new Thread(runnable,"窗口2");
        Thread t3 = new Thread(runnable,"窗口3");

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

}

class Ticket implements Runnable {

    // 票数
    private int count = 1000;

    // 显示锁
    Lock l = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
        	// 此处最好有try catch 防止死锁等异常
            l.lock();
            if (count > 0) {
                // 票数
                count--;
                System.out.println(Thread.currentThread().getName() + "余票:" + count);
            } else {
                break;
            }
            l.unlock();
        }
    }
}

4.3 notifyAll 和 wait

public class test {

    public static void main(String[] args) {
        Food food = new Food();
        new Cooker(food).start();
        new Waiter(food).start();
    }

}

/**
 * 厨师
 */
class Cooker extends Thread {

    private Food food;

    public Cooker(Food food) {
        this.food = food;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                food.setNameAndTaste("馒头", "淡的");
            } else {
                food.setNameAndTaste("酸菜", "酸的");
            }
        }
    }
}

/**
 * 服务生
 */
class Waiter extends Thread {

    private Food food;

    public Waiter(Food food) {
        this.food = food;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            food.get();
        }
    }

}

/**
 * 食物
 */
class Food {

    private String name;

    private String taste;

    private Boolean flag = true;

    public synchronized void setNameAndTaste(String name, String taste) {
        if (flag) {
            this.name = name;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste = taste;
            flag = false;
            // notifyAll唤醒此对象所有线程
            this.notifyAll();
            // wait此线程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public synchronized void get() {
        if (!flag) {
            System.out.println("服务生菜名称:" + name + ",味道:" + taste);
            flag = true;
            // notifyAll唤醒此对象所有线程
            this.notifyAll();
            // wait此线程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTaste() {
        return taste;
    }

    public void setTaste(String taste) {
        this.taste = taste;
    }
}

五、线程池

线程池 Executors
并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,创建线程和销毁线程需要时间,频繁的创建线程就会大大影响系统的效率。线程池容纳了多个线程,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

线程池的好处

  • 降低资源消耗。
  • 提高响应速度。
  • 提高线程的可管理性。

5.1 缓存线程池

/**
*  缓存线程池.
*  (长度无限制)
*  执行流程:
*  1. 判断线程池是否存在空闲线程
*  2. 存在则使用
*  3. 不存在,则创建线程并放入线程池, 然后使用
*/
public class test {

    public static void main(String[] args) throws InterruptedException {

        // 向线程池中加入新的任务
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(() -> System.out.println(Thread.currentThread().getName()));
        service.execute(() -> System.out.println(Thread.currentThread().getName()));
        service.execute(() -> System.out.println(Thread.currentThread().getName()));

        Thread.sleep(1000);
        // 输出结果是上面三个空闲的线程
        service.execute(() -> System.out.println(Thread.currentThread().getName()));
    }

}

5.2 定长线程池

/**
*  定长线程池.
*  (长度是指定的数值)
*  执行流程:
*  1. 判断线程池是否存在空闲线程
*  2. 存在则使用
*  3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
*  4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
public class test {

    public static void main(String[] args) throws InterruptedException {

        // 向线程池中加入新的任务
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        });

        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread.sleep(1000);
        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }

}

5.3 单线程线程池

/**
*  效果与定长线程池创建时传入数值1效果一致.
*  单线程线程池.
*  执行流程:
*  1. 判断线程池的那个线程是否空闲
*  2. 空闲则使用
*  3. 不空闲,则等待池中的单个线程空闲后使用
*/
public class test {

    public static void main(String[] args) throws InterruptedException {

        // 向线程池中加入新的任务
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });

        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });

        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });

        Thread.sleep(1000);
        service.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }

}

5.4 周期定长线程池

/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
*  周期性任务执行时:
*  定时执行, 当某个时机触发时, 自动执行某任务
*/
public class test {

    public static void main(String[] args) throws InterruptedException {

        // 向线程池中加入新的任务
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        /*
         * 1. 定时执行一次
         *
         * 参数1:定时执行的任务
         * 参数2:时长数字
         * 参数3:时长数字的时间单位, TimeUnit的常量指定
         */
        service.schedule(() -> {
            System.out.println(Thread.currentThread().getName());
        }, 5, TimeUnit.SECONDS);

        /*
         * 2. 周期性执行
         *
         * 参数1:定时执行的任务
         * 参数2:延迟时长数字
         * 参数3:周期时长数字
         * 参数4:时长数字的时间单位, TimeUnit的常量指定
         */
        service.scheduleAtFixedRate(() -> {
            System.out.println(Thread.currentThread().getName());
        }, 5, 1, TimeUnit.SECONDS);
    }

}

总结

人生无限,缓缓起航,修正改错,在满足完成任务的条件下,追求完善,全身而退。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值