学习笔记,持续更新!!!
2019.04.30
1.进程和线程的概念
进程:进程是操作系统结构的基础,是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上运行的过程,它是系统进行志愿分配和调度的一个独立单位。
线程:线程可以理解成是在进程中独立执行的子任务。比如,QQ.exe运行时就有很多的子任务同时运行:好友视频线程、下载文件线程、传输数据线程。
2.java实现多线程有两种方法(1)继承extends类(2)实现Runnable接口(3)实现Callable接口,由于java语言只能单继承,所以建议使用第二种方法
一个小例子:
public class MyThread implements Runnable{ @Override public void run() { System.out.println("running........."); } } public class test { @Test public void demo1(){ Runnable runnable = new MyThread(); Thread thread = new Thread(runnable); thread.start(); System.out.println("Ending.........."); } }
运行结果:
从上图的运行结果来看,说明使用多线程技术时,代码运行的结果与代码执行顺序或调用顺序无关(异步执行)。
异步执行:执行语句结束的顺序与语句执行开始的顺序并不一定相同。
3.几个常用方法
(1)currentThread()方法
currentThread()方法可返回代码段正在被哪个线程调用的信息。
(2)isAlive()方法
isAlive()的功能是判断当前的线程是否处于活动状态。
(3)sleep()方法
sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。
sleep()与wait()的区别:
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
(4)getId()方法
getId()方法是取得线程的唯一标识。
(5)interrupt()方法
interrupt()方法的使用效果并不像for+break语句那样,马上就停止循环。调用interrupt()方法仅仅是在当前线程打了一个停止的标记,并不是真的停止线程。(Thread类中的方法)
interrupted()方法:测试当前线程是否已经中断,执行后将状态标志设置为false。(Thread类中的方法)
isInterrupted()方法:测试线程Thread对象是否已经中断,但不清楚状态标志。(Thread类中的方法)
那么如何让线程停止呢——异常法
public class MyThread extends Thread{ @Override public void run() { try{ for(int i=0; i<500000; i++){ if(this.isInterrupted()){ System.out.println("已经是停止状态!"); throw new InterruptedException(); } System.out.println("i=" + (i+1)); } } catch(InterruptedException e){ System.out.println("进MyThread.java类run方法中的catch了!"); e.printStackTrace(); } } } public class test { @Test public void demo1(){ try{ MyThread thread = new MyThread(); thread.start(); thread.sleep(200); thread.interrupt(); }catch(Exception e){ System.out.println("main catch"); e.printStackTrace(); } } }
测试结果:
还可以使用interrupt()方法与return结合使用实现线程停止,不过还是建议使用“抛异常”的方法,因为catch快中还可以将异常向上抛,使线程停止的事件得以传播。
如果线程在sleep状态下停止线程,会进入catch语句,并且清除停止值,使之变成false.
(6)stop()方法
stop()方法已过时,这种方法会强行停止线程,可能使一些清理性的工作得不到完成,导致线程不安全。
(7)suspend与resume方法
suspend()方法暂停线程
resume()方法恢复线程执行
(8)yield()方法
yield()方法的作用是放弃当前的cpu资源,将挞让给其他的任务去占用cpu执行时间。但放弃的时间不缺定,有可能刚刚放弃,马上又获得cpu时间。
(9)setPriority()方法
setPriority()方法设置线程优先级,线程的优先级分为1~10.
2019.05.01
1.非线程安全
非线程安全,就是在多个线程对同一个对象中的实例变量进行访问时发生,产生的后果就是“读脏数据”。
2.synchronized关键字
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁,哪个线程先执行带synchronized关键的方法,哪个线程就持有该方法对象的锁,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。
一些结论:
(1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型方法。
(2)A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。
(3)synchronized关键字拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请此次对象锁时是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块 的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
(4)可重入锁:自己可以再次获取自己内部的内部锁。当存在父子类继承时,子类完全可以通过可重入锁,调用父类的同步方法。
(5)当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
(6)同步不可以被继承。
用关键字synchronized声明方法时有弊端的,比较耗时,所以这里我们使用synchronized同步代码块
public class Task { private String getData1; private String getData2; public void doLongTimeTask(){ try{ System.out.println("begin task"); //Thread.sleep(3000); String privateGetData1 = "长时间处理后从远程返回的值1 threadName=" + Thread.currentThread().getName(); String privateGetData2 = "长时间处理后从远程返回的值2 threadName=" + Thread.currentThread().getName(); synchronized(this){ getData1 = privateGetData1; getData2 = privateGetData2; } System.out.println(getData1); System.out.println(getData2); System.out.println("end task"); }catch(Exception e){ e.printStackTrace(); } } } public class MyThreadA extends Thread{ private Task task; public MyThreadA(Task task){ super(); this.task = task; } @Override public void run() { try{ super.run(); task.doLongTimeTask(); } catch(Exception e){ System.out.println("进MyThread.java类run方法中的catch了!"); e.printStackTrace(); } } } public class MyThreadB extends Thread{ private Task task; public MyThreadB(Task task){ super(); this.task = task; } @Override public void run() { try{ super.run(); task.doLongTimeTask(); } catch(Exception e){ System.out.println("进MyThread.java类run方法中的catch了!"); e.printStackTrace(); } } } public class test { @Test public void demo1(){ try{ Task task = new Task(); MyThreadA thread1 = new MyThreadA(task); thread1.setName("a"); thread1.start(); MyThreadB thread2 = new MyThreadB(task); thread2.setName("b"); thread2.start(); }catch(Exception e){ System.out.println("main catch"); e.printStackTrace(); } } }
一些结论:
(1)在使用synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将 被阻塞,这说明synchronized使用的“对象监听器”是一个。
(2)synchronized(this)同步代码块
对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
(3)synchronized(非this对象x)同步代码块
在多线程持有“对象监视器”为同一对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
当持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
(4)synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁,Class锁可以对类的所有对象实例起作用。
3.多线程的死锁
java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中,“死锁”是必须避免的,因为这会造成线程的“假死”。
可以使用JDK自带的工具来监测是否有死锁的现象,首先进入CMD工具,在进入JDK的安装文件夹的bin目录,执行jsp命令,得到运行线程的id值,再执行jstack命令。
2019.05.02
1.volatile关键字
关键字volatile的主要作用是使变量在多个线程间可见,它可以强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
使用volatile关键字增加了实例变量在多线程之间的可见性,但volatile关键字最致命的缺点是不支持原子性,对于多个线程访问同一个实例变量还是需要加锁同步。
synchronized和volatile之间比较:
(1)关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发 布,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率比较大
(2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
(3)volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据同步。
(4)关键字volatile解决的是变量在多线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
2019.05.03
1.线程间通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理过程中进行有效的把控与监督。
2.等待/通知机制的实现
(1)wait()方法
方法wait()的作用的是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步代码块中调用wait()方法。在执行调用wait()方法后,当前线程释放锁。
(2)notify方法
方法notify()也要在同步方法或同步代码块中调用,即在调用前,线程必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出异常。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait()状态的线程,对其发出notify通知,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程执行完,,也就是退出synchronized代码块后,当前线程才会释放锁。
(3)notifyAll方法
方法notifyAll()可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的哪个线程最先执行,但也有可能是随机执行,因为这要取决于JVM虚拟机的实现。
(4)一些结论
- wait使线程停止运行,而notify使停止的线程继续运行。
- 每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪对列存储了将要获得锁的线程,阻塞队列存储被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
- 当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。
- 调用notify()方法一次只随机通知一个线程进行唤醒。
- 带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。
3.通过管道进行线程间通信
(1)字节流:PipedInputStream和PipedOutputStream
(2)字符流:PipedReader和PipedWriter
(3)使用代码inputStream.connect(outputStream)或outputStream.connect(inputStream)的作用使两个Stream之间产生通信链接,这样才可以将数据进行输出于输入。
4.join()方法
(1)join()的作用使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。
(2)在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。此时,如果主线程想等待子线程执行完之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法。
(3)在join过程中,如果当前线程对象被中断,则当前线程出现异常。
(4)join(long)方法中的参数是设定等待时间
(5)方法join(long)与sleep(long)的区别:join(long)方法的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有施放锁的特点;而Thread.sleep(long)方法却不释放锁。
5.ThreadLocal类
ThreadLocal类可以比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。
6.InheritableThreadLocal类
使用InheritableLocal类可以在子线程中取得父线程继承下来的值。
2019.05.04
1.ReentrantLock类
在JDK1.5中新增加了ReentrantLock类,它可以达到synchronized同样的效果,并且在扩展功能上也更加强大。
使用ReentrantLock实现同步
public class MyService { private Lock lock = new ReentrantLock(); public void testMethod(){ lock.lock(); for(int i=0;i<5;i++){ System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1))); } lock.unlock(); } } public class MyThread extends Thread{ private MyService service; public MyThread(MyService service){ super(); this.service = service; } @Override public void run(){ service.testMethod(); } } public class Run { public static void main(String[] args) { MyService service = new MyService(); MyThread a1 = new MyThread(service); MyThread a2 = new MyThread(service); MyThread a3 = new MyThread(service); MyThread a4 = new MyThread(service); MyThread a5 = new MyThread(service); a1.start(); a2.start(); a3.start(); a4.start(); a5.start(); } }
从运行结果来看,当前线程打印完毕之后将锁进行释放,其他线程才可以继续打印。线程打印的数据是分组打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。
调用lock.lock()代码的线程就持有了“对象监视器”,其他线程只有等待锁被释放时再次争抢。
2.Condition类
Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
- Object类中的wait()方法相当于Condition类中的await()方法
- Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)方法
- Object类中的notify()方法相当于Condition类中的signal()方法
- Object类中的notifyAll()方法相当于Condition类中的signalAll()方法
使用多个Condition实现通知部分线程:
public class MyService { private Lock lock = new ReentrantLock(); public Condition conditionA = lock.newCondition(); public Condition conditionB = lock.newCondition(); public void awaitA(){ try{ lock.lock(); System.out.println("begin awaitA时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionA.await(); System.out.println(" end awaitA时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void awaitB(){ try{ lock.lock(); System.out.println("begin awaitB时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionB.await(); System.out.println(" end awaitB时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } } public void signalAll_A(){ try{ lock.lock(); System.out.println(" signalAll_A时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionA.signalAll(); }finally{ lock.unlock(); } } public void signalAll_B(){ try{ lock.lock(); System.out.println(" signalAll_B时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionB.signalAll(); }finally{ lock.unlock(); } } } public class MyThreadA extends Thread{ private MyService service; public MyThreadA(MyService service){ super(); this.service = service; } @Override public void run() { try{ super.run(); service.awaitA(); } catch(Exception e){ e.printStackTrace(); } } } public class MyThreadB extends Thread{ private MyService service; public MyThreadB(MyService service){ super(); this.service = service; } @Override public void run() { try{ super.run(); service.awaitB(); } catch(Exception e){ e.printStackTrace(); } } } public class test { @Test public void demo1(){ try{ MyService service = new MyService(); MyThreadA thread1 = new MyThreadA(service); thread1.setName("A"); thread1.start(); MyThreadB thread2 = new MyThreadB(service); thread2.setName("B"); thread2.start(); Thread.sleep(3000); service.signalAll_A(); }catch(Exception e){ System.out.println("main catch"); e.printStackTrace(); } } }
3.公平锁与非公平锁
private ReentrantLock lock = new ReentrantLock(isFair)
isFair=true:线程顺序执行
isFair=false:线程随机获得锁
4.getHoldCount()方法
int getHoldCount()的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
5.getQueueLength()
int getQueueLength()的作用是返回正等待获取此锁定的线程估计数,比如五个线程,一个线程首先执行await()方法,那么在调用getQueueLength()方法后返回值是4,说明有4个线程同时在等待lock释放。
6.getWaitQueueLength(Condition condition)
int getWaitQueueLength(Condition condition)的作用是返回等待与此锁定相关的给定条件Condition的线程估计数,比如有5个线程,每个线程都执行同一个condition对象的await()方法,则调用getWaitQueueLength(Condition condition)方法时返回的int值是5。
7.hasQueueThread()
boolean hasQueueThread(Thread thread)的作用是查询指定的线程是否正在等待获取此锁定。
8.hasQueueThreads()
boolean hasQueueThreads()的作用是查询是否有线程正在等待获取此锁定。
9.hasWaiters()
boolean hasWaiters(Condition condition)的作用是查询是否有线程正在等待与锁定有关的condition条件。
10.isFair()
boolean isFair的作用是判断是不是公平锁。
11.isHeldByCurrentThread()
boolean isHeldCurrentThread()的作用是查询当前线程是否保持此锁定。
12.isLocked()
boolean isLocked()的作用是查询此锁定是否由任意线程保持。
13.lockInterruptibly()
void lockInterruptibly()的作用是如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
14.tryLock()
boolean tryLock()的作用是仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。
15.tryLock(long timeout,TimeUnit unit)
boolean tryLock(long timeout,TimeUnit unit)的作用是,如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
16.awaitUninterruptibly()
17.awaitUntil()
2019.05.05
1.ReentrantReadWriteLock类
readLock().lock/unlock() writeLock().lock/unlock()
类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却非常地下。所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁ReentrantReadWriteLock来提升该方法的代码运行速度。
读写锁表示也有两个锁,一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取读锁,而进行写入操作的Thread只有在获取写锁后才能进行写操作。即多个Thread可以同时进程读取操作,但是同一时刻只运行一个Thread进行写入操作。
2.定时器Timer的使用
在JDK库中Timer类主要负责计划任务的功能,也就是在指定的时间开始执行某一任务。Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类,执行计划任务的代码要放入TimerTask的子类中,因为TimerTask是一个抽象类。
(1)schedule(TimerTask task,Date time)
该方法的作用是在指定的日期执行一次某任务
public class Run1TimerIsDaemon { //true开启守护线程 private static Timer timer = new Timer(); static public class MyTask extends TimerTask{ @Override public void run(){ System.out.println("运行了!时间为:" + new Date()); } } public static void main(String[] args) { try{ MyTask task = new MyTask(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dataString = "2019-5-5 15:39:00"; Date dateRef = sdf.parse(dataString); System.out.println("字符串时间:" + dateRef.toLocaleString() + "当前时间:" + new Date().toLocaleString()); timer.schedule(task, dateRef); }catch(Exception e){ e.printStackTrace(); } } }
运行结果:
TimeTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期时间不一致,因为前面的任务有可能消耗的时间较长,则后面的任务运行的时间也会被延迟
(2)schedule(TimerTask task,Date firstTime,long period)
该方法的作用是在指定的日期之后,按指定的间隔周期性地无限循环地执行某一任务。
(3)TimerTask类地cancel()方法
TimerTask类中的cancel()方法的作用是将自身从任务队列中清楚
(4)Timer类的cancel()方法
Timer类中的cancel()方法的作用是将任务队列中的全部任务清空。
注意事项:Timer类中的cancel()方法有时并不一定会停止执行任务,而是正常执行。这是因为Timer类中的cancel()方法有时并没有争抢到queue锁,所以TimerTask类中的任务继续正常执行。
(5)schedule(TimerTask task,long delay)
该方法的作用是以执行schedule(TimerTask task,long delay)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务。
(6)schedule(TimerTask task,long delay,long period)
该方法的作用是以执行schedule(TimerTask task,long delay,long period)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一任务。
(7)scheduleAtFixedRate(TimerTask task,Date firstTime,long period)和schedule()
schedule方法和scheduleAtFixedRate方法都会按顺序执行,所以不要考虑非线程安全的情况。
schedule方法和scheduleAtFixedRate方法主要的区别只在于不延时的情况:
使用schedule方法:如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的“开始”时的时间来计算。
使用scheduleAtFixedRate方法:如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的“结束”时的时间来计算。
延时的情况没有区别,也就是使用schedule方法和scheduleAtFixedRate方法都是如果执行任务的时间被延迟,那么下一次任务的执行时间参考上一次任务“结束”时的时间来计算。
schedule方法不具有追赶执行性
scheduleAtFixedRate方法具有追赶执行性
3.单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
4.立即加载/饿汉式
立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接new实例化。
5.延迟加载/懒汉式
延迟加载就是再调用get()方法时实例才被创建,常见的实现办法就是在get()方法中进行new实例化。
6.DCL双检查锁机制解决(延迟加载+多线程)无法满足单例模式的问题
public class MyObject { private volatile static MyObject myObject; private MyObject(){ } public static MyObject getInstance(){ try{ if(myObject==null){ Thread.sleep(3000); synchronized(MyObject.class){ if(myObject == null){ myObject = new MyObject(); } } } }catch(Exception e){ e.printStackTrace(); } return myObject; } } public class MyThread extends Thread{ @Override public void run(){ System.out.println(MyObject.getInstance().hashCode()); } } public class Run { public static void main(String[] args) { MyThread a1 = new MyThread(); MyThread a2 = new MyThread(); MyThread a3 = new MyThread(); a1.start(); a2.start(); a3.start(); } }
运行结果:
使用双重检查锁功能,成功解决了延迟加载遇到多线程的问题。DCL也是大多数多线程结合单例模式使用的解决方案。
7.使用静态内置类实现单例模式
public class MyObject { private MyObject(){ } private static class MyObjectHandler { private static MyObject myObject = new MyObject(); } public static MyObject getInstance(){ return MyObjectHandler.myObject; } }
8.序列化与反序列化的单例模式实现
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
静态内置类可以达到线程安全问题,但遇到序列话对象,使用默认的方式运行得到的结果还是多例的,解决办法就是在反序列化中使用readResolve()方法。
9.使用static代码块实现单例模式
静态代码块随着类的加载而加载,所以可以用来实现单例设计模式。
public class MyObject { private static MyObject instance = null; private MyObject(){ } static{ instance = new MyObject(); } public static MyObject getInatance(){ return instance; } }
10.使用enum枚举数据类型实现单例模式
枚举enum和静态代码块的特性相识,在使用枚举类时,构造方法会被自动调用,也可以应用其这个特性实现单例设计模式。
目前最为安全的实现单例的方法是通过内部静态enum的方法来实现,因为JVM会保证enum不能被反射并且构造器方法只执行一次。
/** * 使用枚举的单例模式 */ public class EnumSingleton{ private EnumSingleton(){} public static EnumSingleton getInstance(){ return Singleton.INSTANCE.getInstance(); } private static enum Singleton{ INSTANCE; private EnumSingleton singleton; //JVM会保证此方法绝对只调用一次 private Singleton(){ singleton = new EnumSingleton(); } public EnumSingleton getInstance(){ return singleton; } } }
2019.05.06
1.线程的状态
线程对象在不同运行时间有不同的状态,状态信息就存在于State枚举类中,可以通过Thread.currentThread().getState()查看。
(1)NEW状态是线程实例化后还从未执行start()方法时的状态
(2)RUNNABLE状态是线程进入运行的状态
(3)TERMINATED是线程被摧毁时的状态
(4)TIMED_WAITING代表线程执行了Thread.sleep()方法,呈等待状态,等待时间到达,继续向下运行
(5)BLOCKED状态出现在某一个线程在等待锁的时候
(6)WAITING是线程执行了Object.wait()方法后所处的状态
2.线程组
可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程。线程组的作用是,可以批量的管理线程或线程组对象,有效地对线程或线程组对象进行组织。
线程对象关联线程组:
(1)1级关联
所谓地1级关联就是父对象中有子对象,但并不创建子孙对象。这种情况经常出现在开发中,比如创建一些线程时,为了有效地对这些线程进行组织管理,通常情况下是创建一个线程组,然后 再将部分线程归属到该组中。这样地处理可以对零散地线程对象进行有效地组织与规划。
public class MyThreadA extends Thread{ @Override public void run() { try{ while(!Thread.currentThread().isInterrupted()){ System.out.println("ThreadName=" + Thread.currentThread().getName()); Thread.sleep(3000); } } catch(Exception e){ e.printStackTrace(); } } } public class MyThreadB extends Thread{ @Override public void run() { try{ while(!Thread.currentThread().isInterrupted()){ System.out.println("ThreadName=" + Thread.currentThread().getName()); Thread.sleep(3000); } } catch(Exception e){ e.printStackTrace(); } } } public class Run { public static void main(String[] args) { MyThreadA aRunnable = new MyThreadA(); MyThreadB bRunnable = new MyThreadB(); ThreadGroup group = new ThreadGroup("$$$"); Thread a = new Thread(group,aRunnable); Thread b = new Thread(group,bRunnable); a.start(); b.start(); System.out.println("活动地线程数为:" + group.activeCount()); System.out.println("线程组地名称为:" + group.getName()); } }
运行效果:
(2)多级关联
所谓地多级关联就是父对象中有子对象,子对象中再创建子对象,也就是出现子孙对象地效果了。但是此种写法再开发中不太常见,如果线程树结构设计得非常复杂反而不利于线程对象得管 理,但JDK却提供了支持多级关联得线程树结构。
(3)线程组自动归属特性
在实例化一个ThreadGroup线程组x时如果不指定所属得线程组,则x线程组自动归到当前线程对象所属得线程组中,也就是隐式地在一个线程组中添加了一个子线程组。
(4)获取根线程组
Thread.currentThread().getThreadGroup().getParent().getName();
(5)线程组里加线程组
(6)组内的线程批量停止
通过将线程归属到线程组中,当调用线程组ThreadGroup的interrupt()方法时,可以将该组中的所有正在运行的线程批量停止。
3.SimpleDateFormat非线程安全
类SimpleDateFormat主要负责日期的转换与格式化,但在多数的环境中,使用此类容易造成数据转换及处理的不准确,因为SimpleDateFormat类并不是线程安全的。
4.线程中出现异常的处理
在java多线程技术中,可以对多线程中的异常进行“捕捉”,使用的是UncaughtExceptionHandler类,从而可以对发生的异常进行有效的处理。
方法setUncaughtExceptionHandler()的作用是对指定的线程对象设置默认的异常处理器。
在Thread类中还可以使用setDefaultUncaughtExceptionHandler()对所指定线程类的所有线程对象设置异常处理器。
2019.5.14
Synchronized作用于整个方法的写法。
写法一:
public synchronized void method() { // todo }
写法二:
public void method() { synchronized(this) { // todo } }
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁了整个方法时的内容。
this指的是当前对象,如果不同对象调用线程仍然是同步的