10-线程

线程

术语解释
//  并发与并行
    
**并发**:指两个或多个事件在**同一个时间段内**发生。   例: 你吃了一口饭 (间隔的时间--->线程的上下文的切换时间) 你去打电话了  (交替执行) 
                                                并发:(在以后项目中)----> (有多个请求)--->服务器(负载均衡 服务器集群             
**并行**:指两个或多个事件在**同一时刻**发生(同时发生)             例:(嘴巴): 你一边吃饭  一边打电话

   
// 同步 vs 异步
    
同步请求:
  服务器完全响应,才能去做另外一件事情。一致等待wait
                                                                                
异步请求:(第二阶段---> 页面异步刷新  ajax)
  不等服务器的完全响应,可以做很多的其它的事情。---> 异步任务(队列)


// 阻塞  vs 非阻塞

阻塞: IO (堵车)---> 传统IO  BIO(blocking IO)---->  同步阻塞的io
  Scanner  一致等待  知道获得合适的数据
非阻塞:(有数据)---> NIO    


同步阻塞,相当于一个线程在等待。
同步非阻塞,相当于一个线程在正常运行。
异步阻塞,相当于多个线程都在等待。
异步非阻塞,相当于多个线程都在正常运行。



// 进程  vs  线程


进程: (相对于系统)--->应用程序  cpu
   Android: 卡  多进程的。
   IOS: 不卡  单进程

线程:(在进程的基础)
  一个进程里面有多个线程
  WECHAT: 数据请求  网络连接  
  B站: 看视频  发弹幕  评论  声音
 线程也看cpu:  单核: 就是单线程        多核: 多线程
线程 Thread —>

线程与线程之间是相互独立的。会共享进程里面的数据。

jvm可以支持多线程。 java程序运行,至少有2个线程存在。 main线程 GC线程(守护线程)

* Thread
public class Thread extends Object implements Runnable
    Java虚拟机允许应用程序同时执行多个执行线程
    每个线程都有优先级。 具有较高优先级的线程优先于优先级较低的线程执行。
static class  Thread.State  线程的状态(静态内部类)
static int MAX_PRIORITY  线程最大的级别10
static int MIN_PRIORITY  线程最小的级别1
static int NORM_PRIORITY  线程默认级别5    
Thread() 
Thread(Runnable target) 通过Runnable实现类构建线程
Thread(Runnable target, String name) 
Thread(String name) 
static Thread currentThread()  获得当前正在运行的线程
long getId()  获得唯一的标识  1
String getName()   获得线程的名称
int getPriority()  获得线程的优先级别
void setName(String name) 修改线程名称
void setPriority(int newPriority)  修改线程级别
Thread.State getState()  获得线程的状态
    
void interrupt()  中断线程
boolean isAlive()  判断线程是否是活着的
boolean isDaemon() 判断线程是否是守护线程

    
void join()  等待当前线程死亡(当前线程执行完毕之后  其它线程才有机会抢占cpu)
void join(long millis)  
    
void run()  线程的运行逻辑
void setDaemon(boolean on)  一定要在启动线程之前标记

static void sleep(long millis)  让当前线程休眠指定的时间  线程调度
static void sleep(long millis, int nanos) 
    
void start()  启动线程(执行run)

static void yield()  当前线程放弃抢占cpu 立马处于就绪的状态   
void wait()  当前线程一直等待(睡过去了)  --->有同一个监视器()
void notify()/void notifyAll()   唤醒睡过去的线程

void wait(long timeout)  当前线程再指定时间内等待  超时了会自动醒过来
void wait(long timeout, int nanos)      
* 多线程

优势: 提高了cpu的利用率问题。

弊端: 由于线程与线程之间是相互独立,

​ 很可能会出现线程安全问题。(死锁)—>程序不停,程序不会继续执行

SimpleDateFormat.pare()/format()—>数据

继承Thread

Java中通过继承Thread类来创建启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程
模拟: 
   同时下载多张网图。---> 网上资源---> copy--->读写--->字节流
/**
     * 下载功能
     * https://www.javasm.cn/static/upload/image/20200525/1590377569484285.png
     * https://www.javasm.cn/static/upload/image/20200219/1582127034814192.jpg
     * https://www.javasm.cn/static/upload/image/20180702/1530526129232132.png
     *
     * @param target 父级路径  day18/demo/images/
     * @param url    网上图片路径
     */
       
       
   public class DownloadFileThread extends Thread {

    private String url;
    private String targetDirectory;

    //线程逻辑在run方法: 重写父类的run方法
    @Override
    public void run() {
        //调用工具类下载图片的资源
        System.out.println(DownloadFileUtil.download(url, targetDirectory) + "下载成功");
    }

    public DownloadFileThread(String url, String targetDirectory, String threadName) {
        super(threadName);
        this.url = url;
        this.targetDirectory = targetDirectory;
    }
}          
    public static String download(String url, String target) {
        //IO读写数据(读写小说)
        Objects.requireNonNull(url);
        //url  最后一个/内容
        String fileName = url.substring(url.lastIndexOf("/") + 1);
        try (
                //高效字节流
                BufferedInputStream inputStream = new BufferedInputStream(new URL(url).openStream());
                BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(new File(target, fileName)))
        ) {

            //循环读写
            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return fileName;
    } 
public static void main(String[] args) {
        //1.创建3个线程对象
//     * https://www.javasm.cn/static/upload/image/20200525/1590377569484285.png
//     * https://www.javasm.cn/static/upload/image/20200219/1582127034814192.jpg
//     * https://www.javasm.cn/static/upload/image/20180702/1530526129232132.png

        String url = "https://www.javasm.cn/static/upload/image/20200525/1590377569484285.png";
        String directory = "day18/demo/images/";
        DownloadFileThread thread1 = new DownloadFileThread(url, directory, "线程1");

        url = "https://www.javasm.cn/static/upload/image/20200219/1582127034814192.jpg";
        DownloadFileThread thread2 = new DownloadFileThread(url, directory, "线程2");

        url = "https://www.javasm.cn/static/upload/image/20180702/1530526129232132.png";
        DownloadFileThread thread3 = new DownloadFileThread(url, directory, "线程3");

        //2. 启动线程 start()
        thread1.start();
        thread2.start();
        thread3.start();
    }
实现Runnable
@FunctionalInterface
public interface Runnable
Runnable接口应由任何类实现,其实例将由线程执行  
    
步骤如下:
1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正
的线程对象。
3. 调用线程对象的start()方法来启动线程。
案例: 
  火车站卖票: 很多窗口都可以卖固定票数   50  3个线程(窗口)
public class SaleTicketRunnable implements Runnable {
    private  int ticket = 50;//3个线程共享50张票

    @Override
    public void run() {//运行状态
        //开启多个线程  执行的一个线程逻辑
        //50张票
        //获得当前正在运行的线程: 获得线程的名称
        for (int i = 1; i <= 50; i++) {//i 控制有可能会出现一个卖50张票的特殊情况的
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
                ticket--;
                //线程调度
                try {
                    Thread.sleep(400);//休眠 继续执行下面的一些逻辑
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public static void main(String[] args) {
        //创建3个窗口(线程)
        SaleTicketRunnable saleTicketRunnable = new SaleTicketRunnable();
        //Thread
        Thread window1 = new Thread(saleTicketRunnable, "窗口1");//新建状态
//Thread window1 = new Thread(new saleTicketRunnable, "窗口1");   如果这么写 没有共用一个对象  
//那么类里面  private static int ticket = 50;  票数要定义为静态的   保证共用这个50张票
        Thread window2 = new Thread(saleTicketRunnable, "窗口2");
        Thread window3 = new Thread(saleTicketRunnable, "窗口3");

        window1.start();//就绪状态
        window2.start();//就绪状态
        window3.start();//就绪状态
    }
实现Runnable接口比继承Thread类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
* 实现Callable

Callable+Future(FutureTask)

public class FutureTask<V> extends Object implements RunnableFuture<V>
FutureTask(Callable<V> callable)     
案例: 
  火车站卖票: 很多窗口都可以卖固定票数   50  3个线程(窗口)
public class SaleTicketCallable implements Callable<String> {
    private int ticket = 50;//3个线程共享50张票
    @Override
    public String call() throws Exception {      //call===run
        for (int i = 1; i <= 50; i++) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
                ticket--;
                //线程调度
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return "ok";
    }
}
public static void main(String[] args) {
        SaleTicketCallable saleTicketCallable = new SaleTicketCallable();

        //3个窗口(线程)干3件事情(任务)     几个线程就创建几个,方便拿数据
        FutureTask<String> futureTask1 = new FutureTask<>(saleTicketCallable);
        FutureTask<String> futureTask2 = new FutureTask<>(saleTicketCallable);
        FutureTask<String> futureTask3 = new FutureTask<>(saleTicketCallable);

        //要runnable的实例
        Thread window1 = new Thread(futureTask1, "窗口1");
        Thread window2 = new Thread(futureTask2, "窗口2");
        Thread window3 = new Thread(futureTask3, "窗口3");

        window1.start();//就绪状态
        window2.start();//就绪状态
        window3.start();//就绪状态
}
线程池
使用线程池中线程对象的步骤:
1. 创建线程池对象。
2. 创建Runnable接口子类对象。(task)
3. 提交Runnable接口子类对象。(take task)
4. 关闭线程池(一般不做)public static void main(String[] args) {
        SaleTicketCallable saleTicketCallable = new SaleTicketCallable();

        //3个窗口(线程)干3件事情(任务)
        FutureTask<String> futureTask1 = new FutureTask<>(saleTicketCallable);
        FutureTask<String> futureTask2 = new FutureTask<>(saleTicketCallable);
        FutureTask<String> futureTask3 = new FutureTask<>(saleTicketCallable);
        //利用线程池来完成--->创建3个线程
        //ThreadPoolExecutor
     	// 创建线程池对象
        ExecutorService executorService = Executors.newFixedThreadPool(3);//pool-1-thread-3
        executorService.execute(futureTask1);
        executorService.execute(futureTask2);
        executorService.execute(futureTask3);
    }
* 线程安全

多线程的环境下,且有共同使用的资源(数据—>同一个变量)

安全与效率 二者都是不可兼得的。 局部变量

线程安全的类: StringBuffer Vector HashTable java.time.*

监视器对象(锁(互斥锁 悲观锁 同步锁)对象)—> 任意一个对象都可以充当监视器对象 Object

保证每个线程都能正常执行原子操作,Java引入了线程同步机制 :

  • 自动加锁synchronized
    • 同步代码块
    • 同步方法
  • 手动加锁 Lock
自动加锁

synchronized

多个线程使用同一个锁对象。

  • 同步代码块
在方法体里面使用同步代码块解决安全问题。----> 变量数据改变的程序中
    
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
        synchronized(同步锁){
            需要同步操作的代码
        }


同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1. 锁对象 可以是任意类型。
2. 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
(BLOCKED)。    
@Override
    public void run() {//运行状态
        //开启多个线程  执行的一个线程逻辑
        //50张票
        //获得当前正在运行的线程: 获得线程的名称
        for (int i = 1; i <= 50; i++) {//i 控制有可能会出现一个卖50张票的特殊情况的
            synchronized ("锁") {//锁对象  加锁
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
                    ticket--;
                    //线程调度
                    try {
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }//释放锁
        }
同步方法
在方法的定义上面 使用synchronized进行修饰
 public synchronized void  abc(){}

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

    格式:
    public synchronized void method(){
        可能会产生线程安全问题的代码
    }
    
    
    
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)
 @Override
    public void run() {//运行状态
        //开启多个线程  执行的一个线程逻辑
        //50张票
        //获得当前正在运行的线程: 获得线程的名称
        for (int i = 1; i <= 50; i++) {//i 控制有可能会出现一个卖50张票的特殊情况的
            saleTicket();
        }

    }

private synchronized void saleTicket() {//上锁  
        // 锁对象是什么? 当前类对象(this/saleTicketRunnable)
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
            ticket--;
            //线程调度
            try {
                TimeUnit.MILLISECONDS.sleep(500);// 不会
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }//释放锁
手动加锁 Lock
public class ReentrantLock extends Object implements Lock, Serializable
    
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。    
public class SaleTicketCallable implements Callable<String> {
    private int ticket = 50;//3个线程共享50张票
    private static final Lock LOCK = new ReentrantLock();//锁对象

    @Override
    public String call() throws Exception {//call===run
        for (int i = 1; i <= 50; i++) {
            LOCK.lock();//上锁

            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票");
                ticket--;
                //线程调度
                TimeUnit.MILLISECONDS.sleep(500);
            }
            LOCK.unlock();//手动释放锁
        }
        return "ok";
    }
}
死锁(了解)
前提:  (程序不停  但是也不执行  阻塞状态)
  1. 多线程的环境
  2. 有多把锁(至少2)(交叉锁+嵌套锁)
  3. 有共享的资源(至少2)
  父亲: 有零花钱   想要儿子的成绩单
  儿子:有成绩单    想要父亲的零花钱   
public class Father extends  Thread {
    @Override
    public void run() {
        synchronized (Locks.FATHER_LOCK) {
            System.out.println("父亲有零花钱");
            synchronized (Locks.SON_LOCK) {
                System.out.println("父亲想要儿子的成绩单");
            }
        }

    }
}
public class Son extends Thread {

    @Override
    public void run() {
        synchronized (Locks.SON_LOCK) {
            System.out.println("儿子有成绩单");
            synchronized (Locks.FATHER_LOCK) {
                System.out.println("儿子想要父亲的零花钱");
            }
        }

    }
}
public class Locks {
    public static final String FATHER_LOCK = "father";
    public static final String SON_LOCK = "son";
}
 public static void main(String[] args) {
        new Father().start();
        new Son().start();
    }
线程通信

与锁对象有关,synchronized

Object.wait() Object.notify()

体现在: 生产者 与 消费者模式 (现象/问题)

生产者: 一直生产(提交)数据—> 池子(缓冲区)

消费者:一直消费数据-----> 从池子里面获得数据

** 消息队列: RocketMQ **

线程间通信
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
    
为什么要处理线程间通信:
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源:
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— **等待唤醒机制。**    
    

什么是等待唤醒机制

这是多个线程间的一种协作机制。就是在一个线程进行了规定操作后,就进入等待状态 wait(), 等待其他线程执行完他们的指定代码过后 再将其唤醒 (notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

wait/notify 就是线程间的一种协作机制。


等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

>注意:

>哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

调用wait和notify方法需要注意的细节
1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。    
双十一:
    很多用户(生产者)提交订单----> 订单池(排队+队列)  (有一定的量)  不能一直生产  集合
    支付宝处理了订单(用户使用支付宝支付)---->订单池  不能一直消费
public class OrderPool {

    private static List<String> orderPool = Collections.synchronizedList(new ArrayList<>(10));//订单池

    //最多存储30个

    /**
     * 生产者
     */
    public synchronized static void produceOrder() {// 锁对象? 类锁
        try {
            if (orderPool.size() == 30) {
                //池子满了 就不能生产  当前线程等待
                OrderPool.class.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            if (orderPool.size() < 30) {
                //生产一个订单
                orderPool.add("order");// 通知消费者进行消费
                System.out.println(Thread.currentThread().getName()+"提交了一个订单,目前池子里面:" + orderPool.size());
                TimeUnit.MILLISECONDS.sleep(300);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        OrderPool.class.notify();//在这个线程里面唤醒另外一个线程
    }

    /**
     * 消费者
     */
    public synchronized static void consumeOrder() {
        //1.判断orderPool是否有订单 有的话才能消费  没有的话等待
        try {
            if (orderPool.size() <= 0) {
                //当前线程等待
                OrderPool.class.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.有订单--->移除第一个订单---> 池子里面就有多余的空间---->生产者就可以生产
        orderPool.remove(0);
        System.out.println("支付宝处理了一个订单,目前池子里面还有:" + orderPool.size());
        try {
            TimeUnit.MILLISECONDS.sleep(400);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        OrderPool.class.notifyAll();

    }
}
public class UserProduceOrderThread extends Thread {

    @Override
    public void run() {

        //一直生产订单
        while (true) {
            OrderPool.produceOrder();
        }

    }

    public UserProduceOrderThread(String name){
        super(name);
    }
}
public class AiPayOrderThread extends Thread {

    @Override
    public void run() {

        //一直消费订单
        while (true) {
            OrderPool.consumeOrder();
        }

    }
}
public class Test {

    public static void main(String[] args) {
        UserProduceOrderThread produceOrderThread = new UserProduceOrderThread("user1");
        produceOrderThread.start();
        UserProduceOrderThread produceOrderThread1 = new UserProduceOrderThread("user2");
        produceOrderThread1.start();

        AiPayOrderThread payOrderThread = new AiPayOrderThread();
        payOrderThread.start();

    }
}
线程状态(生命周期)

在这里插入图片描述

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法
Runnable(可 运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操 作系统处理器
Blocked(锁阻 塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程持有锁时,该线程将变成Runnable状态
Waiting(无限 等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个 状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时 等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态 将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait
Teminated(被 终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
停止线程
public class StopThread extends Thread {                      

    private int count = 0;
    private boolean flag = true;

    @Override
    public void run() {
        while (flag) {                       //当flag为true的时候运行
            if (count == 10) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + "-----count:" + (++count));
            try {
                TimeUnit.MILLISECONDS.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void stopThread() {                    //创建停止线程的方法   将flag赋值false
        this.flag = false;
    }

    public StopThread(String name) {
        super(name);
    }
}




public class Test {

    public static void main(String[] args) {
        //启动线程
        StopThread stopThread = new StopThread("stop");
        stopThread.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("main......"+i);
            //当i==10 结束线程
            if(i==10){
                //stopThread 结束
                stopThread.stopThread();                     //想停止的时候直接调用stopThread()方法  将flag赋值false
            }
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test1 {

    public static void main(String[] args) {
        StopThread a = new StopThread("a");
        StopThread b = new StopThread("b");
        StopThread c = new StopThread("c");

        a.start();
        try {
            a.join();                           //a后面有join()方法后 a运行完毕  后面才能开始
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        b.start();
        try {
            b.join();//等待当前线程死亡
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        c.start();
    }
}
单例模式

23种设计模式之一。

在一个进程里面 有且只有一个对象。

  1. 类的构造私有 2. 提供静态方法 / 属性
1. 饿汉模式
 public class Singleton {

    //1.构造私有(只能本类访问)
    private Singleton() {
    }
    //2.提供静态的方法
    private static Singleton singleton = new Singleton();
    // jvm加载 class 就会创建  Singleton对象  ---> 饿汉  不会出现线程安全的问题  (没有体现lazy loading的特性)

    public static Singleton getInstance() {
        return singleton;
    }

}   
    
new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+Singleton.getInstance()+"=========="+i);
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();


        new Thread(()->{
            for (int i = 11; i < 20; i++) {
                System.out.println(Thread.currentThread().getName()+Singleton.getInstance()+"=========="+i);
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
2.懒汉模式
public class Singleton1 {
    //1.构造私有(只能本类访问)
    private Singleton1() {
    }
    //2.提供静态的方法
    private static Singleton1 singleton;// 声明一个对象 null

    // 体现出来lazy loading的特性  懒加载  但是有线程安全的问题
    //synchronized 方法  效率很低
    public synchronized static Singleton1 getInstance() {
        if (singleton == null) {
            singleton = new Singleton1();
        }
        return singleton;
    }

}  
Thread-1com.javasm.danli.Singleton1@710a52b==========11
Thread-0com.javasm.danli.Singleton1@377839d0==========0
。。。。。

public class Singleton1 {
    //1.构造私有(只能本类访问)
    private Singleton1() {
    }
    //2.提供静态的方法
    private volatile static Singleton1 singleton;// 声明一个对象 null
    // 体现出来lazy loading的特性  懒加载  但是有线程安全的问题
    //synchronized 方法  效率很低
    //推荐使用double-check方法实现安全
    //JMM JAVA memory model  无序性  jvm/cpu(指令重排)   1. 分配空间  2. 赋值  3. 初始化
    // volatile: 有序性  原子性  限制指令重排   (线程可见性)---> 写之前先读
    //缓存一致性。
    public static Singleton1 getInstance() {
        if (singleton == null) {
            synchronized ("abc") {
                if (singleton == null) {
                    singleton = new Singleton1();
                }
            }
        }
        return singleton;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值