多线程学习笔记二
一、线程间通信
线程间通信,其实就是多个线程在操作同一个资源,但是操作的动作不同。
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所示。
图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, String name) {
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());
}
}
输出结果:
由输出结果可知:程序一直处于等待状态。
八、定时器:
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
由输出结果可知,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
由输出结果可知,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类中。