1.线程和进程

1.java默认有两个线程:
    1.main线程
    2.GC垃圾回收线程
    
2.java真的可以开启线程么?
    答案是否定的,其实底层本地去调用是c++的方法,因为java是运行在虚拟机上的,无法操作硬件!
    原理如下:
        public synchronized void start() {
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
            group.add(this);
            boolean started = false;
            try {
                //重点:线程内部调用的是start0的本地方法
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                }
            }
        }
        //调用的其实是内部的start0方法
        private native void start0();
        
3.并发、并行
     3.1 并发:多个线程操作同一个资源
         CPU 一核情况下,模拟出来多条线程,快速交替,营造出共同进行的假象!
     3.2 并行: 
         CPU 多核情况下,多个线程可以同时执行!
     
     程序获取cpu核数:
         System.out.println("cpu核数:"+
             Runtime.getRuntime().availableProcessors()
         );
     如何提高执行效率呢:
         1.并发:CPU资源的充分利用
         2.并行:线程池
         多线程在多核cpu下的执行速率快于单线程,因为多核cpu下,多个线程是同时执行的,
         但是单核cpu下,多线程的执行速率慢于单线程的速率,因为单核下,多线程是交替进行,会有上下文的切换!
 
 结论:
     1.单核CPU下,多线程不能实际的提高程序运行效率,只是为了能够在不同的任务间切换,不同线程轮流使用CPU,
         会涉及到下上文切换,会损失一部分的速率
     2.多核cpu可以并行跑多个线程,但是是否能够提升效率还得分情况:
         2.1 有的任务:经过精心设计,将任务拆分,并行执行,当然可以提高程序得运行效率,但是不是所有得任务都能拆分
     3.IO操作不占用CPU,只是我门一半拷贝文件使用的是【阻塞IO】,这时相当于线程虽然不用cpu,但需要一直等待IO结束,没能充分利用线程
       后面会有【非阻塞IO】和【异步IO优化】
     

1.线程的状态

总计6状态:
     1. NEW(新建)
     2. RUNNABLE(运行)
     3. BLOCKED(阻塞)
     4. WAITING(等待)
     5. TIMED_WAITING(超时等待,超时后就过)
     6. TERMINATED(终止)
 
 1.wait和sleep的区别
     1.1 来自不同的类
         wait-->Object
         sleep--->Thread
     1.2 关于锁的释放
         wait:会释放锁,  sleep:不会释放锁
     1.3 使用的范围:
         wait--->只能在同步代码块中使用
         sleep--->可以在任意位置上使用
     1.4 是否需要捕获异常
         wait:不需要捕获异常
         sleep:必须要捕获异常

2.Lock锁(重点)

传统的synchronized
一个简单的场景:售票
    重点:
        1.并发就是多个线程操作同一个资源,资源类尽量是一个纯粹的类,不和其他做耦合,之前的是在资源类上实现runable接口,这样不太好!
        2.lambda表达式的使用:
示例如下:
    public class SellTicket {
        public static void main(String[] args) {
            /*
                1.并发:多个线程操作同一个资源类,把资源丢入到线程中
                2.lambda表达式:(请求参数)->{代码}
             */
    
            Ticket ticket=new Ticket();
            new Thread(()->{
                    for(int i =0;i<=50;i++){
                        ticket.sellTicket();
                    }
                },"A").start();
            // 重点2:不使用lambda表达式,定义匿名内部类,并实现其run方法
            //发现lambda表达式是省略了匿名内部类命和方法名
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i =0;i<=50;i++){
                        ticket.sellTicket();
                    }
                }
            }, "B").start();
            new Thread(()->{
                for(int i =0;i<=50;i++){
                    ticket.sellTicket();
                }
            },"C").start();
        }
    }
    //重点3:纯粹的资源类,不和任何做耦合,不实现runable接口
    class Ticket{
        private int num=50;
        //重点4:方法上加锁,可以按照预期卖票
        public synchronized void sellTicket(){
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"卖票!剩余票:"+num--);
            }
        }
    }

上述例子使用synchronized实现了锁,那如何根据Lock接口实现锁呢?
由上述截图可知,Lock接口有三个实现类:
    1.ReentrantLock(普通锁-->最常用)
    2.ReentrantReadWriteLock.ReadLock(读写锁-->读锁)
    3.ReentrantReadWriteLock.WriteLock(读写锁-->写锁)

具体的实现代码如下:
    public class Juc_Test_Lock {
        public static void main(String[] args) {
            int num=50;
            Ticket ticket=new Ticket(num);
            new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"A").start();
            new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"B").start();
            new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"C").start();
        }
    }
    @AllArgsConstructor
    @NoArgsConstructor
    class Ticket{
        public Ticket(int ticketNum) {
            this.ticketNum = ticketNum;
        }
        private int ticketNum;
        //重点1:可重入锁的使用
        Lock lock=new ReentrantLock();
        public  void sell(){
            //重点2:步骤1:加锁
            lock.lock();
            try {
                //步骤2:trycatch写业务逻辑
                if(ticketNum>0)
                    System.out.println(Thread.currentThread().getName()+"售票:剩余-->"+ticketNum--);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //重点3:步骤3:解锁
                lock.unlock();
            }
        }
    }


Lock lock=new ReentrantLock();代码细究:底层代码如下:
    
    {
        构造器1:发现创建的是非公平锁
        public ReentrantLock() {
            sync = new NonfairSync();
        }
        构造器2:传入参数,创建对应的锁,默认是非公平锁
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    }

1.公平锁FairSync:不能插队,排队进行
2.非公平锁NonfairSync: 可以插队,默认都是非公平锁(包括synchronized )


重点:synchronized和lock锁的区别
    1.synchronized是内置关键字,Lock是java类
    2.synchronized无法判断获取锁的状态,Lock可以判断是否获取锁
    3.synchronized会自动会释放锁,Lock必须要手动释放锁,要不会造成死锁
    4.synchronized(线程1获取锁,线程2等待,即使线程1阻塞);
      Lock锁就不一定会等待下去!
    5.synchronized 可重入锁,不可以中断,非公平
      Lock,可重入锁,可以判断锁,非公平(可以自己设置)
    6.synchronized:适合锁少量的同步代码,
      Lock适合锁大量的同步代码

问题:锁是什么?如何判断锁的是谁?    
    1.对象(可能是多个)
    2.Class(只能是一个)

生产者和消费者的synchronized 版本以及虚假唤醒问题

先看一个场景:
    一个卖面的面馆,有一个做面的厨师和一个吃面的食客,需要保证,厨师做一碗面,食客吃一碗面,
    不能一次性多做几碗面,更不能没有面的时候吃面;
    按照上述操作,进行十轮做面吃面的操作。
样例代码:
    public class Juc_test {
        public static void main(String[] args){
            Noodels noodels=new Noodels();
            //重点1:创建两个线程:1厨子造面   2.食客吃面,先做10碗
            new Thread(()->{
                try {
                    for (int i=0;i<=10;i++){
                        noodels.makeNoodeles();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"厨子").start();
            new Thread(()->{
                try {
                    for (int i=0;i<=10;i++){
                        noodels.eatNoodeles();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"食客").start();
        }
    }
    class Noodels{
        //面的数量
        private int num=0;
        /*
            做面方法
         */
        public synchronized  void makeNoo生产者和消费者的synchronized 版本以及虚假唤醒问题deles() throws InterruptedException {
            if(num>0){
                this.wait();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
            //面已做好,唤醒食客
            this.notifyAll();
        }
    
        public synchronized void eatNoodeles() throws InterruptedException {
            if (num==0){
                this.wait();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
            this.notifyAll();
        }
    }
运行结果如下:很满意,达到了效果,做一碗吃一碗的水平!
    厨子造面了!当前面数:1
    食客造面了!当前面数:0
    厨子造面了!当前面数:1
    食客造面了!当前面数:0
    厨子造面了!当前面数:1
    食客造面了!当前面数:0
    厨子造面了!当前面数:1


问题来了:如果多个线程呢,即两个厨子,两个食客呢??改造代码,只需要在测试类里多加几个线程!
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"厨子A").start();
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"厨子B).start();
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客A").start();
    new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客B").start();
运行结果:打破了吃一碗造一碗的逻辑了,并且明明加了synchronized锁,为什么会这样呢?这里就涉及到了线程的虚假唤醒!
    厨子A造面了!当前面数:1
    食客A造面了!当前面数:0
    厨子B造面了!当前面数:1
    厨子A造面了!当前面数:2
    厨子B造面了!当前面数:3
    ...

虚假唤醒

上面的问题就是"虚假唤醒"。
当我们只有一个厨师一个食客时,只能是厨师做面或者食客吃面,并没有其他情况;
但是当有两个厨师,两个食客时,就会出现下面的问题:

1.初始状态

2.厨师A得到操作权,发现面的数量为0,可以做面,面的份数+1,然后唤醒所有线程;

3.厨师B得到操作权,发现面的数量为1,不可以做面,执行wait操作;

4.厨师A得到操作权,发现面的数量为1,不可以做面,执行wait操作;

5.食客甲得到操作权,发现面的数量为1,可以吃面,吃完面后面的数量-1,并唤醒所有线程;

6.此时厨师A得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;

7.此时厨师B得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;

    if(num != 0){
         this.wait();
    }
出现虚假唤醒的原因:
    是从阻塞态到就绪态再到运行态没有进行判断(num的判断),直接进行下述的+1或-1,
    所以我们只需要让其每次得到操作权时都进行判断就可以了;
解决办法:将if判断改为while,每次都会判断,唤醒以后都先判断条件是否成立!
    while(num != 0){
         this.wait();
    }

JUC版的生产者和消费者

使用Lock和Condition实现上述生产者和消费者问题:
    样例代码:
        class Noodels{
            //面的数量
            private int num=0;
            //重点1:获取lock锁(可重入锁)
            Lock lock = new ReentrantLock();
            //重点2:获取condition对象
            Condition condition = lock.newCondition();
            /*
                做面方法
             */
            public void makeNoodeles() throws InterruptedException {
                //重点3:加锁,不用synchronized关键字
                lock.lock();
                try {
                    while (num>0){
                        //重点4:使用condition.await来阻塞队列
                        condition.await();
                    }
                    num++;
                    System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
                    //重点5:condition.signalAll();来解锁
                    condition.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //重点5:解锁
                    lock.unlock();
                }
            }
        
            public  void eatNoodeles() throws InterruptedException {
                lock.lock();
                try {
                    while (num==0){
                        condition.await();
                    }
                    num--;
                    System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
                    condition.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
        
问题:如何精确唤醒呢?例如A,B,C,D四个线程,如何精确的执行顺序A->B->C->D
    实现如下:    
    /**
     * Bruk.liu
     * A执行完调用B,B执行完调用C,C执行完调用A
     */
    public class C {
        public static void main(String[] args) {
            Data3 data = new Data3();
     
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printA();
                }
            },"A").start();
            
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printB();
                }
            },"B").start();
            
            new Thread(()->{
                for (int i = 0; i < 10; i++) {
                    data.printC();
                }
            },"C").start();
        }
    }
     
    //资源类
    class Data3{
        //创建Lock锁
       private Lock lock = new ReentrantLock();
        //同步监视器,创建三个监视器
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
        private int number = 1;//1就是A执行,2就是B执行
     
       public void printA(){
            lock.lock();
           try {
               while(number != 1){
                   //等待
                   condition1.await();
               }
               System.out.println(Thread.currentThread().getName()+"===>AAAAAAAAA");
               //唤醒指定线程
                number = 2;
               //调用指定监视器,唤醒指定线程
               condition2.signal();
           } catch (Exception e) {
               e.printStackTrace();
           } finally {
               lock.unlock();
           }
       }
     
        public void printB(){
            lock.lock();
            try {
                while(number != 2){
                    //等待
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName()+"===>BBBBBBBB");
                //唤醒指定线程
                number = 3;
                condition3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
     
        public void printC(){
            lock.lock();
            try {
                while(number != 3){
                    //等待
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName()+"===>CCCCCCCCC");
                //唤醒指定线程
                number = 1;
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

8锁现象

1.synchronized锁的对象是方法调用者!
2.static synchronized...锁的是class这个类!
示例如下:
1.两种方法都sendMessage/call都加上锁synchronized,锁的是方法的调用者!
        public class Juc_Test_Lock {
            public static void main(String[] args) throws InterruptedException {
               //同一个资源对象Phone,所以所的是同一资源对象phone
               Phone phone=new Phone();
               new Thread(()->{phone.sendMessage();},"发短信!").start();
               //重点1:两个线程间休眠4秒,能明显看出哪个线程先执行
               TimeUnit.SECONDS.sleep(1);;
               new Thread(()->{phone.call();},"打电话!").start();
            }
        }
        class Phone{
            //重点2:方法加锁,并在打印前加入休眠,能明显看出哪个先执行!
            public synchronized void sendMessage(){
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("发送短信");
            }
            public synchronized void call(){
                System.out.println("打电话!");
            }
        }
    输出:因为两个方法都加了synchronized,锁的是方法的调用者,方法调用者是同一对象phone,所以锁是同一把锁!
        发送短信
        打电话!

2.两个对象:
    创建两个对象,因为synchronized锁的是方法调用者,此处是 phone1和phone2,不是同一对象,所以线程2不会等待线程1执行完毕
        Phone phone1=new Phone();
        Phone phone2=new Phone();
        new Thread(()->{phone1.sendMessage();},"发短信!").start();
        TimeUnit.SECONDS.sleep(1);;
        new Thread(()->{phone2.call();},"打电话!").start();
    结论:
        打电话!   
        发送短信 

3.static synchronized:锁定的是这个类模板
    代码示例如下:
        public class Juc_Test_Lock {
            public static void main(String[] args) throws InterruptedException {
               Phone phone=new Phone();
               new Thread(()->{phone.sendMessage();},"发短信!").start();
               TimeUnit.SECONDS.sleep(1);;
               new Thread(()->{phone.call();},"打电话!").start();
            }
        }
        class Phone{
            重点1:方法上使用static synchronized修饰
            public static synchronized void sendMessage(){
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("发送短信");
            }
            public static synchronized void call(){
                System.out.println("打电话!");
            }
        }
    输出:static synchronized:锁定的是这个类模板,线程2必须等待线程1执行完毕后才能执行!因为这是一把锁
        发送短信
        打电话!

4.上述情况下,方法加了static synchronized,但是是两个对象
    重点1:创建了两个对象去执行static synchronized修饰的方法
        Phone phone1=new Phone();
        Phone phone2=new Phone();
        new Thread(()->{phone1.sendMessage();},"发短信!").start();
        TimeUnit.SECONDS.sleep(1);;
        new Thread(()->{phone2.call();},"打电话!").start();
    输出:发现线程2必须等线程1执行完毕后才执行,因为static synchronized锁的是类模板,是一般锁!
        发送短信
        打电话!

5.staic synchronized和synchronized混用:
    public class Juc_Test_Lock {
        public static void main(String[] args) throws InterruptedException {
           重点1:单个对象 
           Phone phone=new Phone();
           new Thread(()->{phone.sendMessage();},"发短信!").start();
           TimeUnit.SECONDS.sleep(1);;
           new Thread(()->{phone.call();},"打电话!").start();
        }
    }
    class Phone{
        重点2:使用了static synchronized
        public static synchronized void sendMessage(){
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发送短信");
        }
        重点3:使用synchronized
        public synchronized void call(){
            System.out.println("打电话!");
        }
    }
    输出:因为staic synchronized和synchronized锁的是不同的对象,synchronized锁的是方法调用者phone对象,而staic synchronized锁的是Phnoe类模板
    所以是两把不同的锁,所以线程2不会等待1执行结束才执行!
        打电话!
        发送短信

 

posted @ 2022-05-13 20:28  努力的达子  阅读(72)  评论(0编辑  收藏  举报