从零开始学Java-Day17

Posted 无声specialweek

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始学Java-Day17相关的知识,希望对你有一定的参考价值。

多线程编程的两种实现方式

  1. extends Thread
    • 优点:
    • 缺点:后续变化小,局限性大
  2. implement Runnable
    • 优点:多实现,更加灵活且解耦
    • 缺点:写法相对复杂,一些资源需要借助Thread

多线程数据安全隐患

  1. 怎么产生?线程的随机性+访问延迟
  2. 以后如何判断程序有没有线程安全问题

在多线程程序中 + 有共享数据 + 多条语句操作共享数据

  • 单线程程序不会出现多线程抢占资源的情况
  • 如果没有共享数据,互不干涉,也不会出现数据安全问题
  • 多条语句操作了共享数据,而多条语句执行是需要时间的,存在延迟所以这个时间差导致了数据的安全问题
package cn.tedu.tickets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestRunnableV2 {
    public static void main(String[] args) {
        TicketRunnable target = new TicketRunnable();
//        Thread t1 = new Thread(target, "黄金船");
//        Thread t2 = new Thread(target, "目白麦昆");
//        Thread t3 = new Thread(target, "东海帝王");
//        Thread t4 = new Thread(target, "小栗帽");
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        //线程池ExecutorService:用于存储线程的池子,把新建/启动/关闭线程都交给池来做
        //Executors:用于创建线程池的工具类,newFixedThreadPool()
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 1; i <= 5; i++){
            pool.execute(target);
        }
        pool.shutdown();
    }
}


class TicketRunnable implements Runnable{
    static int tickets = 100;
    Object object = new Object();
    @Override
    public void run() {
        while (true){
            /*锁对象必须唯一*/
            /*
            如果一个方法中所有代码均需要被同步,那么可以用 synchronized 修饰
            被synchronized关键字修饰的方法称为同步方法
             */
            synchronized (object){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName() + "卖了第" + tickets-- + "张票" );
                }
                if (tickets <= 0){
                    break;
                }
            }
        }
    }
}

多线程安全问题解决

加锁:注意

  1. 加锁的位置

同步|亦步

同步:类似于排队效果

  • 优点:线程安全
  • 缺点:效率低

异步:不排队,多线程效果,各线程都抢占资源

  • 优点:效率高
  • 缺点:线程不安全

双重校验机制

  1. 增加判断控制--有票的时候在卖票

  2. 同步代码块,主要强调同步,同一时刻同一资源只能被一个线程对象

  3. sychronized(锁对象){可能会发生安全问题的代码}

    • 同步代码块使用的锁对象必须唯一
package cn.tedu.tickets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestRunnableV2 {
    public static void main(String[] args) {
        TicketRunnable target = new TicketRunnable();
//        Thread t1 = new Thread(target, "黄金船");
//        Thread t2 = new Thread(target, "目白麦昆");
//        Thread t3 = new Thread(target, "东海帝王");
//        Thread t4 = new Thread(target, "小栗帽");
//        t1.start();
//        t2.start();
//        t3.start();
//        t4.start();
        //线程池ExecutorService:用于存储线程的池子,把新建/启动/关闭线程都交给池来做
        //Executors:用于创建线程池的工具类,newFixedThreadPool()
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 1; i <= 5; i++){
            pool.execute(target);
        }
        pool.shutdown();
    }
}


class TicketRunnable implements Runnable{
    static int tickets = 100;
    Object object = new Object();
    @Override
    public void run() {
        while (true){
            /*锁对象必须唯一*/
            /*
            如果一个方法中所有代码均需要被同步,那么可以用 synchronized 修饰
            被synchronized关键字修饰的方法称为同步方法
             */
            synchronized (object){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName() + "卖了第" + tickets-- + "张票" );
                }
                if (tickets <= 0){
                    break;
                }
            }
        }
    }
}
package cn.tedu.tickets;
//本类用于解决继承下多线程售票案例的数据安全问题
public class TestExtendV2 {
    public static void main(String[] args) {
        TicketThreadV2 t1 = new TicketThreadV2("小林");
        TicketThreadV2 t2 = new TicketThreadV2("托尔");
        TicketThreadV2 t3 = new TicketThreadV2("法夫涅尔");
        TicketThreadV2 t4 = new TicketThreadV2("康纳");
//        t1.setName("小林");
//        t2.setName("托尔");
//        t3.setName("法夫涅尔");
//        t4.setName("康纳");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class TicketThreadV2 extends Thread{
    private static int tickets = 100;

    public TicketThreadV2() {
    }

    public TicketThreadV2(String name) {
        setName(name);
    }

    @Override
    public void run() {
        while (true){
            /*
             * 双重校验二:使用同步代码块
             * synchronized(锁对象){容易发生数据安全问题的代码}
             * 在同步代码块中,同一时刻,同一资源只能被一个线程独享,排队
             * 注意:锁对象必须唯一,如果不唯一,还会发生安全问题
             * 如果是继承的方式,锁对象一般使用本类的字节码对象
             */
            synchronized (TicketThreadV2.class){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets <= 0){
                    break;
                }
                if (tickets == 1){
                    System.out.println("票卖完了");
                }
                //双重校验一
                if (tickets > 0){
                    System.out.println(getName() + "买到第" + tickets-- + "张票");
                }

            }
        }

    }
}

ExecutorService/Executors

ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理

  • execute(Runnable任务对象) 把任务丢到线程池

Executors 辅助创建线程池的工具类

  • newFixedThreadPool(int nThreads) 最多n个线程的线程池
  • newCachedThreadPool() 足够多的线程,使任务不必等待
  • newSingleThreadExecutor() 只有一个线程的线程池

线程池

ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理

创建线程池的工具类:Executors.newFixedThreadPool(int 自定义线程数)

启动线程池中的线程:pool.execute(target目标业务对象)

线程池会自动管理线程,线程池目前单机测试不关闭,需要手动关闭

线程锁

悲观锁(sychronized):也叫互斥锁

乐观锁

设计模式

单例模式可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。
其中单例模式最重要的是确保对象只有一个。
简单来说,保证一个类在内存中的对象就一个。

单例设计模式:

  1. 创建一个私有化静态方法
  2. 私有化的构造方法不让外部调用
  3. 通过自定义的静态方法获取实例

实现思路:

  1. 构造方法私有化--为了防止外部直接调用本类构造方法
  2. 本类调用构造方法创建私有对象--对象私有化是为了不让外界直接获取
  3. 提供公共的全局访问点--为了让外界按指定的方式获取对象
package cn.likou.demo06;

public class Test02 {
    private Test02(){}
    static private Test02 t = new Test02();
    public static Test02 go(){
        return t;
    }

    public static void main(String[] args) {
        Test02 t1 = Test02.go();
        Test02 t2 = Test02.go();
        System.out.println(t1);
        System.out.println(t2);
        System.out.println(t1 == t2);

        MySingle s1 = MySingle.go();
        MySingle s2 = MySingle.go();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

class MySingle{
    private MySingle(){}
    static private MySingle mySingle;

    public static MySingle go(){
        if (mySingle == null){
            mySingle = new MySingle();
        }
        return mySingle;
    }
}

以上是关于从零开始学Java-Day17的主要内容,如果未能解决你的问题,请参考以下文章

从零开始学Java-Day16

从零开始学Java-Day14

自学it18大数据笔记-第一阶段Java-day16-day17-day18-day19--day20-day21-day22——会持续更新

《从零开始学Swift》学习笔记(Day 17)——Swift中数组集合

从零开始学Go之容器:切片

从零开始配置vim(27)——代码片段