Java基础-线程

线程

1.1.线程的概念

进程和线程的区别

​ 进程是资源(CPU,内存)分配的基本单位,是静态的概念

​ 线程是程序运行的最小单位,真正干活的

​ 进程中至少会有一个线程

并行和并发:

​ 并发:是指同时发生了,程序支持并发而已,任务同时发生了;

​ 并行:是同时进行,同时执行某些任务

在单核CPU情况下,并不是真正的多线程,只不过是CPU在多个线程间进行快速切换,用户就认为线程在同时运行而已;

1.2.创建线程

1.继承Thread类,重写run方法

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程的执行逻辑
        for (int i = 0; i < 20; i++) {
            System.out.println("线程: " + i);
        }
    }
}

测试

public static void main(String[] args) {
        System.out.println("main 线程执行开始");
        // 创建线程对象
        Thread threadA = new MyThreadA();
        Thread threadB = new MyThreadB();
        //start方法 只是告诉OS调度器 线程准备好了而已 时间片
        threadA.start();
        threadB.start();
        System.out.println("main 线程执行完了");
    }

2.实现Runnable接口

   Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int l = 0; l < 20; l++) {
                    System.out.println("线程A " + l);
                }
            }
        }, "ThreadA");
        Thread threadB = new Thread(() -> {
            for (int l = 0; l < 20; l++) {
                System.out.println("线程B " + l);
            }
        }, "ThreadB");

        threadA.start();
        threadB.start();

3.线程执行完之后如果需要得到线程的返回结果,需要使用到线程间的通信;

实现 Callable接口,

FutureTask futureTask = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                for (int i = 0; i < 2000; i++) {
                    System.out.println(i);
                }
                return 120;
            }
        });
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
            // get()方法是阻塞时的方法,必须等线程执行完了才会得到返回值
            Object o = futureTask.get();
            System.out.println("线程的返回值是:" + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("主线程执行完了");

4.线程常用方法

 Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int l = 0; l < 10; l++) {
                    System.out.println("线程A " + l);
                }
            }
        }, "ThreadA");
        Thread threadB = new Thread(() -> {
            for (int l = 0; l < 10; l++) {
                System.out.println("线程B " + l);
            }
        }, "ThreadB");
        threadB.start();
        System.out.println("线程A的状态:" + threadA.getState());
        threadA.start();
        System.out.println("线程A的状态:" + threadA.getState());
        try {
            //当前线程休眠1秒 单位是毫秒
//            Thread.sleep(1000L);
//            TimeUnit.SECONDS.sleep(1L);
            //返回一个当前线程
            Thread thread = Thread.currentThread();
            // 获取线程名
            String name = thread.getName();
            // 获取线程ID
            long id = thread.getId();
            System.out.println("线程ID是:" + id);
            // 当前线程执行结束了,其他线程才能继续执行
            threadA.join();
            // 让出CPU时间片,给其他线程进行调度
//            Thread.yield();
            System.out.println("线程A的状态:" + threadA.getState());
            // 优先级
            int priority = threadA.getPriority();
            //
            System.out.println("线程A的优先级是:" + priority);
        } catch (Exception e) {
        }

1.3.线程池

频繁创建线程和销毁线程会消耗系统资源;使用线程池可以节省系统资源

 // newFixedThreadPool生成一个固定数量线程的线程池
//        ExecutorService pool = Executors.newFixedThreadPool(100);
        // 10 200 创建一个数量可变的线程池
        ExecutorService pool2 = Executors.newCachedThreadPool();
        pool2.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(i);
                }
                System.out.println(Thread.currentThread().getId());
            }
        });
        pool2.submit(() -> {
            for (int i = 0; i < 20; i++) {
                System.out.println(i);
            }
            return 1;
        });
        //把线程池关闭,
        pool2.shutdown();
//        pool.shutdown();

1.4. 线程安全问题

线程安全问题:多线程情况下,同时操作主存中某个数据的时候,出现数据不一致情况

1.4.1.线程同步

同步:一个任务的执行需要等待另一个任务的结束

异步:任务可以同时进行

1.synchronized关键字,内部JVM进行底层实现

  • 每个对象都有一把对象锁,同一时刻只能有一个线程获取这个对象的对象锁

  • synchronized可以用在方法声明处,也可以用在方法体中

  • 是可重入锁,不需要重写获取锁 ;排他锁/互斥锁(线程A获取对象锁之后如果未释放,线程B是不能获取到的,所以叫互斥锁)

  • 如果在方法声明处方法执行完就释放对象锁,如果是方法体,方法体执行完就释放锁;如果在执行过程中出现异常也会释放锁;当线程调用wait方法的时候 也会释放锁;调用sleep方法不会释放对象锁;

银行账户

public class BankCount {
    private double balance;
    public double getBalance() {
        return balance;
    }
    /**
     * 存钱
     * @param money
     */
    public synchronized void saveMoney(double money) {
        // 1,取出balance的值
        // 2,add
        // 3, 赋值之后刷新到主存  3条指令要在一个原子性的操作中,不能被打断
        balance = balance + money;
    }
    /**
     * 取钱
     * @param money
     */
    public void drawMoney(double money) {
        // () 表示要获取谁的对象锁,synchronized代码块的范围比synchronized方法小
        synchronized (this) {
            balance -= money;
        }
    }
}

测试类

public static void main(String[] args) {
        // 引用/对象/实例
        BankCount bankCount = new BankCount();
        Thread threadA = new Thread(() -> {
            int m = 0;
            for (int i = 0; i < 200000; i++) {
                bankCount.saveMoney(10);
                m++;
            }
            System.out.println("threadA end");
        });

        Thread threadB = new Thread(() -> {
            int m = 0;
            for (int i = 0; i < 200000; i++) {
                bankCount.drawMoney(10);
                m++;
            }
            System.out.println("threadB end");
        });
        threadA.start();
        threadB.start();
        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前余额:" + bankCount.getBalance());
    }

在静态方法中的使用 synchronized

 /**
     * synchronized在静态方法中使用需要的是类对象(class)的对象锁
     */
    public synchronized static void method() {
        System.out.println("=======");
    }

    public static void method2() {
        synchronized (BankCount.class) {
            
        }
    }

2.Lock的使用,lock后的代码写入try语句块中,释放锁写入finally中

public class BankCount2 {
    private double balance;
    private ReentrantLock lock = new ReentrantLock();
    /**
     * 存钱
     * @param money
     */
    public void saveMoney(double money) {
        // 上锁
        lock.lock();
        try {
            balance = balance + money;
        } catch (Exception e) {
        } finally {
            // 释放锁,一定要执行
            lock.unlock();
        }
    }
    /**
     * 取钱
     * @param money
     */
    public void drawMoney(double money) {
        // () 表示要获取谁的对象锁,synchronized代码块的范围比方法小
        lock.lock();
        try {
            balance -= money;
        } finally {
            lock.unlock();
        }
    }
    public double getBalance() {
        return balance;
    }
}

1.4.1.线程安全的类

1.集合ArrayList和Vector

public static void main(String[] args) throws InterruptedException {
        //ArrayList是线程不安全的,Vector是线程安全的
//        ArrayList<Integer> integers = new ArrayList<>();
        Vector<Integer> integers = new Vector<>();
        new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                integers.add(i);
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                integers.add(i);
            }
        }).start();
        
        TimeUnit.SECONDS.sleep(1L);
        System.out.println(integers.size());
    }

2.AtomicInteger类

public class DeadLock {
    private static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) {
        task2();
    }
    public static void task2() {
        Thread threadA = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                count.incrementAndGet();
            }
        });
        Thread threadB = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                count.decrementAndGet();
            }
        });
        threadA.start();
        threadB.start();
        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count的值是:" + count);
    }
 }

1.5.死锁

多个线程间出现互相等待对方释放锁的情况,就是死锁

解决:嵌套上锁的时候,上锁的顺序保持一致

 public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        new Thread(() -> {
            synchronized (o1) {
                System.out.println("线程A获取到1号锁了");
                try {
                    TimeUnit.SECONDS.sleep(1L);
                    synchronized (o2) {
                        System.out.println("线程A获取到2 号锁了");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (o2) {
                System.out.println("线程B获取到2号锁了");
                try {
                    TimeUnit.SECONDS.sleep(1L);
                    synchronized (o1) {
                        System.out.println("线程B获取到1 号锁了");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
public static void main(String[] args) {
        ReentrantLock lock1 = new ReentrantLock();
        ReentrantLock lock2 = new ReentrantLock();
        new Thread(() -> {
            // 和lock 的区别,不会无限等待,获取不到直接返回false
            lock1.tryLock();
            System.out.println("线程A获取到1号锁了");
            try {
                TimeUnit.SECONDS.sleep(1L);
                lock2.tryLock();
                System.out.println("线程A获取到2 号锁了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock2.unlock();
            lock1.unlock();
        }).start();

        new Thread(() -> {
            lock2.tryLock();
            System.out.println("线程B获取到2号锁了");
            try {
                TimeUnit.SECONDS.sleep(1L);
                if (lock1.tryLock()) {
                    System.out.println("线程B获取到1 号锁了");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock2.unlock();
            lock1.unlock();
        }).start();
    }

1.6.生产者消费者

1.wait()方法和notify()方法

  • 只能在同步方法或者同步代码块中使用,而且应该在循环中使用
  • this.notify()表示随机唤醒一个当前对象等待池中的线程,this.notifyAll()是唤醒当前对象等待池中的所有线程

仓库类

public class Store {
    // 记录商品数量
    private int count;
    private final int MAX_COUNT = 100;
    /**
     * 生成商品,
     */
    public synchronized void product() {
        while (count >= MAX_COUNT) {
            System.out.println("仓库已满,生产者需要等待");
            try {
                // wait()是让当前线程进入当前对象的等待池,而且会释放对象锁
                // 当线程被唤醒之后,应该继续判断条件,如果还是不满足需要继续等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count++;
        System.out.println("产出一件商品,当前数量是 " + count);
        // 只能在同步方法或者同步代码块中调用
        this.notifyAll();
    }
    /**
     * 消费商品
     */
    public synchronized void pop() {
        while (count <= 0) {
            System.out.println("没有商品了,消费者需要等待了");
            try {
                // wait()是让当前线程进入当前对象的等待池,而且会释放对象锁
                // 当线程被唤醒之后,应该继续判断条件,如果还是不满足需要继续等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        System.out.println("消费一件商品,当前数量是 " + count);
        // 唤醒生产者 this.notify()表示随机唤醒一个当前对象等待池中的线程,this.notifyAll()是唤醒当前对象等待池中的所有线程
        this.notifyAll();
    }
}

测试类

Store store = new Store();
        Thread product = new Thread(() -> {
            for (int i = 0; i < 2000; i++) {
                store.product();
            }
        });

        Thread pop = new Thread(() -> {
            for (int i = 0; i < 2000; i++) {
                store.pop();
            }
        });
        product.start();
        pop.start();

面试题

1,sleep和wait方法的区别

  • sleep 是线程的静态方法,wait是Object的方法
  • sleep可以在任意地方使用,wait只能在同步方法或者同步代码块中使用
  • sleep是不释放对象锁,wait方法会释放对象锁

1.7.多线程的三个核心概念

  1. 原子性

​ 一个操作要么全部执行,要么全部不执行;银行转账案例,都要符合原子性

​ synchronized可以满足原子性

2.可见性

​ 多个线程访问共享变量的时候,一个线程对共享变量的操作,其他线程可见

​ synchronized可以保证可见性

​ volatile可以保证可见性

3.顺序性

​ JVM底层有指令重排序,代码执行顺序不一定就是你认为的顺序;单线程情况下绝对没有问题;

​ 执行过程根据程序代码的顺序来进行;

​ volatile可以保证顺序性,防止JVM进行指令重排序

1.8. volatile关键字

  • volatile只能修饰成员变量

1.8.1. volatile保证可见性

public class Demo {
    //volatile可以保证可见性
    private volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (flag) {
                
            }
        }).start();

        TimeUnit.SECONDS.sleep(1L);
        flag = false;
        System.out.println("主线程结束了");
    }
}

1.8.2. volatile保证顺序性

设计模式

​ 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式

​ 一个类只能有一个实例,管理器类/调度器类

实现单例的三个关键点:

​ 1,构造方法私有化

​ 2,提供一个本类型的静态成员变量

​ 3,提供一个静态的共有方法,返回值类型是本类型

1.8.2.1. 饿汉式单例
  • 实例提前创建好,线程安全的
  • 如果后期没有用到此单例,内存空间浪费

使用场景:如果此单例占用内存比较小,可以使用饿汉式单例进行创建

public class HungrySingleton {
    
    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return instance;
    }
}

1.8.2.2. 懒汉式单例

1.初级版本

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

2.多线程情况下,代码继续改进,保证多线程情况下只能有一个实例

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {
    }
    
    public synchronized static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

3.性能较低,会出现线程间的等待;继续升级

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {
    }
    public static LazySingleton getInstance() {
        // 双重检测,double check
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

4.最终版

public class LazySingleton {
    // volatile可以防止指令重排序
    private volatile static LazySingleton instance;

    private LazySingleton() {
    }
    public static LazySingleton getInstance() {
        // 双重检测,double check
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    //JVM 指令重排序; 在单线程不会有数据不一致情况
                    // 1, new 实例  10S
                    // 2,给引用赋值 2S
                    // 3,return 引用 1S  13S
                    // 231 3S 因为指令重排序的问题,在实际使用过程中会出现对象状态不正确的现象
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}
1.8.2.3.单例的攻破

1.使用反射攻破

		Class<?> aClass = Class.forName("com.qy28.sm.design.LazySingleton");
        // 得到无参构造
        Constructor<?> constructor = aClass.getDeclaredConstructor();
        //把访问权限设置为可访问
        constructor.setAccessible(true);
        Object o1 = constructor.newInstance();
        Object o2 = constructor.newInstance();
        Object o3 = constructor.newInstance();
        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o3);
  1. 使用序列化攻破
 LazySingleton instance = LazySingleton.getInstance();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("obj.txt"));
             ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("obj.txt"))) {
            objectOutputStream.writeObject(instance);
            Object object = objectInputStream.readObject();
            System.out.println(object);
            System.out.println(instance);
            System.out.println(object == instance);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
1.8.2.4.使用枚举类实现单例

只有一个实例,使用反射或者序列化都不能有其他实例

public enum EnumSingleton {
    INSTANCE;
}

1.9.多线程常见面试题

1.sleep和wait方法的区别

  • sleep 是线程的静态方法,wait是Object的方法
  • sleep可以在任意地方使用,wait只能在同步方法或者同步代码块中使用
  • sleep是不释放对象锁,wait方法会释放对象锁

2.synchronized和volatile的区别

  • volatile 是变量修饰符;synchronized 是修饰 方法、代码段。
  • volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
  • volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
  • volatile可以保证顺序性

3.synchronized 和 Lock 有什么区别?

  • synchronized 可以给方法、代码块加锁;而 lock 只能给代码块加锁。
  • synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

4.什么是死锁以及如何防止

  • 两个线程互相持对方释放所需要的锁,而发生的阻塞现象,我们称为死锁。
  • 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
  • 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
  • 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
  • 尽量减少同步的代码块。

5.runnable 和 callable 有什么区别?

  • runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。

6.notify()和 notifyAll()有什么区别?

  • notifyAll()会唤醒所有的线程,notify()之后唤醒一个线程。notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。

7.atomic的原理

  • atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升
2020/5月/15好上传最新版 JavaGuide 目前已经 70k+ Star ,目前已经是所有 Java 类别项目中 Star 数量第二的开源项目了。Star虽然很多,但是价值远远比不上 Dubbo 这些开源项目,希望以后可以多出现一些这样的国产开源项目。国产开源项目!加油!奥利给! 随着越来越多的人参与完善这个项目,这个专注 “Java知识总结+面试指南 ” 项目的知识体系和内容的不断完善。JavaGuide 目前包括下面这两部分内容: Java 核心知识总结; 面试方向:面试题、面试经验、备战面试系列文章以及面试真实体验系列文章 内容的庞大让JavaGuide 显的有一点臃肿。所以,我决定将专门为 Java 面试所写的文章以及来自读者投稿的文章整理成 《JavaGuide面试突击版》 系列,同时也为了更加方便大家阅读查阅。起这个名字也犹豫了很久,大家如果有更好的名字的话也可以向我建议。暂时的定位是将其作为 PDF 电子书,并不会像 JavaGuide 提供在线阅读版本。我之前也免费分享过PDF 版本的《Java面试突击》,期间一共更新了 3 个版本,但是由于后面难以同步和订正所以就没有再更新。《JavaGuide面试突击版》 pdf 版由于我工作流程的转变可以有效避免这个问题。 另外,这段时间,向我提这个建议的读者也不是一个两个,我自己当然也有这个感觉。只是自己一直没有抽出时间去做罢了!毕竟这算是一个比较耗费时间的工程。加油!奥利给! 这件事情具体耗费时间的地方是内容的排版优化(为了方便导出PDF生成目录),导出 PDF 我是通过 Typora 来做的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值