第二阶段(day08)Thread

1基本概念

进程

通常指在计算机中运行的应用程序(进程是资源(CPU、内存等)分配的基本单位

(通过任务管理器可看到详细信息,每个进程有他的id,运行状态,不同进程会分配单独的内存空间,数据改变和运算会消耗cpu资源。故,应用程序的使用要占内存和CPU)

每个应用程序 有自己独立的内存空间

(程序运行时系统就会创建一个 进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。)

线程

每个应用程序 调用cpu时的最小调度单位(读写通道)

(几核几线程,CPU有几个核就有几个处理单元;有几个线程就有几个通道通向CPU来跟内存进行数据交互。)

(应用程序运行时,在内存中开辟的空间,与CPU进行数据交互时的通道即线程。)

(进程的一个执行流,是CPU调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。)

在多核多线程cpu的计算机中 允许程序 开启多线程处理任务 可以节省时间

进程与线程区别:

1.进程是资源分配的最小单位,线程是程序执行的最小单位。

2.进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

3.线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据。进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

4.但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了, 而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

5.一个进程有多个线程。

并发

多个事物同时发生

串行

顺序处理

并行

同时处理

同步

按照顺序处理

(多个事物不能同时进行,必须一个一个的来,上一个事物结束后,下一个事物才开始.(程序里一般叫做同步方式处理)

(串行处理方式 )

异步

同时处理

(多个事物同时进行,不需要等待另一个事件的执行.(程序里一般叫做异步方式处理))

阻塞

不通状态

(描述执行过程的状态,阻塞意味着停下来等待 wait)

阻塞伴随同步出现

非阻塞

通行状态

组合使用:

1.所谓同步/异步,关注的是能不能同时开工。

2.所谓阻塞/非阻塞,关注的是能不能动。

同步阻塞,不能同时开工,也不能动。只有一条小道,一次只能过一辆车,可悲的是还都堵上了。

同步非阻塞,不能同时开工,但可以动。只有一条小道,一次只能过一辆车,幸运的是可以正常通行。

异步阻塞,可以同时开工,但不可以动。有多条路,每条路都可以跑车,可气的是全都都的堵上了。

异步非阻塞,可以同时开工,也可以动。有多条路,每条路都可以跑车,很爽的是全都可以正常通行。

3.回到程序里,把它们和线程关联起来:

同步阻塞,相当于一个线程在等待。

同步非阻塞,相当于一个线程在正常运行。

异步阻塞,相当于多个线程都在等待。

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

2线程的创建方式 常用方法

Thread类(线程核心的类)

比较常用的构造方法:

1.Thread(Runnable target) 传入Runnable接口

2.Thread(String name) 传入字符串(线程名)

3.Thread()

4.Thread(Runnable target,String name)

(1,4是一套;2,3,是一套)

创建线程有几种不同的方式:

1.让类继承Thread类,里面会重写一个run()方法。若想开多线程处理,处理的代码写在run()方法里。

2.方式二:实现Runnable接口,该接口只定义了一个run()抽象方法,故重写run()方法

3.方式三:实现Callable接口,且重写call()方法,该方法需要返回值

4.方式四:使用线程池(jdk提供的线程池)

几个重要的方法:

1.start() 启动线程 (之后java虚拟机会自动调用run()方法执行,别自己调,自己调就是普通方法,就走一次)

2.join() 让指定的线程优先执行结束

3.interrupted() 主动中断某线程

4.sleep() 指定线程休眠一段时间

5.yield() 把当前线程转回就绪状态 与其他线程重新抢资源(cpu)

6.run(){ 需要重写 并且把线程需要执行的代码写入run方法 }

7.setPriority()设置优先级 1-10

8.wait() 挂起(等待)线程

9.notify() 唤醒线程

10.notifyAll() 唤醒所有挂起(等待)的线程

单线程:

public static void main(String[] args) {
        //main() 程序的入口。程序执行都有线程执行,main()是程序的主线程,主线程是一个线程。故平时不开启多线程,程序是单线程。
        // 其实垃圾回收gc线程也在跟着主线程执行。(创建字符串对象,在内存中占空间,程序运行结束,gc将空间清空。传统的编程语言
        // 比如c语言,需要自己找到内存地址,然后自己去清空)
        //gc在后台运行,一般叫守护线程(没法主动操作,由java虚拟机自动调配。伴随着主线程的结束而结束 )
​
        System.out.println("abc");
​
        //查看当前运行的线程的名字
        System.out.println(Thread.currentThread().getName());    //main
​
        //Thread[线程名称,优先级]
        //优先级高的CPU优先执行,可在任务管理器中对进程点右键,设置优先级。java中有1-10个优先级(由低到高),主线程默认5
        System.out.println(Thread.currentThread());   //Thread[main,5,main]
​
        //设置线程优先级(在多线程运行时才会看出效果)
        Thread.currentThread().setPriority(10);
​
        try {
            //线程休眠,参数是毫秒
            Thread.sleep(200);
        } catch (InterruptedException e) {   //线程中断异常
            e.printStackTrace();
        }
​
        System.out.println("over");
​
    }

在主线程里将卖票这个事扔给子线程处理:

创建子线程:

public class MyThread extends Thread{
    //创建一个新的类,要让这个类和线程有关:
    //方式一:继承Thread类,且必须重写Thread类里的run()方法,在run方法里写要处理的事情。
​
    private static Integer ticket = 10;  //默认10张票
​
    @Override
    public void run() {
        //让子线程执行的代码
        while (ticket>0){
            ticket--;
            System.out.println("卖了一张票,还剩"+ticket);
            try {
                Thread.sleep(200);    //每卖一张票,休息一会
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }
}

主线程与子线程建立关系:

public class Demo1 {
    public static void main(String[] args) {
        //让主线程和子线程建立关系,创建子线程
        Thread newThread = new MyThread();
​
        //启动子线程
        newThread.start();
​
        System.out.println("执行完了");  //执行完该代码,才执行子线程
​
        //CPU处理数据,若多线程,由CPU自己调度,从线程里拿数据。多个线程同时过来,按优先级选。若优先级相同,一般会执行代码较少的一方。
        //多线程同时执行,抢CPU资源,CPU理论上按优先级处理(会偏向于优先级高的执行)。
    }
}

如何让子线程先执行完,再执行主线程:

public class Demo1 {
    public static void main(String[] args) {
​
        Thread newThread = new MyThread();
​
        newThread.start();
​
        try {
            //子线程执行结束,再执行主线程
            //newThread.join();   指定newThread优先执行,必须该线程执行结束,其它线程才能执行
            newThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("执行完了");  //控制台最后打印执行完了
​
    }
}

//休眠语句的其它写法: java.util.concurrent.TimeUnit
        //TimeUnit可先选时间单位
        try {
            TimeUnit.MILLISECONDS.sleep(300);   //等同于Thread.sleep(300); 推荐TimeUnit的写法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

创建子线程的第二种方式:

public class MyThread implements Runnable{
    //创建一个新的类,要让这个类和线程有关:
    //方式二:实现Runnable接口,该接口只定义了一个run()抽象方法,故重写run()方法
​
    private static Integer ticket = 10;  //默认10张票
​
    @Override
    public void run() {
        //让子线程执行的代码
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
            try {
                Thread.sleep(200);    //每卖一张票,休息一会
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }
}

对应的主线程:

public class Demo2 {
    public static void main(String[] args) {
​
        //使用Thread(Runnnable target)  传入其实现类
        //Thread newThread = new Thread(new MyThread());
        //也可同时给线程起名
        Thread newThread = new Thread(new MyThread(),"窗口1");
        newThread.start();
        try {
            newThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("执行完了");
    }
}

当然第一套也可加名称:

public class Demo1 {
    public static void main(String[] args) {
​
        //Thread newThread = new MyThread();
        Thread newThread = new MyThread("窗口1");    //注意子线程里要有对应的有参构造
​
        newThread.start();
​
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("执行完了");
​
    }
}
public class MyThread extends Thread{
​
    private static Integer ticket = 10;
​
    @Override
    public void run() {
        //让子线程执行的代码
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    public MyThread(String name) {
        super(name);
    }
}

对于Thread newThread = new Thread(new MyThread(),"窗口1");也可用匿名内部类,对应的子线程也不用实现Runnnable类,不用重写run()方法,写普通方法即可:

public class MyThread {
​
    private static Integer ticket = 10;
​
    public static void sale() {
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    }
}
public class Demo2 {
    public static void main(String[] args) {
​
        Thread newThread = new Thread(new Runnable(){
            @Override
            public void run(){
                MyThread.sale();
            }
​
        },"窗口1");
        newThread.start();
        try {
            newThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("执行完了");
    }
}

此写法也有简化写法:(可以简化成λ表达式)

Thread newThread = new Thread(()->{
                MyThread.sale();
        },"窗口1");

第三种创建线程的方式:

public class MyThread implements Callable {
​
    //方式三:实现Callable接口,且重写call()方法,该方法需要返回值
    private static Integer ticket = 10;
​
    @Override
    public Object call() throws Exception {
        //让子线程执行的代码
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return ticket;
    }
​
}
public class Demo3 {
    public static void main(String[] args) {
​
        //创建FutureTask要传入Callable<V>事件或Runnable事件。
        //FutureTask(Callable<V>)    FutureTask(Runnable,V)
​
        //用FutureTask(Callable<V>)创建:
        MyThread myThread = new MyThread();
        FutureTask task = new FutureTask(myThread);   //FutureTask异步任务
​
        Thread newThread = new Thread(task);      //创建出的异步任务传给线程对象
        newThread.start();
​
        //task.get(); 可拿到异步任务的结果。(子线程重写call方法有返回值,认为异步任务调用结束可能需要知道结果)
        //不加get方法,先执行主线程再执行子线程,即先输出"执行完了";加入get方法,先执行子线程。再执行主线程,即最后输出"执行完了"。
        //task.get();要求先打印异步任务结果
        //当然,若该代码放在System.out.println("执行完了");之后,则先主线程执行。
        try {
            System.out.println(task.get());     //0
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("执行完了");
​
    }
}

方式三现场实现的方法:

public class MyThread{
​
    private static Integer ticket = 10;
​
    public static void sale() throws Exception {
        //让子线程执行的代码
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    public static Integer getTicket() {
        return ticket;
    }
}
public class Demo3 {
    public static void main(String[] args) {
​
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                MyThread.sale();
                return MyThread.getTicket();
            }
        });
​
        Thread newThread = new Thread(task);
        newThread.start();
​
        try {
            System.out.println(task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("执行完了");
​
    }
}

也可用lamda表达式 :

FutureTask task = new FutureTask(()-> {
           MyThread.sale();
           return MyThread.getTicket();
        });

第四种创建线程的方式:

资源复用:将东西分离,看哪些可共用,可共用的用完之后不让它马上回收,建一个集合将它们装起来。一般,该集合称为各种资源池。java里提供了线程池,装的线程对象,用法:先创建线程池,将要执行的代码提交给获取的线程,让其执行。执行结束,对象回归到线程池。(类似数据库连接池)

线程池核心类:ThreadPoolExecutor,即线程池生成器。

public static void main(String[] args) {
        //官方提供的Executors,在java.util.concurrent包。第三方也有,这里说官方的。

        ExecutorService executorService = Executors.newCachedThreadPool(); //缓冲线程池,自己扩容,自己控制大小

        ExecutorService executorService1 = Executors.newFixedThreadPool(10); //指定初始大小的线程池

        //可延时调度线程池,也指定初始大小
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

        ExecutorService executorService2 = Executors.newSingleThreadExecutor(); //单线程线程池(测试用)
​
    }
public class Demo4 {
    public static void main(String[] args) {
        //这些线程池用法类似,以指定初始大小的线程池为例:
        //注:创建线程池的方法很多,但底层大多在用ThreadPoolExecutor去创建线程池。
        ExecutorService executorService1 = Executors.newFixedThreadPool(10);
​
        //executorService1.submit(Callable<T> task)
        //executorService1.submit(Runnable task)
        executorService1.submit(()->{       //要执行的代码提交给线程池
            try {
                MyThread.sale();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        //一旦线程池创建,就有专门维护线程状态的线程存在,需要主动关闭线程池
        executorService1.shutdown();
    }
}
public class MyThread {
​
    private static Integer ticket = 10;
​
    public static void sale() throws Exception {
        //让子线程执行的代码
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    public static Integer getTicket() {
        return ticket;
    }
}

自己手动创建线程池,不用其他人提供的:

先看ThreadPoolExecutor的相关参数:(面试题或笔试题)

public ThreadPoolExecutor(int corePoolSize,       启动时创建的线程个数
                              int maximumPoolSize,   最大线程个数
                              long keepAliveTime,    存活时间(超过存活时间,自动释放,不浪费线程池空间) 
                              TimeUnit unit,         时间单位(与存活时间搭配使用)
                              BlockingQueue<Runnable> workQueue,  任务队列(要执行代码会先放到任务队列,之后由线程池自动分配给线程。任务队列有几种不同的实现策略,如下:
                              //ArrayBlockingQueue  有界队列(数组方式)   可以指定长度(参数部分)
                              //LinkedBlockingQueue  无界队列(链表方式,一个挨一个,谁先进谁执行)
                              //PriorityBlockingQueue  优先队列  默认按类名排(可自定义排序规则)
                              //SynchronousQueue     同步队列(一旦有任务进来必须马上交给线程处理)
                              )
                              ThreadFactory threadFactory,   创建线程对象的默认工厂函数(一般选用默认工厂创建方式,Executors.defaultThreadFactory())
                              RejectedExecutionHandler handler) {     拒绝策略(比如:选用有界队列,线程在执行,任务队列也放满了,再有任务进来,就得指定处理方式。有如下几种方式:
                              AbortPolicy 直接放弃
                              CallerRunsPolicy() 调用主线程,让主线程处理 )
                        
 threadPoolExecutor.submit(MyThread::sale);  把执行的代码提交给线程池执行
      shutdown()  关闭线程池  
public class Demo4 {
    public static void main(String[] args) {
        //concurrent并发,和并发或多线程相关的都在java.util.concurrent包里
​
        //手动创建线程
        ThreadPoolExecutor threadPoolExecutor =
        new ThreadPoolExecutor(5,10,60l, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
​
        //使用方式和调用官方提供线程的使用方式一样
        threadPoolExecutor.submit(()->{
            try {
                MyThread.sale();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        threadPoolExecutor.shutdown();
    }
}

创建方式很多,先写出一种,熟悉以后,再替换成其它方式。使用方式和结果都是差不多的。

3.多线程并发问题

数据安全性

多线程 对共享数据进行写操作
​
 //MESI缓存一致性协议
 //(加载到缓存里的数据会打成标记,由专门的监控去监控这些数据。打上的标记主要有:独享数据,共享数据,改变状态,作废状态。若只有一个核处理数据,该数据会被标记成独享数据;若多个核写数据,标记成共享数据;若共享数据,一个核使数据发生改变(新的数据称为A),其它核会将数据标记为作废状态,直接抛弃,再次从一级,二级,三级缓存依次找该数据。此时A已到达三级缓存,其它核会处理A数据,而不是原先的数据,jvm的jmm就是模拟的这个过程)
 //多线程间写操作 会有线程安全问题
 
    //jvm  模拟计算机运行环境(故称为jvm虚拟机)
    
    //jmm  内存管理模型 
    //(CPU有一级,二级,三级缓存,三级缓存允许多核间共享。数据来源在内存。处理数据时:先读,内存加载数据,传到缓存3。CPU调用数据,先从一级缓存找,找不到去二级找,还没有去三级缓存找。然后写,依次经过一二三,回到内存。这是单线程处理数据流程。
    //但到了多线程,多个处理单元时,写操作时,经过缓存一二,都到达缓存三,就可能出现问题。)
    
    // monitor 监视器
    //(代码中无法直接写monitor,但一些代码可直接触发monitor,如下几个:
    //1.悲观锁:1.synchronized   2.lock
    //1.1 synchronized   同步  触发监视器  
    //(将需要监控的代码用synchronized包起来,保证每次只有一个线程执行。)
    //1.2 lock      
    
    //2.乐观锁
    //2.1 volatile+compareAndSwap  保证线程安全
    //compareAndSwapLong    CAS乐观锁的具体实现
    //1.可见性  2.有序性 3.原子性                  )

在2中是开启一个线程,看下开启多个线程的情况:

public class Demo5 {
    public static void main(String[] args) {
​
        //开三个线程,并都启用。希望运行时间变为原来的三分之一
        //但多线程竞争资源会有数据不安全情况出现。ticket用static修饰,属于类变量,可在不同代码间共享。但计算机执行线程是随意挑的。
        //若没有ticket--;只是一堆线程读这个数,不会有问题。 
        Thread newThread1 = new Thread(()->{
            MyThread.sale();
        },"窗口1");
        Thread newThread2 = new Thread(()->{
            MyThread.sale();
        },"窗口2");
        Thread newThread3 = new Thread(()->{
            MyThread.sale();
        },"窗口3");
​
        newThread1.start();
        newThread2.start();
        newThread3.start();
        System.out.println("执行完了");
    }
}

3.1同步代码块

 synchronized (监视器对象){//使用同一个对象 "xxx"intern() 从地址返回同一个对象
    //需要顺序执行的代码
 }​

用synchronized 触发监控,解决多线程问题。(synchronized同步,做同步代码块(程序执行时,特定代码段必须顺序执行,这种做法也叫悲观锁,多个人争抢资源,认为一定会出错,让一个一个来,但效率很低)。)

view--show ByteCote,虚拟机执行的机器码,找MONITOR,可看到MONITORENTER(监控进入),监控执行代码,MONITOREXIT监控离开。

public class MyThread {
​
    private static Integer ticket = 10;
​
    public static void sale(){
        //让子线程执行的代码
        while (ticket>0){
​
            //synchronized的参数"mylock".intern(),可保证使用的监视器是同一个对象对代码进行监控。
            //保证了线程一个一个来
            synchronized ("mylock".intern()){
                //加if,满足限制,才让线程执行。
                if(ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    public static Integer getTicket() {
        return ticket;
    }
}

3.2同步方法

执行此方法的线程 需要排队执行
public synchronized static void diffTicket(){
        if(ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票 还剩"+ticket);
        }
    }
注意:1.分析此方法是否可以直接加同步synchronized
    2.同步方法 默认使用当前类型做监视器对象  类型锁  MyThread.class

 //在方法上加synchronized,只要执行这个方法的线程都要排队。比如线程1,执行该方法,执行循环,将票卖完。然后其他线程再来执行该方法
    //希望几个线程一起卖票,不是单独一个线程卖完
    public synchronized static void sale(){
        while (ticket>0){
​
                if(ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

将需要同步的部分,单独拿出来写一个方法:

public class MyThread {
​
    private static Integer ticket = 10;
​
    //希望几个线程一起卖票,不是单独一个线程卖完
    public synchronized static void sale(){
        while (ticket>0){
            diffTicket();
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
    public synchronized static void diffTicket(){
        if(ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+ticket);
        }
    }
​
}

之前说,使用synchronized,要有对象充当监视器,同步方法的监视器是谁?(类型对象。类一般指实例对象,类型对象两个。实例对象一般通过new创建,类型对象是虚拟机创建。方法上加synchronized,默认使用类型对象。即同步方法默认使用类型锁)同步方法等同于如下代码:

public class MyThread {
​
    private static Integer ticket = 10;
​
    public synchronized static void sale() {
        while (ticket > 0) {
            synchronized (MyThread.class) {
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket);
                }
            }
​
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
}

3.3LOCK

使用lock 使代码顺序执行
private static final Lock mylock = new ReentrantLock();    
 mylock.lock();  上锁
 mylock.unlock(); 解锁
 
 lock遇到异常不会自动释放锁对象
 synchronized 遇到异常 会自动释放锁对象   
     
     try{
         if(ticket>0){
             ticket--;
             System.out.println(Thread.currentThread().getName()+"卖了一张票 还剩"+ticket);
             if("窗口1".equals(Thread.currentThread().getName())){
                 throw new RuntimeException("窗口被打劫了 卖不了了....");
             }
​
         }
     }finally {
         mylock.unlock();
     }
     

public class MyThread {
​
    private static Integer ticket = 10;
​
    //创建锁对象  java.util.concurrent.locks.Lock;   lock也是悲观锁,进来都得排队
    //Lock里有很多实现类
    private static final Lock myLock = new ReentrantLock();
​
    public synchronized static void sale() {
        while (ticket > 0) {
​
            //需要顺序执行的地方加myLock.lock();
            myLock.lock();
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket);
                }
             //结束的地方加myLock.unlock();
            myLock.unlock();
​
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
}

使用锁,若碰到异常,不会自动释放。

myLock.lock();
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket);
                    throw new RuntimeException("窗口被打劫,卖不了了...");
                }
             //执行过程出现异常,将卡死,因为myLock.unlock();无法执行,没有解锁。
            myLock.unlock();

如何处理:(加try...finally...结构)

 myLock.lock();
            try{
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩" + ticket);
                    throw new RuntimeException("窗口被打劫,卖不了了...");
                }
            }finally {
                myLock.unlock();
            }

3.4 volatile+CAS

volatile 修饰变量  //可见性  有序性
CAS   乐观锁  
//compareAndSwapLong    CAS乐观锁的具体实现
AtomicLong 
LongAdder  
如果多线程 需要使用到数字的加减 一般使用官方提供的操作方式
​
Unsafe直接操作内存地址,不建议直接使用。(底层自己用,给你写好功能)
地址在内存如何存?由基础地址+偏移量(offset)决定。 
valueoffset 数据在内存中真正的位置
volatile 修饰变量,可保证数据在线程间的可见性
数据想安全,有3个点要保证:
1.可见性   2.有序性   3.原子性
悲观锁可保证三个特性。但volatile只能保证前两点,不能保证操作的原子性。
但volatile+compareAndSwap  保证线程安全
compareAndSwap对比并且替换,CAS乐观锁的具体实现。
乐观锁思想:预期的和出现的一致时,才会执行代码(所有的都能过来,不是自己想要的,不做处理)
悲观锁思想:一个一个来(全部堵死,就留一个通道)
乐观锁换到数据上:就是在内存地址里数据可自由改变,但一直做对比,看数据与预期值是否一样,一样时才运行。
乐观锁的代码很难写,故官方底层写好,提供了一些方式。

模拟1000个线程拿id:

public class IdGenerator {
    private static Long id = 0l;
​
    public static void myIncrement(){
        id++;
    }
​
    public static Long getId() {
        return id;
    }
}
​
class Test{
    public static void main(String[] args) {
        //模拟1000个线程同时拿id的情况:
        List<Thread> listThread = new ArrayList<Thread>();
        for (int i = 0; i < 1000; i++) {
            listThread.add(new Thread(()->{
                IdGenerator.myIncrement();
            }));
        }
​
        //启动线程
        listThread.forEach(Thread::start);
​
        //子线程走完,再走主线程
        for(Thread thr:listThread){
            try {
                thr.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
​
        //读取结果
        System.out.println(IdGenerator.getId());
​
    }
}

以上未做限制,处理方式若用悲观锁

synchronized ("lick".intern()){
            id++;
        }

效率偏低,就相当于一个线程id自增操作。

用乐观锁:

public class IdGenerator {
    //private static Long id = 0l;
    //Atomic原子,使用官方提供的原子操作类(乐观锁)
    private static AtomicLong id = new AtomicLong(0);   //参数为初始值
​
    public static void myIncrement(){
          //  id++;
          //需要自增时的操作
          id.incrementAndGet();
    }
​
    public static Long getId() {
        //需要获取值时
        return id.get();
    }
}

官方提供的另外一种:

public class IdGenerator {
​
    //官方提供的LongAdder
   private static LongAdder id = new LongAdder();
   //给LongAdder添值
    static {
        id.add(100l);   //初始值从1000开始
   }
​
    public static void myIncrement(){
​
          //需要自增时的操作
          id.increment();
    }
​
    public static Long getId() {
        //需要获取值时
        return id.longValue();
    }
}

面试:CAS思想是什么?

4单例

单例在单线程里的使用:

public class SingletonDemo {
​
    private SingletonDemo(){}
​
    private static SingletonDemo mysig = new SingletonDemo();
​
    public static SingletonDemo getInstance(){
        return mysig;
    }
}
​
/*class SingletonDemo{
​
    private SingletonDemo(){}
​
    private static SingletonDemo mysig;
​
    public static SingletonDemo getInstance(){
        if(mysig == null){
            mysig = new SingletonDemo();
        }
        return mysig;
    }
​
}*/
​
class Test{
    public static void main(String[] args) {
        System.out.println(SingletonDemo.getInstance());
    }
}

懒汉模式在多线程里,不能保证是同一个对象:

public class SingletonDemo{
​
    private SingletonDemo(){}
​
    private static SingletonDemo mysig;
​
    public static SingletonDemo getInstance(){
        if(mysig == null){
            mysig = new SingletonDemo();
        }
        return mysig;
    }
​
}
​
class Test{
    public static void main(String[] args) {
        //多线程里获取对象,结果是有些数据不一样
        //同一时间很多线程挤进去,new SingletonDemo();不止执行一次,不在同一时间挤进去的线程,获取对象是同一个。
        //即刚创建时,一堆线程挤进来,创建了好几个对象。
        new Thread(()->{
            System.out.println(SingletonDemo.getInstance());
        }).start();
​
        new Thread(()->{
            System.out.println(SingletonDemo.getInstance());
        }).start();
​
        new Thread(()->{
            System.out.println(SingletonDemo.getInstance());
        }).start();
​
    }
}

为保证是同一个对象,要加同步:

public static SingletonDemo getInstance(){
​
        synchronized (SingletonDemo.class) {
​
            if (mysig == null) {
                mysig = new SingletonDemo();
            }
        }
        return mysig;
    }

加同步保证了最初挤进来的线程,一个个排列。但后续的不需要排列,也被强行排列。故进一步使用双重检测的方式:(典型的面向过程的方式)

public static SingletonDemo getInstance(){
​
        if (mysig == null) {
            synchronized (SingletonDemo.class) {
                if (mysig == null) {
                    mysig = new SingletonDemo();
                }
            }
        }
​
        return mysig;
    }

前人总结,即使这样写还会出现概率极低的错误:(java虚拟机里有及时编译机制,叫JIT,用于提高虚拟机执行效率。一堆对象创建好,有的关联这批,有的关联那批,JIT执行时,哪种效率最高,使用哪种方式,故使用时真正执行效率和想象的顺序可能不一样,但结果一样。单线程没问题,但多线程中,顺序不一样,可能导致数据乱串。)故,还要有一步:

 //加volatile关键字,保证可见性和有序性(可以阻止代码重排。代码重排是JIT为了提高虚拟机效率的一种策略)
    private volatile static SingletonDemo mysig;
​
    public static SingletonDemo getInstance(){
​
        if (mysig == null) {
            synchronized (SingletonDemo.class) {
                if (mysig == null) {
                    mysig = new SingletonDemo();
                }
            }
        }
​
        return mysig;
    }

笔试题:写多线程里的单例模式。

单例在多线程使用时
public class SingletonDemo {
    /*
    * 饿汉
    *
    * 懒汉  延迟加载
    *
    * */
    //private static SingletonDemo mysig = new SingletonDemo();
    private volatile static SingletonDemo mysig;
​
​
    public static SingletonDemo getInstance(){
        
        //JIT 即时编译  代码重排 提高虚拟机执行效率
        //volatile 可以阻止代码重排
        
       //双重检测 保证安全性 和后续使用的效率
        if(mysig==null){
            //排队创建
            synchronized (SingletonDemo.class){
                //延迟加载
                if(mysig==null){
                    mysig = new SingletonDemo();
                }
            }
        }
        return mysig;
    }
​
    //私有构造
    private SingletonDemo(){}
​
}    

单例模式的另一个笔试题,与单例冲突的情形一(也叫打破单例)(平时不会这样写):(和线程没关系)

public class SingletonDemo implements Cloneable{
//类可实现克隆接口
​
    private SingletonDemo(){}
​
    private volatile static SingletonDemo mysig;
​
    public static SingletonDemo getInstance(){
​
        if (mysig == null) {
            synchronized (SingletonDemo.class) {
                if (mysig == null) {
                    mysig = new SingletonDemo();
                }
            }
        }
​
        return mysig;
    }
​
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
​
class Test{
    public static void main(String[] args) {
​
        SingletonDemo instance = SingletonDemo.getInstance();   //创建对象
        Object clone = null;
        try {
            clone = instance.clone();    //克隆对象
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
​
        System.out.println(instance);
        System.out.println(clone);
​
        //结果:不是同一个实例对象
    }
}

如何保证是同一个实例对象:(在重写的克隆方法里,将返回值改成同一个对象)

 @Override
    protected Object clone() throws CloneNotSupportedException {
        return mysig;
    }

与单例冲突的情形二:反序列化

解决方式:readResolve() 返回对象的方法。允许重写,和克隆重写一样,返回同一个对象即可。

5死锁

多线程间 互相持有了对方需要的资源 并且不释放
避免死锁  如果有嵌套使用 认真检查充当监视器的对象

public class DeadLock {
     //画家画画,需要画笔和颜料两个资源,但资源同一时间只能给一个人用
    private static String lock1 = "画笔";
    private static String lock2 = "颜料";
​
    public static void wangPrint(){
        synchronized (lock1.intern()){
            System.out.println("小王拿到了画笔");
            synchronized (lock2.intern()){
                System.out.println("小王拿到了颜料");
                System.out.println("小王画画");
            }
        }
    }
​
    public static void liPrint(){
        synchronized (lock2.intern()){
            System.out.println("小李拿到了颜料");
            synchronized (lock1.intern()){
                System.out.println("小李拿到了画笔");
                System.out.println("小李画画");
            }
        }
    }
​
}
​
​
class Test{
    public static void main(String[] args) {
        //通过两个线程,让它们同时画画
        new Thread(()->{
            DeadLock.wangPrint();
        }).start();
​
        new Thread(()->{
            DeadLock.liPrint();
        }).start();
​
        //结果是:小王拿到了画笔,小李拿到了颜料。都在等对方释放资源,程序无法停止
    }
}

6线程通信

上面讲了多个线程做一个事会产生的问题。还有线程间协作的问题,也叫线程通信问题。(当前线程控制其他线程的运行和挂起)

多线程间 存在 使用不同的代码 但是操作的是相同的资源
需要线程暂停  和线程继续执行
synchronized
OrdeDemo.class.wait() 当前持有监视器的线程等待
OrdeDemo.class.notify() 由于当前监视器对象等待的线程唤醒 
    
private static final Lock mylock = new ReentrantLock();
private static Condition mycond = mylock.newCondition();    
​
mycond.await();  等待
mycond.signal(); 唤醒    

线程通信经典模型:生产者-消费者模型

public class OrdeDemo {
    //生成订单,处理订单,还有个集合维护订单状态
    //多线程下对集合写操作,要用安全的集合。
    //或用Collections.synchronizedList()拿到安全的集合
    private static List<String> orderList = Collections.synchronizedList(new ArrayList<>());  //维护订单状态
​
    //生成订单
    public static void produceOrder(){
        orderList.add("新订单");    //对象用字符串模拟一下
        System.out.println("生成了新订单  当前订单数:" + orderList.size());
    }
​
    //处理订单
    public static void handleOrder(){
        orderList.remove(0);    //用移除对象作为处理订单结束的标志,参数是索引
        System.out.println("处理了订单  当前订单数:" + orderList.size());
    }
​
}
​
class Test{
    public static void main(String[] args) {
        //让线程启动后不停生产订单
        new Thread(()->{
            while(true){
                OrdeDemo.produceOrder();
                try {
                    TimeUnit.MILLISECONDS.sleep(400);   //每生产一个休息400毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
​
        //消费订单,当集合里没东西时,应挂起
        new Thread(()->{
            while(true){
                OrdeDemo.handleOrder();
                try {
                    TimeUnit.MILLISECONDS.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

当集合里没东西时,应挂起:

public static void handleOrder(){
​
        //监视器应配合同步去用
        synchronized (OrdeDemo.class){
            if(orderList.size() == 0){
                System.out.println("当前没有订单");
                try {
                    //让线程暂停,wait()属于Object对象。
                    //new Object().wait();
                    OrdeDemo.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
​
        orderList.remove(0);    //用移除对象作为处理订单结束的标志,参数是索引
        System.out.println("处理了订单  当前订单数:" + orderList.size());
    }

当线程生产订单,集合里不空时,另一个线程应该消费订单,但并没有。故,生产订单的线程应唤醒它:

public static void produceOrder(){
        synchronized (OrdeDemo.class){
            orderList.add("新订单");    //对象用字符串模拟一下
            System.out.println("生成了新订单  当前订单数:" + orderList.size());
​
            //同样先找到监视器。notify()唤醒一个 notifyAll()都唤醒
            //同样,监视器对象直接拿出来用不行,仍要配合同步
            OrdeDemo.class.notify();
        }
    }

若再加一个生产订单的线程,但不想让订单无限制的增长,要求集合里订单达到10个,生产的线程休息:

public static void produceOrder(){
        synchronized (OrdeDemo.class){
            if(orderList.size() == 10){
                System.out.println("当前订单太多 歇一会");
                try {
                    OrdeDemo.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            orderList.add("新订单");
            System.out.println("生成了新订单  当前订单数:" + orderList.size());
            OrdeDemo.class.notify();
        }
    }

同样也要有对应的唤醒:

public static void handleOrder(){
​
        synchronized (OrdeDemo.class){
            if(orderList.size() == 0){
                System.out.println("当前没有订单");
                try {
                    OrdeDemo.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
​
        orderList.remove(0);   
        System.out.println("处理了订单  当前订单数:" + orderList.size());
        OrdeDemo.class.notify();
    }

但还是有问题,wait()方法应按官方给的案例进行:(条件的部分用while )

public static void produceOrder(){
        synchronized (OrdeDemo.class){
            while (orderList.size() == 10){
                System.out.println("当前订单太多 歇一会");
                try {
                    OrdeDemo.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            orderList.add("新订单");
            System.out.println("生成了新订单  当前订单数:" + orderList.size());
            OrdeDemo.class.notify();
        }
    }
​
    //处理订单
    public static void handleOrder(){
​
        synchronized (OrdeDemo.class){
            while(orderList.size() == 0){
                System.out.println("当前没有订单");
                try {
                    OrdeDemo.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
​
        orderList.remove(0);
        System.out.println("处理了订单  当前订单数:" + orderList.size());
        OrdeDemo.class.notify();
    }

以上是用synchronized处理生产者-消费者问题,下面用其它方式:

测试类同上,其它代码如下:

public class OrdeDemo2 {
​
    private static List<String> orderList = Collections.synchronizedList(new ArrayList<>());
    //创建锁对象
    private static final Lock myLock = new ReentrantLock();
    private static Condition mycond = myLock.newCondition();  //通过锁对象拿到Condition对象
    //Condition对象可用来替代监视器对象,对线程挂起和唤醒
​
    public static void produceOrder(){
       // synchronized (OrdeDemo.class){
        myLock.lock();
        try {
            while (orderList.size() == 10){
                System.out.println("当前订单太多 歇一会");
                try {
                    //OrdeDemo.class.wait();
                    //wait()   await()   都可传秒数,指定一定时间唤醒,若不传时间,则一直等待。
                    mycond.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
​
            orderList.add("新订单");
            System.out.println("生成了新订单  当前订单数:" + orderList.size());
           // OrdeDemo.class.notify();
            //signal()    signalAll()    也有两个
            mycond.signal();
        }finally {
            myLock.unlock();
        }
​
      //  }
​
    }
​
    public static void handleOrder(){
​
       // synchronized (OrdeDemo.class){
        myLock.lock();
        try {
            while(orderList.size() == 0){
                System.out.println("当前没有订单");
                try {
                    //OrdeDemo.class.wait();
                    mycond.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //  }
​
            orderList.remove(0);
            System.out.println("处理了订单  当前订单数:" + orderList.size());
           // OrdeDemo.class.notify();
            mycond.signal();
        }finally {
            myLock.unlock();
        }
​
    }
​
}

7线程状态

public class ThreadStatus {
    public static void main(String[] args) {
​
        Thread mythread = new Thread(() -> {
            System.out.println("aaa");
        });
​
        //查看线程状态
        System.out.println(mythread.getState().name());   //new 新创建线程,其状态就是new
    }
}
public class ThreadStatus {
    public static void main(String[] args) {
​
        Thread mythread = new Thread(() -> {
            System.out.println("aaa");
            System.out.println(Thread.currentThread().getState().name());  //runnable
            //线程执行过程中的状态:runnable   执行状态
        });
        mythread.start();
​
    }
}
public class ThreadStatus {
    public static void main(String[] args) {
​
        Thread mythread = new Thread(() -> {
            System.out.println("aaa");
            System.out.println(Thread.currentThread().getState().name());
        });
        mythread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(mythread.getState().name());   //terminated    终极状态
​
    }
}

线程状态都在status里,里面有枚举类,如下:

        /**
         * Thread state for a thread which has not yet started.
         */
       // NEW, 初始状态  (新建线程时的状态)
​
                /**
                 * Thread state for a runnable thread.  A thread in the runnable
                 * state is executing in the Java virtual machine but it may
                 * be waiting for other resources from the operating system
                 * such as processor.
                 */
               // RUNNABLE,   运行状态
​
                /**
                 * Thread state for a thread blocked waiting for a monitor lock.
                 * A thread in the blocked state is waiting for a monitor lock
                 * to enter a synchronized block/method or
                 * reenter a synchronized block/method after calling
                 * {@link Object#wait() Object.wait}.
                 */
               // BLOCKED,   阻塞状态
​
                /**
                 * Thread state for a waiting thread.
                 * A thread is in the waiting state due to calling one of the
                 * following methods:
                 * <ul>
                 *   <li>{@link Object#wait() Object.wait} with no timeout</li>
                 *   <li>{@link #join() Thread.join} with no timeout</li>
                 *   <li>{@link LockSupport#park() LockSupport.park}</li>
                 * </ul>
                 *
                 * <p>A thread in the waiting state is waiting for another thread to
                 * perform a particular action.
                 *
                 * For example, a thread that has called <tt>Object.wait()</tt>
                 * on an object is waiting for another thread to call
                 * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
                 * that object. A thread that has called <tt>Thread.join()</tt>
                 * is waiting for a specified thread to terminate.
                 */
               // WAITING,  等待状态
​
                /**
                 * Thread state for a waiting thread with a specified waiting time.
                 * A thread is in the timed waiting state due to calling one of
                 * the following methods with a specified positive waiting time:
                 * <ul>
                 *   <li>{@link #sleep Thread.sleep}</li>
                 *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
                 *   <li>{@link #join(long) Thread.join} with timeout</li>
                 *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
                 *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
                 * </ul>
                 */
                //TIMED_WAITING,  等待指定时间   (有限时的等待)
​
                /**
                 * Thread state for a terminated thread.
                 * The thread has completed execution.
                 */
                //TERMINATED;   线程结束

笔试面试题:1.哪些方法会触发哪些状态

2.线程转化状态

3.wait 和sleep的区别

sleep由Thread对象调用,会进入的状态是等待状态

wait由监视器对象调用,不唤醒就是阻塞状态,唤醒就是等待状态。

wait等待时其它线程会执行,sleep不会主动释放锁资源让其它线程执行,wait会主动释放锁资源让其它线程执行。

(sleep 不释放锁 调用对象不同 线程状态不同

wait 释放锁)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值