22天学习java基础笔记之day12&13

一、多线程

1、概述

在一个进程中有多条执行路径。

A:进程 正在执行的程序,它代表着应用程序的执行区域。

B:线程 进程的执行路径.就是进程中一个负责程序执行的控制单元。

线程总是属于某个进程,进程中的多个线程共享进程的内存。

2、关于多线程的相关问题

  1. jvm的启动是多线程的还是单线程的,为什么?

多线程的,它至少启动了两个线程(主线程和垃圾回收线程),垃圾回收机制这个线程不可能是在程序执行完毕后才启动的,否则的话,我们的程序很容易出现内存溢出。

  1. 调用start方法和run方法区别?

调用start方法后,线程进入就绪状态,此时线程对象仅有执行资格,还没有执行权。当该线程对象抢到了执行权时,方可调用run方法,当run方法执行完毕后,线程死亡,不能复生。

  1. 线程的随机性的导致的原因?

在同一时刻,CPU只能执行一个程序,这个多线程的程序其实是CPU的高速切换造成的。

  1. 什么时候使用多线程?以及创建线程的目的?

多线程的引入是为了解决现实生活中的需求的,提高解决问题的效率。比如购买火车票这个动作。当许多对象要对同一有限资源进行操作的时候,我们就要使用多线程。

  1. 线程状态的内容和每一个状态的特点?

创建线程对象后,并对这个对象进行了一些初始化工作,当调用了start方法后,这个状态就有了执行资格,但是此时还未获得执行权,进入到了就绪状态。当抢到执行权后进入了运行状态,此时该线程既有了执行资格,又有了执行权,当调用了run方法后,此线程进入了死亡状态。当然你也可以调用stop方法令其强制死亡。在运行状态的时候,如果该线程调用了sleep或者wait等方法后,他会进入到阻塞状态,此时,这个对象释放了执行资格和执行权,当他的sleep或者wait时间结束后,亦或者调用了notify(notifyAll)方法,该线程被唤醒,又进入了就绪状态。如此周而复始。

3、创建线程的方式

A:继承Thread

**步骤

                a:定义一个类继承Thread类;

                b:重写Thread类中的run()方法,run()方法里边是多线程要运行的代码;

                c:创建定义的那个类的对象;

                d:调用start()方法开启线程,执行run()方法里的内容。

另外可以通过Thread的getName()获取线程的名称。

public class ThreadDemo extends Thread{

    public void run(){

        for(int x=0;x<10;x++){

            System.out.println("线程"+getName()+"正在运行:"+x);

        }

    }

    public static void main(String[] args) {

        ThreadDemo t1 = new ThreadDemo();

        ThreadDemo t2 = new ThreadDemo();

        t1.start();

        t2.start();

    }

}

        **线程的生命周期  创建-----阻塞------运行------死亡

B:实现Runnable接口

        **步骤    

                a:定义一个类实现Runnable接口;

                b:重写Runnable接口中的run()方法,run()方法里是多线程要运行的代码;

                c:创建定义的那个类的对象,并将其作为参数放置于Thread类的对象里。线程的     任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须 明确要运行的任务

                d:调用start()方法启动线程,执行run()方法里的内容。

public class RunnableDemo implements Runnable {

    public void run() {

        for(int x = 0;x<10;x++){

            System.out.println(Thread.currentThread().getName()+" "+x);

        }

    }

    public static void main(String[] args) {

        RunnableDemo rd = new RunnableDemo();

        Thread t1 = new Thread(rd);

        Thread t2 = new Thread(rd);

        t1.start();

        t2.start();

    }

}

总结:两种方法的比较:实现Runnable接口,将线程的任务从线程的子类中分离出来的,进行了单独的封装,按照面向对象的思想将任务的封装成对象,避免了java中单继承的局限性。(创建线程第二种方式较好)static变量的生命周期过长。

  1. 对创建线程的第二种方式的设计的理解?

创建多线程的第二种方式是实现Runnable接口,并实现run()方法。由于第一种方式有资源不能共享的缺点,它需要创建很多的线程对象,而这些线程对象对同一资源又是独有的,如果设定为共享资源(设为静态),必将消耗太多内存资源,静态变量的生命周期过长。此外,如果一个线程对象类继承了其他类,此时他无法继承Thread类,也就不能使用第一种方式来创建线程。

4、多线程的安全问题

  • 产生的原因

        A:线程访问的延迟

        B:线程的随机性

  • 线程安全问题表现?原因?解决思想?解决具体的体现?

当一个线程对象在执行run方法的某一操作时,其他线程对象也进来了,并发的访问了临界资源,破环了原子操作,造成了数据的不一致。

多线程访问的延迟和线程的随机性产生了线程的安全问题。

当某一线程对象进入了run方法后,如果能做一个标记,说我已经在里面了,其他的哥们(线程对象)你就等着吧,等我操作完了,出来去掉标记你再进去吧。这样一来,原子操作就不会遭到破坏。

具体体现就是给那个原子操作加锁,使整个操作同步,不让其他线程对象破环,保证数据的一致性。

5、同步解决线程安全问题

A:同步代码块

        同步代码块中的锁可以是任意对象,但是要在成员范围内定义.

        在局部的话,会导致锁发生变化,因为你每次执行方法,都会重新创建一个对象.         

                **同步的前提

                        ***至少要有两个线程

                        ***同一个锁

                **同步的好处 提高了安全性

                **同步的弊端 效率较低

安全性和效率是你们一直要考虑的问题,而且很多时候,他们是对立的关系。

B:同步函数

  1. 同步函数的使用

                用synchronized关键字修饰方法即可。

                        public synchronized void show(){

                                //需要同步的代码块

                        }

  1. 同步函数使用的锁

                同步函数使用是this对象锁,静态同步函数的锁是(类名.class)

//采用双重判断完成,同步函数效率低,所以采用同步代码块完成单例的延迟加载

public class Singleton {

    private Singleton(){}

    private static Singleton s = null;

    public static Singleton getInstance(){

        if(s==null){

            synchronized(Singleton.class){

                if(s==null){

                    s = new Singleton();

                }

            }

        }

        return s;

    }

}

  1. 同步的好处,弊端,前提?

它能解决多线程的安全问题,但是效率却下降了许多。前提有二:首先必须得有两个或者两个以上的线程,其次就是这些线程用的是同一把锁。

6、死锁

        每个线程都不会释放自己拥有的锁标记,却阻塞在另外的线程所拥有的锁标记的对象锁池中,就会造成死锁现象。

  1. 产生原因

        假如有A和B两个锁,在A锁中要使用B锁,在B锁中要是A锁,而他们都不想让,最终导致了死锁.

                因为系统资源不足。

                进程运行推进的顺序不合适。

                资源分配不当等。

        如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则

就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

public class DeadLockDemo implements Runnable {

    public boolean flag = false;

    public void run() {

        if (!flag) {

            while (true) {

                synchronized (this) {

                    synchronized (DeadLockDemo.class) {

                        System.out.println("true");

                    }

                }

            }

        } else {

            while (true) {

                synchronized (DeadLockDemo.class) {

                    synchronized (this) {

                        System.out.println("false");

                    }

                }

            }

        }

    }

    public static void main(String[] args) {

        DeadLockDemo dld = new DeadLockDemo();

        Thread t1 = new Thread(dld);

        Thread t2 = new Thread(dld);

        t1.start();

        try{

            Thread.sleep(10);

        }catch(InterruptedException e){

           

        }

        dld.flag=true;

        t2.start();

    }

}

  1. 产生死锁的四个必要条件

                互斥条件:一个资源每次只能被一个进程使用。

                请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

                不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

                循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

只要上述条件之一不满足,就不会发生死锁。

  1. 如何解决?

        不同时在A锁中用B锁,B锁中用A锁.

        理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和

解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确

定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态

的情况下占用资源。因此,对资源的分配要给予合理的规划。

7、线程状态图解

创建:使用start()开启线程。

运行:具备着执行资格,具备着执行权

冻结:释放执行权,同时释放执行资格      

从运行到冻结的方式:

sleep(time),sleep(time)时间到,进入临时阻塞状态(具备着执行资格,但是不具备执行权,正在等待执行权)

wait()线程等待,notify()线程唤醒,进入临时阻塞状态。

消亡:

从运行到消亡的方式:

stop()中止线程;

run()方法结束,线程的任务结束。

二、线程间的通信

1、概述

以前呢,是同一个操作的多个线程来执行一个资源。

现在呢,需求变了,不同的操作,多个线程来完成对某个资源操作。

举例:        池水 放水的时候,加水。

                一堆煤 一辆车把煤拉走,一辆车把煤拉到。

2、应用(等待唤醒机制)

需求:一个线程给学生赋值,另一个线程输出学生的内容。

class Student{

                String name;

                int age;

        }

        class Input implements Runnable{

                private Student s;

                Input(Student s){

                        this.s = s;

                }

                public void run(){

                        int x = 0;

                        while(true){

                                if(x==0){//对共享数据的操作分了多条语句来做的

                                        s.name="aa";

                                        s.age = 10;

                                }else{

                                        s.name="bb";

                                        s.age = 20;

                                }             

                        }             

                }

        }

        class Output implements Runnable{

                private Student s;

                Output(Student s){

                        this.s = s;

                }

                public void run(){

                        while(true){

                                System.out.println(s.name+"  "+s.age);

                        }

                }

        }

        class StudentTest{

                public static void main(String[] args){

                        Studnet s = new Student();

                        Input in = new Input(s);

                        Output out = new Output(s);

                        Thread t1 = new Thread(in);

                        Thread t2 = new Thread(out);

                        t1.start();

                        t2.start();

                }

        }

到此我们发现程序出问题了,就是说aa有可能拿的是bb的年龄,bb有可能拿的是aa的年龄

为什么会出这个问题呢?

线程的随机性.

线程安全问题的产生:

        1:共享数据

        2:对共享数据的操作分了多条语句来做的。

安全问题产生后,我们怎么解决的?

        1:同步代码块

        2:同步函数

经过比较,我们发现同步代码块比较合适,而且在这个过程中,我们一直在找同步代码块中的对象用谁比较合适Object--this--Student s------>资源唯一,可以作为锁!

        class Input implements Runnable{

                private Student s;

Input(Student s){

                        this.s = s;

                }

                public void run(){

                        int x = 0;

                        while(true){    

                                synchronized(s){

                                        if(x==0){

                                                s.name="aa";

                                                s.age = 10;

                                        }else{

                                                s.name="bb";

                                                s.age = 20;

                                        }

                                }

                        }             

                }

        }

        class Output implements Runnable{

                private Student s;

                Output(Student s){

                        this.s = s;

                }

                public void run(){

                        while(true){

                                synchronized(s){

                                        System.out.println(s.name+"  "+s.age);

                                }

                        }

                }

        }

这个时候呢,我们已经完成了线程间的通信,但是效果不是很理想,我们想的是,有值我就输出,没有值,我就先产生一个值。找java,问他有没有提供这样的一个操作,发现它提供了

wait() 让线程等待

        notify() 唤醒线程

        notifyAll() 唤醒线程池中的所有线程

这个时候,我们是如何改进代码的呢?继续

        class Student{

                private String name;

                private int age;

                private boolean flag ;

 

        }

        class Input implements Runnable{

                private Student s;

                Input(Student s){

                        this.s = s;

                }

                public void run(){

                        int x = 0;

                        while(true){    

                                synchronized(s){

                                        if(s.flag){

                                                try{s.wait();}catch (InterruptedException ie){}

                                        }

                                        if(x==0){

                                                s.name="aa";

                                                s.age = 10;

                                        }else{

                                                s.name="bb";

                                                s.age = 20;

                                        }

                                        s.flag = true;

                                        s.notify();

                                        x = (x+1)%2;

                                }

                        }           

                }

        }

        class Output implements Runnable{

                private Student s;

Output(Student s){

                        this.s = s;

                }

                public void run(){

                        while(true){

                                synchronized(s){

                                        if(!s.flag){

                                                try{s.wait();}catch (InterruptedException ie){}

                                        }

                                        System.out.println(s.name+"  "+s.age);

                                        s.flag = false;

                                        s.notify();

                                }

                        }

                }

        }     

wait(),notify(),notifyAll()

通过抓人游戏生动的描述了一下。

3、几个方法的使用

  1. 为什么wait(),notify(),notifyAll()都定义在Object类中?

        A:这些方法存在于同步中。

        B:使用这些方法时必须要标识所属的同步的锁。

        C:锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

  1. wait()sleep()的区别

        A:对时间指定而言

                wait():可以不指定时间。

                sleep():必须指定时间。

        B:对执行权和锁而言

                wait():释放cpu执行权(资格也没了),释放锁。存储于线程池

                sleep():释放cpu执行权,不释放锁(会自动醒)。

  1. 停止线程

        A:通过控制循环

        B:interrupt()方法

                **stop已过时,被interrupt取代

  1. 守护线程 后台线程

        你只要把一个线程设置为守护线程,那么主方法线程结束,不管什么情况,守护线程就结束.

        举例:坦克大战

        A:setDaemon(boolean flag)

  1. join:加入线程,把执行权抢夺,自己执行完毕,其他的线程才可能有机会执行.
  2. toString():线程名称,优先级,线程组(是由多个线程组成的,默认的线程组是main)
  3. yield():让本线程暂停执行,把执行权给其他线程.
  4. setPriority(int num):设置线程优先级

        getPrinrity():获取线程优先级

           线程的级别:1 - 10

           默认级别为5.   

4、生产者与消费者

解决了多个生产者与多个消费者的问题

public class ThreadTest{

    public static void main(String[] args){

        Resource r = new Resource();

        Shengchan sc = new Shengchan(r);

        Xiaofei xf = new Xiaofei(r);

        Thread t1 = new Thread(sc);

        Thread t2 = new Thread(sc);

        Thread t3 = new Thread(sc);

        Thread t4 = new Thread(xf);

        Thread t5 = new Thread(xf);

        t1.start();

        t2.start();

        t3.start();

        t4.start();

        t5.start();

    }

}

class Resource{

    private String name;

    private int count = 1;

    private boolean flag = false;

    public synchronized void set(String name){

        while (flag)

            // 这里写while循环是为了当线程每次醒后再判断一次标记。

            try {

                this.wait();

            } catch (InterruptedException e){

            }

        this.name = name + count;

        count++;

        System.out.println(Thread.currentThread().getName() + "...生产者...."+ this.name);

        flag = true;

        notifyAll();

    }

    public synchronized void out(){

        while (!flag)

            try {

                this.wait();

            } catch (InterruptedException e){

            }

        System.out.println(Thread.currentThread().getName()

                + ".........消费者........." + this.name);

        flag = false;

        notifyAll();

    }

}

class Shengchan implements Runnable{

    Resource r;

    Shengchan(Resource r){

        this.r = r;

    }

    public void run(){

        while (true)

            r.set("烤鸭");

    }

}

class Xiaofei implements Runnable{

    Resource r;

    Xiaofei(Resource r){

        this.r = r;

    }

    public void run(){

        while (true)

            r.out();

    }

}

5Lock&Condition接口

JDK1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了对象中,将隐式动作编程了显示动作。对多线程中的内部细节进行了升级改良。

它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作编程了显示锁操作。 同事更为灵活。可以一个锁上加上多组监视器。

在java.util.concurrent.locks包中提供了一个Lock接口。Lock接口中提供了lock()获取锁,unlock()释放锁(通常需要定义finally代码块中)的操作。Lock接口更符合面向对象的思想,将锁这种事物封装成了对象。

public void run(){

    synchronized(obj){//获取锁。

         code...

         //释放锁。

    }

}

但是对于释放和获取锁的操作,都是隐式的。

JDK1.5后,就有了新的方法,将锁封装成了对象。因为释放锁和获取锁动作,锁自己最清楚。

锁对象的类型就是Lock接口。并提供了,显示的对锁的获取和释放的操作方法。

Lock lock;

public void run(){

   try{

       lock.lock();//获取锁。

       code...throw ...

   }finally{

       lock.unlock();//释放锁.

   }

}
    Lock
接口替代了synchronized

    Condition替代了Object类中监视器方法 wait notify  notifyAll。

    将监视器方法单独封装成了Condition对象。而且一个锁上可以组合多组监视器对象。

实现了多生产者多消费者时,本方只唤醒对方中一个的操作,提高效率。

await()睡眠;signal(),signalAll()唤醒;将这些监视器方法单独进行封装,变成了Condition监视器对象。可以任意锁进行组合。

使用为一般是生产者是被消费者唤醒,消费者是被生产者唤醒。

6Scanner (扩展)

接受从键盘输入的数据

(1)这是java中提供的类,在util包中

(2)用法基本格式:

   Scanner sc = new Scanner(System.in);

   通过sc对象就可以取得值了。

   输入每个数据换行

   int--int OK

   String--String OK

   int--String No

   String--int OK 

   输入数据在一行上.就没有问题

Scanner类的使用 猜数字小游戏

import java.util.Scanner;

import java.util.Random;

class CaiShuTest {

public static void main(String[] args) {

   System.out.println("请输入一个1~100之间的整数!");

   Random r = new Random();

   int b = r.nextInt(100);

   guessNum(b);

}

public static void guessNum(int b){

   Scanner sc = new Scanner(System.in);

   while (true){

      int a= sc.nextInt();

      if (a<b){

         System.out.println("你猜的数太小了");

         System.out.println("请继续输入!");

      }else if (a>b){

         System.out.println("你猜的数太大了");

         System.out.println("请继续输入!");

      }else{

         System.out.println("恭喜您猜对了");

         break;

      }

   }

}

}

原文地址:https://www.cnblogs.com/aohongzhu/p/12938752.html