2. 多线程编程

112 阅读4分钟

CS-Notes/notes/Java 并发.md at master · CyC2018/CS-Notes (github.com)

【【狂神说Java】多线程详解】 www.bilibili.com/video/BV1V4…

开辟线程

Thread

public class TestThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("countdown tik : " + i);
        }
    }
    public static void main(String[] args) {
        TestThread1 t = new TestThread1();
        t.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("okok" + i);
        }
    }
}

Runable

public class TestThread3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("countdown tik : " + i);
        }
    }
    public static void main(String[] args) {
        TestThread3 testThread3 = new TestThread3();
//        Thread thread = new Thread(testThread3);
//        thread.start();
        new Thread(testThread3).start();
        for (int i = 0; i < 200; i++) {
            System.out.println("okok" + i);
        }
    }
}

小结

继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象.start()
  • 不建议使用:避免OOP单继承局限性 实现Runnable接口
  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

并发问题

// 下面会多个线程一起操作,会产生并发问题,日期错乱
public class TestThread4 implements Runnable{
    private int ticketNumber = 10;

    @Override
    public void run() {
        while (true) {

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (ticketNumber <= 0)break;
            System.out.println(Thread.currentThread().getName() +"拿到了第" + ticketNumber -- + "张票");
        }
    }

    public static void main(String[] args) {
        TestThread4 ticket = new TestThread4();

        new Thread(ticket,"cfd").start();
        new Thread(ticket,"glz").start();
        new Thread(ticket,"lh").start();
    }
}

多个线程并发访问变量ticketNumber,导致出现并发错误

代理模式

总结:
代理者和被代理者需要实现相同的接口
被代理者必须是真实对象

好处:
代理对象可以做很多被代理对象做不了的事情
真实对象就专注做自己的事情

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

        // 匿名闭包函数 lambda λ
        new Thread(() -> System.out.println("I Love you Concurrently")).start();

        WeddingCompany weddingCompany = new WeddingCompany(new CFD());
        weddingCompany.HappyMarry();
    }
}
interface Marry{
    void HappyMarry();
}
// 真实角色
class CFD implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("CFD is Happy");
    }
}
// 代理对象
class WeddingCompany implements Marry{
    private Marry target;
    public WeddingCompany(Marry target){
        this.target = target;
    }
    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();
        after();
    }
    private void before() {
        System.out.println("布置婚礼");
    }
    private void after() {
        System.out.println("收拾婚礼现场");
    }

}

lambda

() -> System.out.println("I Love you Concurrently");
  • (params)->expression[表达式]
  • (params)>statement[语句]
  • (params)->fstatements }

函数式接口

理解FunctionalInterface(函数式接口)是学习Java8 lambda表达式的关键所在

函数式接口的定义:

任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

public interface Runnable {  
	public abstract void run();  
}

对于函数式接口,我们可以通过lambda表达式来创建该接口的对象,他会自动推导使用该方法

lambda表达式在Java8以前没有出现的替换品


public class lambda {
    //3. 静态内部类
    static class Like2 implements People {

        @Override
        public void Study() {
            System.out.println("I'm studying static lambda");
        }
    }
    public static void main(String[] args) {
        People like = new Like();
        like.Study();

        People like2 = new Like2();
        like2.Study();

        // 4. 局部内置类
        class Like3 implements People {
            @Override
            public void Study() {
                System.out.println("I'm studying local lambda");
            }
        }
        People like3 = new Like3();
        like3.Study();

        //5. 匿名内部类,没有内部类,必须借助接口或者父类
        like = new People() {
            @Override
            public void Study() {
                System.out.println("I'm studying anonymous lambda");
            }
        };
        like.Study();

        //6. lambda简化
        like = ()-> {
            System.out.println("I'm studying anonymous lambda");
        };
        like.Study();

    }
}
// 1.定义一个函数式接口
interface People{
    void Study();
//    void Stupid();
}
// 2.实现类
class Like implements People {

    @Override
    public void Study() {
        System.out.println("I'm studying lambda");
    }
}

lambda 表示的用法示例


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

        // 简化再简化,简化的地方只能有一个变量或者语句
        Feelings fellings = (num)->{
            System.out.println("I appreciate you " + num);
        };
        fellings = num->{
            System.out.println("I appreciate you " + num);
        };
        fellings = num->System.out.println("I appreciate you " + num);
        fellings.express(10);
    }
}
interface Feelings {
    void express(int num);
}
class appreciate implements Feelings{

    @Override
    public void express(int num) {
        System.out.println("I appreciate you" + num);
    }
}

线程相关api

MyConcurrencyProcessInJava/src/main/java/demo04 at master · cfddd/MyConcurrencyProcessInJava (github.com)

join

详解java Thread中的join方法 - 知乎 (zhihu.com)

守护进程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕,如main函数
  • 虚拟机不用等待守护线程执行完毕,如后台记录操作日志,监控内存,gc垃圾回收等待
public class ThreadDaemon {
    public static void main(String[] args) {
        God god = new God();
        People people = new People();

        Thread thread = new Thread(god);
        thread.setDaemon(true);     //false Norm;true Daemon


        thread.start();     // 一直运行,直到全部线程运行结束后,自动结束

        new Thread(people).start();

    }
}

class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("God bless you");
        }
    }
}

class People implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("living passionately");
        }
        System.out.println("Goodbye World");
    }
}

线程同步synchronized隐式锁

锁 + 队列,就可以实现

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可.存在以下问题

一个线程持有锁会导致其他所有需要此锁的线程挂起

在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;

如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题

public class UnsafeAdd {
    static class Add implements Runnable {
        public int sum = 0;
        public boolean addOk = false;
        @Override
        public synchronized void run() {
            if (addOk)return;
            sum ++;
            if (sum == 10000)addOk = true;
        }
    }
}

给函数区域加上 synchronized 即可

块锁

给一整个 status 对象加上锁

    static class Add implements Runnable {
        @Override
        public void run() {
            synchronized (status) { // 通过synchronized把临界区包起来,即使对象不在这里,也可以保护到
                while (!status.addOk) {
                    status.sum++;
                    if (status.sum >= 100000) {
                        status.addOk = true;
                    }
                }
            }
        }
    }

使用闭包时加锁,保证 List 的线程安全

        List<String> list = new ArrayList<String>(10);
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
//                list.add(Thread.currentThread().getName());   并发错误
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }
            ).start();
        }

JUC

ArrayList的线程安全变体,其中所有可变操作(添加、设置等)都通过制作底层数组的新副本来实现。

	public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
                }).start();
        }
        Thread.sleep(1000);
        System.out.println(list.size());
    }

死锁

定义不再赘述,操作系统里面有

Lock 显式锁

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了 作用域自动释放

Lock只有代码块锁,synchronized有代码块锁和方法锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:

Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

public class Lock {
    public static void main(String[] args) {
        Add add = new Add();
        new Thread(add,"cfd").start();
        new Thread(add,"lh").start();
        new Thread(add,"glz").start();
    }
}

class Add implements Runnable{
    int countdown = 20;
    // 无法被继承final
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                if (countdown > 0){
                    System.out.println(Thread.currentThread().getName() + " got the lock ,countdown is " + countdown --);
                }else{
                    break;
                }
            }finally {
                lock.unlock();
            }

        }
    }
}

线程通信

生产者与消费者——管程

类似 go 语言的 channel ,下面是有缓冲版本

// 管程法
// 生产者,消费者,缓冲区,数据
public class ProductorAndConsumer {
    public static void main(String[] args) {
        SyncArea syncArea = new SyncArea();
        Consumer consumer = new Consumer(syncArea);
        Productor productor = new Productor(syncArea);
        consumer.start();
        productor.start();

    }
}
class Productor extends Thread{
    SyncArea syncArea;
    public Productor(SyncArea syncArea){
        this.syncArea = syncArea;
    }

    @Override
    public void run() {
        for (int i = 0; i < 250; i++) {
            System.out.println("生产了" + i + "个商品");
            syncArea.push(new Products(i));
        }
    }
}

class Consumer extends Thread{
    SyncArea syncArea;
    public Consumer(SyncArea syncArea){
        this.syncArea = syncArea;
    }

    @Override
    public void run() {
        for (int i = 0; i < 250; i++) {
            syncArea.pop();
            System.out.println("消费了第" + i + "个商品");
        }
    }
}
class SyncArea {
    Products[] products = new Products[20];
    int count = 0;

    public synchronized void push(Products product){
        if (products.length == count){
            try {
                // 等待消费者
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        products[count ++] = product;
        // 通知消费者无需等待了
        this.notifyAll();
    }

    public synchronized Products pop(){
        if (count == 0){
            try {
                // 等待生产者
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Products product = products[--count];
        // 通知消费者无需等待了
        this.notifyAll();
        return product;
    }
}

class Products {
    int id;
    public Products(int id){
        this.id = id;
    }
}

信号量

下面是无缓冲版本

// 管程法
// 生产者,消费者,缓冲区,数据
// 无缓冲
// 播放了:第0集
//观看了:第0集
//播放了:第1集
//观看了:第1集
//播放了:第2集
//观看了:第2集
// ...
public class ProductorAndConsumer2 {
    public static void main(String[] args) {
        TV tv = new TV("老三国");
        new Interviewer(tv).start();
        new Interviewee(tv).start();

    }
}

class Interviewer extends Thread{
    TV tv;
    public Interviewer(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 250; i++) {
            tv.show("第"+i+"集");
        }
    }
}
class Interviewee extends Thread{
    TV tv;
    public Interviewee(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 250; i++) {
            tv.watch();
        }
    }
}
class TV{
    String name ;
    boolean flag = true;
    public TV(String name){
        this.name = name;
    }
    public synchronized void show(String name){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("播放了:"+name);
        this.notifyAll();
        this.name=name;
        flag = !flag;
    }
    public synchronized void watch(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("观看了:" + name);
        this.notifyAll();
        flag = !flag;
    }
}

线程池

  • 背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理(....)
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

execute 需要的是 implements Runnable的类的对象

    public static void main(String[] args) {
        // 1.创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 2.加入线程
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        // 3.关闭线程池
        service.shutdown();

    }

submit 需要的是 implements Callable<Object> 的对象

public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://commons.apache.org/io/images/commons-logo.png","1.png");
        TestCallable t2 = new TestCallable("https://pic1.zhimg.com/80/v2-2b6f88d8ff119c91d8802bf8092f056c_720w.webp","2.png");
        TestCallable t3 = new TestCallable("https://pic3.zhimg.com/80/v2-197402121bc8256c2b4d7bc815ac40de_720w.webp","3.png");
        // 创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);

        // 提交执行
        Future<Object> r1 = ser.submit(t1);
        Future<Object> r2 = ser.submit(t2);
        Future<Object> r3 = ser.submit(t3);
        // 获取结果
        Object res1 = r1.get();
        Object res2 = r2.get();
        Object res3 = r3.get();

        System.out.println(res1);
        System.out.println(res2);
        System.out.println(res3);
        // 关闭服务
        ser.shutdown();
    }

douyin.cfddfc.online/myPicture/%…