Java学习笔记之多线程 2

时间:2023-02-26 13:13:47

多线程学习笔记二

一、线程间通信

线程间通信,其实就是多个线程在操作同一个资源,但是操作的动作不同。

1.1等待唤醒机制:

测试案例1:生产者生产一部手机,消费者就购买一部手机

//测试类

public class PhoneTest {

    public static void main(String[] args) {

       //手机对象(锁对象)

       Phone p = new Phone();

       //生产者

       SetPhone set = new SetPhone(p);

       //消费者

       GetPhone get = new GetPhone(p);

       //创建线程对象

       Thread setThread = new Thread(set,"生产者");

       Thread getThread = new Thread(get,"消费者");

       //启动线程

       setThread.start();

       getThread.start();

    }

}

//手机类:共享资源

public class Phone {

    private Stringbrand;

    private Stringcolor;

    private boolean isNewPhone = false;//默认没有生产手机

    //生产手机函数,同步函数的锁是this

    public synchronized void set(String brand, String color) {

       //是否有新手机

       if (this.isNewPhone) {

           try {

              this.wait();//锁对象调用Object类中的wait方法,让生产者处于等待状态注意:锁对象可以是任意对象

           } catch (InterruptedExceptione) {

              e.printStackTrace();

           }

       }

       //如果到了当前代码位置,说明没有新手机,要生产新手机

       this.brand =brand;

       this.color =color;

       //模拟生产手机的耗时操作

       try {

           Thread.sleep(1000);

       } catch(InterruptedExceptione) {

           e.printStackTrace();

       }

       //打印生产后的信息

       System.out.print(Thread.currentThread().getName());//生产者线程名称

       System.out.println("生产了:" +this.brand + "---" + this.color);

       //手机生产完成,把手机状态更新为有新手机 true

       this.isNewPhone =true;

       //唤醒消费者,有新手机了,可以购买

       this.notify();

    }

    //购买手机函数,同步函数锁是this

    public synchronized void get() {

       //判断是否有新手机

       if (!this.isNewPhone) {

           try {

              this.wait();//没有手机,消费者处于等待状态

           } catch(InterruptedExceptione) {

              e.printStackTrace();

           }

       }

       //执行到了当前代码,说明目前有手机了

       //购买手机

       try {

           Thread.sleep(1000);

       } catch(InterruptedExceptione) {

           e.printStackTrace();

       }

       //打印购买后的信息

       System.out.print(Thread.currentThread().getName());//消费者

       System.out.println("购买了:" +this.brand +"---" +this.color);

       //手机被买走了,更新手机的状态,false

       this.isNewPhone =false;

       //唤醒生产者,该生产新手机了

       this.notify();

    }

}

//生产者类

public class SetPhone implements Runnable {

    Phone phone ;

    int num = 0;//线程体中的代码切换

    //构造函数

    public SetPhone(Phonep) {

       phone = p;

    }

    //重写run方法

    public void run() {

       while (true) {

           if (num %2 == 0) {//线程体切换条件

              phone.set("小米4","蓝色");//生产小米手机

           } else {

              phone.set("iPhone6","黄色");//生产苹果手机

           }

           num++;

       }

    }

}

//消费者类

public class GetPhone implements Runnable {

    Phone phone;

    //构造函数

    public GetPhone(Phonep) {

       phone = p;

    }

    //重写run方法

    public void run() {

       while (true) {

           phone.get();//消费手机

       }

    }

}

输出结果:

生产者生产了:小米4---蓝色

消费者购买了:小米4---蓝色

生产者生产了:iPhone6---黄色

消费者购买了:iPhone6---黄色

...

由输出结果可知:生产者生产了一部手机,就通知消费者来买手机,在等待消费者买手机的过程中,生产者停止生产,处于等待状态;消费者收到通知后,便来买手机,买完手机后也通知生产者没手机了,该生产手机了,于是消费者停止购买,处于等待状态,等待生产者生产手机...依此循环下去。程序执行过程如图1所示。

Java学习笔记之多线程 2

图1

注意:本例是在只有2个线程的理想情况下,实际中可能会有多个线程。

测试案例2:生产者与消费者升级版,JDK1.5中的新特性来处理,多个生产者,多个消费者的同步问题。

//测试类

classProducerConsumerDemo {

    public static void main(String[] args) {

       //创建共享资源对象

       Resource r = new Resource();

       //生产者对象

       Producer pro = new Producer(r);

       //消费者对象

       Consumer con = new Consumer(r);

       //创建线程对象

       Thread t1 = new Thread(pro);//生产者1

       Thread t2 = new Thread(pro);//生产者2

       Thread t3 = new Thread(con);//消费者1

       Thread t4 = new Thread(con);//消费者2

       //开启线程

       t1.start();

       t2.start();

       t3.start();

       t4.start();

    }

}

//JDK1.5中提供了多线程升级解决方案。

//共享资源

classResource {

    private String name;

    private int count = 1;

    private boolean flag = false;

    private Lock lock = new ReentrantLock();//创建lock对象

    //生产者锁对象

    private Condition condition_pro =lock.newCondition();

    //消费者锁对象

    private Condition condition_con =lock.newCondition();

    //生产商品函数

    public void set(String name)throws InterruptedException {

       lock.lock();//上锁

       try {

           while(flag) {//有商品

               condition_pro.await();//等待,不生产

           }

            // 执行到当前代码说明没有商品,开始生产商品

           this.name =name+"--"+count++;

            //模拟生产手机的耗时操作

           try {

              Thread.sleep(500);

           }catch(InterruptedException e){

              e.printStackTrace();

           }

            //打印生产后的信息

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

                //产品生产完成,把产品状态更新为有新产品:true

           flag = true;

            //唤醒消费者,有新产品,可以购买了

           condition_con.signal();

       }

       Finally {

           lock.unlock();//解锁,释放锁的动作一定要执行。

       }

    }

    //消费商品函数

    public void out()throws InterruptedException {

       lock.lock();  //上锁

       try {

           while(!flag){//判断是否有新产品

              condition_con.await();//没有新产品,消费者处于等待状态

           }

            //执行到了当前代码,说明目前有产品,可以购买了

            //模拟购买产品的耗时动作

           try {

              Thread.sleep(500);

           }catch(InterruptedException e){

              e.printStackTrace();

           }

            //打印购买后的信息

           System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);

  //产品被买走了,更新产品的状态,false

           flag = false;

            //唤醒生产者,该生产新产品了

           condition_pro.signal();

       }

       Finally {

           lock.unlock();//解锁

       }

    }

}

//生产者类

classProducer implements Runnable {

    private Resource res;

    Producer(Resource res) {

       this.res = res;

    }

    //重写run方法

    public void run() {

       while(true) {

           try{

              res.set("+商品+");

           }

           catch (InterruptedException e) {

               //不处理

}

       }

    }

}

//消费者类

classConsumer implements Runnable {

    private Resource res;

    Consumer(Resource res) {

       this.res = res;

    }

    public void run() {

       while(true) {

           try {

              res.out();

           } catch (InterruptedException e)   {

              //不处理异常

           }

       }

    }

}

输出结果

Thread-0...生产者..+商品+--1

Thread-2...消费者.........+商品+--1

Thread-1...生产者..+商品+--2

Thread-3...消费者.........+商品+--2

Thread-0...生产者..+商品+--3

Thread-2...消费者.........+商品+--3

Thread-1...生产者..+商品+--4

Thread-3...消费者.........+商品+--4

...

二、Object类中和线程相关的方法

public final void wait()//在其他线程调用此对象的notify()方法或 notifyAll()方法前,导致当前线程等待

public final void wait(long timeout)//单位:ms

//在其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。该方法等价于Thread类中sleep(long timeout)

public final void wait(long timeout,int nanos)//在其他线程调用此对象的notify() 方法或 notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。该方法等价于 Thread类中sleep(longtimeout,int nanos)

public final void notify()//唤醒在此对象监视器(锁对象)上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的 

publicfinal void notifyAll()       //唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

sleep()与 wait()的区别,notify()的作用?

sleep():让线程处于暂时休眠状态,当时间到达后,自动醒来运行线程对象。注意:在线程处理休眠状态的过程中,不会释放锁对象,会释放CPU执行权

wait():让线程处于等待状态,需要被其他线程对象,通过notify()或者notifyAll()进行唤醒,醒来后运行线程对象。  注意:在线程处理等待状态的过程中,会释放锁对象,会释放CPU执行权。

notify():唤醒处于等待状态的线程。 注意:当把一个等待状态的线程唤醒后,不会立刻执行该线程,会先获取锁对象,获取到锁对象和CPU执行权后,才会执行线程。

三、线程其他操作

1、停止线程

停止线程其实只有一种方法,就是让run方法结束。让run方法结束有一下3种方式:

1.1、定义循环结束标记,因为线程运行代码一般都是循环,只要控制了循环即可。注意:特殊情况,当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。

1.2、使用interrupt(中断)方法,该方法让处于冻结状态的线程,强制回到运行状态中来。

1.3、Thread类中的stop方法,此方法已经过时不再使用。

    测试案例3

class StopThread implements Runnable {

    private booleanflag = true;

    publicsynchronized void run() {

       while(flag) {

           try{

              wait();//当线程处于了冻结状态,就不会读取到标志位。那么线程就不会结束

           }catch(InterruptedExceptione){

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

              flag =false;//线程被强制醒来后,可以操作标记让线程结束

           }

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

       }

    }

    public voidchangeFlag() {

       flag = false;

    }

}

//测试类

class StopThreadDemo {

    public staticvoid main(String[] args) {

       //创建自定义类对象

       StopThread st= new StopThread();

       //创建自定义类对象

       Thread t1 =new Thread(st);

       Thread t2 =new Thread(st);

       //创建线程对象

       t1.setDaemon(true);

       t2.setDaemon(true);

       //开启线程

       t1.start();

       t2.start();

       int num = 0;

       while(true) {

           if(num++== 60) {

              t1.interrupt();//把处于冻结状态的线程强制恢复到运行态

              t2.interrupt();

              break; //退出死循环

           }

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

       }

       System.out.println("mainover");//主线程执行完毕标志

    }

}

输出结果:

main.......58

main.......59

main.......60

main over

Thread-0....Exception

Thread-0....run

Thread-1....Exception

Thread-1....run

2、守护线程

    简介:相当于后台线程,任然会和前台线程(例如主线程)抢夺CPU资源(具备执行权),但是,当所有的前台线程都结束后,后台线程会自动结束。后台线程和前台线程有一种依赖关系。

public final void setDaemon(boolean on) // 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

public final boolean isDaemon() //测试该线程是否为守护线程

测试案例4

//1自定义类实现runnable接口

classStopThread implements Runnable {

    privateboolean flag =true;

    //2重写run方法

    public voidrun() {

       while(flag){

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

       }

    }

    public voidchangeFlag() {

       flag =false;

    }

}

//测试类

class StopThreadDemo {

    publicstatic void main(String[] args) {

       //3创建自定义类对象

       StopThreadst = new StopThread();

       //4创建线程对象

       Threadt1 = new Thread(st);

       Threadt2 = new Thread(st);

       //5设置为守护线程,必须在启动线程前完成

       t1.setDaemon(true);

       t2.setDaemon(true);

       //如果自定义线程都是守护线程,那么程序执行完主线程后,JVM虚拟机就直接退出,不再执行自定义线程了。

       //6启动线程

       t1.start();

       t2.start();

       int num= 0;

       while(true){

           if(num++== 60) {

              break;//退出死循环

           }

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

       }

       System.out.println("mainover");//主线程执行完毕标志

    }

}

输出结果:

main.......59

Thread-1....run

main.......60

Thread-0....run

main over         //主线程结束

Thread-1....run

Thread-0....run

请按任意键继续. . .

由输出结果可知:当主线程结束时,守护线程也随之结束。

3、Join方法

简介:当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。join可以用来临时加入线程执行。

public final void join()

测试案例5

//自定义类

class Demo implements Runnable{

    //重写run方法

    public void run() {

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

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

       }

    }

}

//测试类

class  JoinDemo {

    public static void main(String[] args) throws Exception {

       Demo d = new Demo();

       Thread t1 = new Thread(d);

       Thread t2 = new Thread(d);

       t1.start();

       t1.join();//会抛出异常,t1抢到主线程的CPU执行权开始执行(此时主线程处于冻结状态),等线程t1执行完毕后其他线程才能继续执行。也就是说主线程要等到t1挂掉后他才能运行。

       t2.start();

       //t1.join();//当t1在此处加入线程时,主线程处于冻结状态,线程t1,t2处于就绪态,那么t1,t2两个线程在CPU的切换下轮流执行。当t1执行完毕,不管t2是否执行完毕,主线程已经“活过来了”,此时主线程和t2处于抢夺cpu的状态,这两个线程在CPU的切换下执行直到所有线程都结束为止。

       //主线程

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

           System.out.println("main....."+x);

       }

       System.out.println("over");

    }

}

输出结果:略

4、Priority和yield方法

优先级表示抢夺CPU的频率。优先级从1-10,1级最低,10级最高,所有的线程的默认优先级都是5。

publicfinal void setPriority(int newPriority)//更改线程的优先级

publicstatic void yield()//暂停当前正在执行的线程对象,并执行其他线程。Yield:放弃

测试案例6

//自定义类实现runnable接口

class Demo implements Runnable {

    public void run() {

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

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

           //Thread.yield();//临时释放CPU执行权,减缓线程的运行,让线程都有机会平均运行的效果。

       }

    }

}

    //测试类

class JoinDemo {

        public static void main(String[] args) throws Exception {

           Demo d = new Demo();

           Thread t1 = new Thread(d);

           Thread t2 = new Thread(d);

           t1.start();

           //当数据是固定时就定义成常量,当数据是共享时就定义成static

           t1.setPriority(Thread.MAX_PRIORITY);//把t1设置为最高优先级

           t2.start();      

           //主线程

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

              //不操作

           }

           System.out.println("over");

        }

}  

四、匿名内部类方式使用多线程

方式1:

new Thread(){重写run方法...}.start();

方式2:

New Thread(new Runnable(){重写run方法...}).start();

测试案例7:

public class ThreadDemo {

    public static void main(String[] args) {

       //匿名内部类,Thread类的子类

       new Thread(){

           //Thread类的匿名子类对象

           public void run() {

              System.out.println("Thread匿名内部类对象");

           };

       }.start();

       //匿名内部类方式,实现Runnable接口,父类引用指向子类对象

       new Thread(new Runnable() {

           @Override

           public void run() {

              System.out.println("Runnable接口匿名内部类子类对象");

           }

       }).start();

    }

}

五、线程组

5.1线程组简介:Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。默认情况下,所有的线程都属于主线程组。从JDK1.0开始。

5.2 ThreadGroup构造方法

publicThreadGroup(Stringname)//构造一个新线程组。新线程组的父线程组是目前正在运行线程的线程组

publicThreadGroup(ThreadGroup parent,Stringname)//创建一个新线程组。新线程组的父线程组是指定的线程组

5.3 ThreadGroup常用方法

publicfinal StringgetName()//返回此线程组的名称

5.3Thread类有关线程组的构造方法

public Thread(ThreadGroupgroup, String name)

public Thread(ThreadGroup group, Runnable target)

public Thread(ThreadGroup group,Runnable target, String name)

//给线程设置分组。

5.4 Thread类有关线程组的常用方法

publicfinal ThreadGroup getThreadGroup()//返回该线程所属的线程组

5.5测试案例:

public class ThreadDemo {

    public static void main(String[] args) {

       MyThread t1 = new MyThread();

       MyThread t2 = new MyThread();

       t1.start();

       t2.start();

       //创建一个线程组

       MyThreadGroupgroup = new MyThreadGroup("javaEE");

       //创建一个新的线程对象,并把该线程对象设置为javaEE组的成员

       MyThread t3 = new MyThread(group,"se");

       t3.start();

    }

}

//线程类

public class MyThread extends Thread {

    public MyThread() {

       super();

    }

    publicMyThread(ThreadGroupgroup, Stringname) {

       super(group,name);

    }

    public void run() {

       System.out.println("当前线程的名字是:" + getName());

       //获取到线程组对象

       ThreadGroup threadGroup =this.getThreadGroup();

       //获取线程组的名字

       String name = threadGroup.getName();

       System.out.println("当前线程对象所在的组名是: " +name);

    }

}

//线程组类

public class MyThreadGroup extends ThreadGroup {

    publicMyThreadGroup(Stringname) {

       super(name);

    }

}

六、线程池

6.1简介:程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

在JDK5之前,必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。

JDK5新增了一个Executors(执行者)工厂类来产生线程池。

6.2 Executors类的构造方法:无

6.3 Executors类的常用方法:

publicstatic ExecutorService newCachedThreadPool()//创建一个具有高效缓存功能的线程池对象

publicstatic ExecutorService newFixedThreadPool(int nThreads)//创建一个可重用的线程池,具有固定(Fixed)线程数的线程池,n为1时也可以创建单个线程对象的线程池

publicstatic ExecutorService newSingleThreadExecutor()//创建单个线程对象的线程池对象。

这些方法的返回值是ExecutorService(线程池)对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。

ExecutorService接口常用方法:(JDK1.5)

注意:ExecutorService直译为“执行者服务”在java中叫线程池。

Future<?>submit(Runnable task)//提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。(submit:提交)

<T>Future<T> submit(Callable<T> task)// 提交一个 Runnable任务用于执行,并返回一个表示该任务的 Future。

Future<V>接口简介(jdk1.5)

Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。

6.4 测试案例

//1、自定义类,实现runnable接口

public class MyRunnable implements Runnable {

    //2、重写run方法,线程体

    public void run() {

       System.out.println("我是线程体");

       //获取线程名字

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

    }

}

//测试类

public class ThreadPool {

    public static void main(String[] args) {

       //3、用Executors类中的newFixedThreadPool()方法创建带有2个线程对象的线程池对象(用方法的返回值"创建"对象)

       ExecutorServiceservice = Executors.newFixedThreadPool(2);

       //4、将线程体对象当做参数传递给ExecutorService接口中的submit()方法,来启动线程,执行现场体。

       //submit()方法相当于Thread类中的start()方法.

       service.submit(new MyRunnable());

       service.submit(new MyRunnable());

    }

}

输出结果:

我是线程体

我是线程体

pool-1-thread-2

pool-1-thread-1

七、java多线程实现方式3,实现Callable接口

1 简介

通过线程池对象,实现Callable(随时可偿还的)接口。Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。从JDK1.5开始。

2构造方法:无

3 普通方法

V call()//计算结果,如果无法计算结果,则抛出一个异常.相当于runnable接口中的run方法。

4 创建线程的步骤
1) 自定义类实现Runnable接口
2) 重写call方法
3) 创建线程池对象
4) 实现线程池中的线程对象,来执行线程要执行的代码

5测试案例

//1、自定义类实现Callable接口

public class MyCallable implementsCallable{

    //2、重写call方法,相当于Runnable接口中的run()方法

    public Object call()throws Exception {

       System.out.println("创建线程的第三种方法,通过线程池对象实现Callable接口");

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

       return null;

    }

}

//测试类

public class ThreadTest {

    public static void main(String[] args) {

       //3、创建线程池对象

       ExecutorServiceservice = Executors.newFixedThreadPool(2);

       //4、实现线程池中的线程对象,来执行线程要执行的代码

       service.submit(new MyCallable());

       service.submit(new MyCallable());

    }

}

输出结果:

Java学习笔记之多线程 2

由输出结果可知:程序一直处于等待状态。

八、定时器:

1 简介:定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。

2 Timer类

一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。位于java.util包中。从JDK1.3开始。

Timer类构造方法:

public Timer()//创建一个新计时器

常用方法:

        publicvoid schedule(TimerTask task, long delay) //在指定的时间到达后,执行TimerTask中的run方法

        public void schedule(TimerTask task,longdelay,long period)//在指定的时间到达后,执行TimerTask中的run方法,并且以后每隔固定的时间重复执行

3 TimerTask类

是一个抽象类。由Timer 安排为一次执行或重复执行的任务,位于java.util包中,从JDK1.3开始。

TimerTask构造方法

protected TimerTask()//创建一个新的计时器任务

TimerTask常用方法

public abstract void run()//此计时器任务要执行的操作。

public boolean cancel()//取消此计时器任务。

备注:在android开发中Quartz是一个完全由java编写的开源调度框架。

4 测试案例:设置一个定时器,5秒钟后闹钟响起,以后每隔3秒响一次。

public class TimerDemo {

public static void main(String[] args) {

       //1、创建timer对象

       Timer timer = new Timer();

       //2、匿名方式创建TimerTask对象

       TimerTask task = new TimerTask() {

           //3、重写run()方法,准备好定时器Timer要执行的内容(任务)

           public void run() {

              System.out.println("除非你能在床上挣钱,否则就别赖在床上不起");

           }

       };

       //4、执行任务

       timer.schedule(task, 5000, 3000);

    }

}

九、设计模式

1概述

设计模式(Designpattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让带码更容易被他人理解、保证代码可靠性。

设计模式不是一种方法和技巧,而是一种思想。

设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,实现低耦合,高内聚。

2设计模式分类

创建型模式简单工程模式工厂方法模式抽象工厂模式,建造者模式,原型模式单例模式(6个)

行为型模式模板方法模式观察者模式,状态模式,职责链模式,命令模式,访问者模式,策略模式,备忘录模式,迭代器模式,解释器模式(10个)

结构型模式:外观模式,适配器模式代理模式装饰模式,桥接模式,组合模式,享元模式(7个)

       备注:红色为实际编程中常用的设计模式

3单例设计模式

概述:在内存中一个类只能有一个实例对象。

分类:

饿汉式:创建步骤 1、构造方法私有,2、在本类中创建本类对象,3、对外提供公共访问方法,用来获取当前类对象。

        懒汉式:创建步骤1、构造方法私有,2、在本类中创建本类的对象引用,而不是创建对象,3、对外提供公共访问方法,用来获取当前类对象,3.1如果是第一次访问方法,完成对象的创建,3.2如果不是第一次访问对象,返回创建好的对象。

    测试案例1:创建饿汉式单例设计模式

    //饿汉式

public class Single {

    //1、构造方法私有

    private Single(){} 

    //2、在本类中,创建本类对象

    private static Single s = new Single();

    //3、对外提供公共访问方法,用来获取当前类对象

    public static SinglegetInstance(){

       returns;

    }

}

//测试类

public class Test {

    public static void main(String[] args) {

       //

       Single s1 = Single.getInstance();

       Single s2 = Single.getInstance();

       System.out.println(s1 ==s2);//判断对象引用s1、s2指向的地址是否相等

       System.out.println(s1);

       System.out.println(s2);

    }

}

输出结果:

true

Single@659e0bfd

Single@659e0bfd

由输出结果可知,s1、s2指向堆中同一块内存地址。

    测试案例2:创建懒汉式单例设计模式

    //懒汉式

public class Single2 {

    //构造方法私有

    private Single2(){}

    //在本类中,创建本类对象的引用,而不创建对象

    private static Single2 s = null;

    //对外提供公共访问方法,用来获取当前类对象

    public synchronized static Single2getInstance(){

       if (s ==null) {

           //t1, t2

           //如果是第一次访问方法:完成对象的创建

           s = new Single2();  

       }

       //如果不是第一次访问方法,返回创建好的对象

       returns;

    }

}

//测试类

public class Test2 {

    public static void main(String[] args) {

       Single2 s1 = Single2.getInstance();

       Single2 s2 = Single2.getInstance();

       System.out.println(s1 ==s2);

       System.out.println(s1);

    System.out.println(s2);

    }

}

输出结果

true

Single2@659e0bfd

Single2@659e0bfd

    由输出结果可知,s1、s2指向堆中同一块内存地址。

    注意:在多线程的情况下,懒汉式的会有线程安全问题,饿汉式没有线程安全问题。开发的时候用饿汉式。

十、多线程常见问题

1多线程有几种实现方案,分别是哪几种?

答:3种,

方式一:继承Thread类,重写run方法

方式二:实现Runnable接口,实现run方法

方式三:通过线程池,实现Callable接口,实现call()方法。

2同步有几种方式,分别是什么?

答:3种

方式一:同步代码块,锁对象是任意的

方式二:同步函数,普通同步函数的锁对象是this,静态同步函数的锁是类名.class

方式三:Lock锁

当一个程序有多个线程操作同一个共享数据时,要求线程对象所使用的是同一把锁对象。

3 启动一个线程是run()还是start()?它们的区别?

答:启动一个线程是start()方法,

run()方法是线程要执行的操作,线程体

start()方法启动线程,告诉JVM调用run()方法。

4 sleep()和wait()方法的区别?

答:sleep()方法是让线程休眠指定的时间,时间到后自动醒来。在线程休眠的过程中不会释放锁对象,但会释放CPU执行权。

wait()方法是让线程处于等待状态,当设置指定的时间,时间到后会自动醒来,如果没有设置等待时间,则必须由notify()或notifyAll()方法来唤醒。在线程等待的过程中会释放CPU的执行权和锁对象。

5 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

答:因为这些方法的调用,要使用当前线程中的锁对象来调用,而锁对象的类型是任意对象,所以,放在Object类中。