小白学java-多线程学习笔记

Java中实现多线程有几种方法

继承 Thread 类;
实现 Runnable 接口;
实现 Callable 接口通过 FutureTask 包装器来创建 Thread 线程;
使用 ExecutorService Callable Future 实现有返回结果的多线程(也就是使用了 ExecutorService 管理前面的三种方式)。
如何停止一个正在运行的线程
1 、使用退出标志,使线程正常退出,也就是当 run 方法完成后线程终止。
2 、使用 stop 方法强行终止,但是不推荐这个方法,因为 stop suspend resume 一样都是过期作废的
方法。
3 、使用 interrupt 方法中断线程。
 
class MyThread extends Thread {
volatile boolean stop = false ;
public void run () {
while ( ! stop ) {
System . out . println ( getName () + " is running" );
try {
sleep ( 1000 );
} catch ( InterruptedException e ) {
System . out . println ( "week up from blcok..." );
stop = true ; // 在异常处理代码中修改共享变量的状态
}
}
System . out . println ( getName () + " is exiting..." );
}
}
class InterruptThreadDemo3 {
public static void main ( String [] args ) throws InterruptedException {
MyThread m1 = new MyThread ();
System . out . println ( "Starting thread..." );
m1 . start ();
Thread . sleep ( 3000 );
System . out . println ( "Interrupt thread...: " + m1 . getName ());
m1 . stop = true ; // 设置共享变量为 true
m1 . interrupt (); // 阻塞时退出阻塞状态
Thread . sleep ( 3000 ); // 主线程休眠 3 秒以便观察线程 m1 的中断情况
System . out . println ( "Stopping application..." );
}
}

notify() notifyAll() 有什么区别?
notify 可能会导致死锁,而 notifyAll 则不会
任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行 synchronized 中的代码
使用 notifyall, 可以唤醒
所有处于 wait 状态的线程,使其重新进入锁的争夺队列中,而 notify 只能唤醒一个。
wait() 应配合 while 循环使用,不应使用 if ,务必在 wait() 调用前后都检查条件,如果不满足,必须调用
notify() 唤醒另外的线程来处理,自己继续 wait() 直至条件满足再往下执行。
class MyThread extends Thread {
volatile boolean stop = false ;
public void run () {
while ( ! stop ) {
System . out . println ( getName () + " is running" );
try {
sleep ( 1000 );
} catch ( InterruptedException e ) {
System . out . println ( "week up from blcok..." );
stop = true ; // 在异常处理代码中修改共享变量的状态
}
}
System . out . println ( getName () + " is exiting..." );
}
}
class InterruptThreadDemo3 {
public static void main ( String [] args ) throws InterruptedException {
MyThread m1 = new MyThread ();
System . out . println ( "Starting thread..." );
m1 . start ();
Thread . sleep ( 3000 );
System . out . println ( "Interrupt thread...: " + m1 . getName ());
m1 . stop = true ; // 设置共享变量为 true
m1 . interrupt (); // 阻塞时退出阻塞状态
Thread . sleep ( 3000 ); // 主线程休眠 3 秒以便观察线程 m1 的中断情况
System . out . println ( "Stopping application..." );
}
} notify() 是对 notifyAll() 的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死
锁。正确的场景应该是 WaitSet 中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果
唤醒的线程无法正确处理,务必确保继续 notify() 下一个线程,并且自身需要重新回到 WaitSet .

sleep() wait() 有什么区别?
对于 sleep() 方法,我们首先要知道该方法是属于 Thread 类中的。而 wait() 方法,则是属于 Object 类中的。
sleep() 方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当 指定的时间到了又会自动恢复运行状态。在调用sleep() 方法的过程中,线程不会释放对象锁。
当调用 wait() 方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
volatile 是什么 ? 可以保证有序性吗 ?
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语
义:
1 )保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile 关键字会强制将修改的值立即写入主存。
2 )禁止进行指令重排序。
volatile 不是原子性操作
什么叫保证部分有序性 ?
当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果 已经对后面的操作可见;在其后面的操作肯定还没有进行;
x = 2 ; // 语句 1
y = 0 ; // 语句 2
flag = true ; // 语句 3
x = 4 ; // 语句 4
y = - 1 ; // 语句 5
由于 flflag 变量为 volatile 变量,那么在进行指令重排序的过程的时候,不会将语句 3 放到语句 1 、语句 2 前 面,也不会讲语句3 放到语句 4 、语句 5 后面。但是要注意语句 1 和语句 2 的顺序、语句 4 和语句 5 的顺序是不作任何保证的。
使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁
Thread 类中的 start() run() 方法有什么区别?
start() 方法被用来启动新创建的线程,而且 start() 内部调用了 run() 方法,这和直接调用 run() 方法的效果不一样。当你调用run() 方法的时候,只会是在原来的线程中调用,没有新的线程启动, start() 方法才会启动新线程。
为什么 wait, notify notifyAll 这些方法不在 thread 类里面? 明显的原因是 JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需
要等待某些锁那么调用对象中的 wait() 方法就有意义了。如果 wait() 方法定义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait notify notifyAll 都是锁级别的操作,所以把他们定义在Object 类中因为锁属于对象。
为什么 wait notify 方法要在同步块中调用?
1. 只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的 wait(),notify() notifyAll() 方法。
2. 如果你不这么做,你的代码会抛出 IllegalMonitorStateException 异常。
3. 还有一个原因是为了避免 wait notify 之间产生竞态条件。
wait() 方法强制当前线程释放对象锁。这意味着在调用某对象的 wait() 方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait() 方法。 在调用对象的notify() notifyAll() 方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify() notifyAll() 方法。
调用 wait() 方法的原因通常是,调用线程希望某个特殊的状态 ( 或变量 ) 被设置之后再继续执行。调用
notify() notifyAll() 方法的原因通常是,调用线程希望告诉其他等待中的线程 :" 特殊状态已经被设置 " 。 这个状态作为线程间通信的通道,它必须是一个可变的共享状态( 或变量 )
Java interrupted isInterruptedd 方法的区别?
interrupted() isInterrupted() 的主要区别是前者会将中断状态清除而后者不会。 Java 多线程的中断机制是用内部标识来实现的,调用Thread.interrupt() 来中断一个线程就会设置中断标识为 true 。当中断线程调用静态方法Thread.interrupted() 来检查中断状态时,中断状态会被清零。而非静态方法 isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出
InterruptedException 异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
Java synchronized ReentrantLock 有什么不同?
相似点:
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行 线程阻塞和唤醒的代价是比较高的.
区别:
这两种方式最大区别就是对于 Synchronized 来说,它是 java 语言的关键字,是原生语法层面的互斥,需要jvm 实现。而 ReentrantLock 它是 JDK 1.5 之后提供的 API 层面的互斥锁,需要 lock() unlock() 方法配 合try/fifinally 语句块来完成。
Synchronized 进过编译,会在同步块的前后分别形成 monitorenter monitorexit 这个两个字节码指
令。在执行 monitorenter 指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1 ,相应的,在执行 monitorexit 指令时会将锁计算器就减 1 ,当计算器为0 时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。
由于 ReentrantLock java.util.concurrent 包下提供的一套互斥锁,相比 Synchronized
ReentrantLock 类提供了一些高级功能,主要有以下 3 项: 1. 等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于 Synchronized来说可以避免出现死锁的情况。
2. 公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁, Synchronized 锁非公平锁, ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数 true 设为公平锁,但公平锁表现的性 能不是很好。
3. 锁绑定多个条件,一个 ReentrantLock 对象可以同时绑定对个对象。
有三个线程 T1,T2,T3, 如何保证顺序执行?
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的 join() 方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3 调用 T2, T2 调用 T1) ,这样 T1 就会先完成而 T3 最后完成。
实际上先启动三个线程中哪一个都行,
因为在每个线程的 run 方法中用 join 方法限定了三个线程的执行顺序。
public class JoinTest2 {
// 1. 现在有 T1 T2 T3 三个线程,你怎样保证 T2 T1 执行完后执行, T3 T2 执行完后执行
public static void main ( String [] args ) {
final Thread t1 = new Thread ( new Runnable () {
@Override
public void run () {
System . out . println ( "t1" );
}
});
final Thread t2 = new Thread ( new Runnable () {
@Override
public void run () {
try {
// 引用 t1 线程,等待 t1 线程执行完
t1 . join ();
} catch ( InterruptedException e ) {
e . printStackTrace ();
}
System . out . println ( "t2" );
}
});
Thread t3 = new Thread ( new Runnable () {
@Override
public void run () {
try {
// 引用 t2 线程,等待 t2 线程执行完
t2 . join ();
} catch ( InterruptedException e ) {
e . printStackTrace ();
}
System . out . println ( "t3" );
}
SynchronizedMap ConcurrentHashMap 有什么区别?
SynchronizedMap() Hashtable 一样,实现上在调用 map 所有方法时,都对整个 map 进行同步。而
ConcurrentHashMap 的实现却更加精细,它对 map 中的所有桶加了锁。所以,只要有一个线程访问 map,其他线程就无法进入 map ,而如果一个线程在访问 ConcurrentHashMap 某个桶时,其他线程, 仍然可以对map 执行某些操作。
所以, ConcurrentHashMap 在性能以及安全性方面,明显比 Collections.synchronizedMap() 更加有优势。同时,同步操作精确控制到桶,这样,即使在遍历map 时,如果其他线程试图对 map 进行数据修改,也不会抛出ConcurrentModifificationException
什么是线程安全
线程安全就是说多线程访问同一代码,不会产生不确定的结果。
在多线程环境中,当各线程不共享数据的时候,即都是私有( private )成员,那么一定是线程安全的。
但这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。
线程安全一般都涉及到 synchronized , 就是一段代码同时只能有一个线程来操作 不然中间过程可能会 产生不可预制的结果。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
Thread 类中的 yield 方法有什么作用?
Yield 方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU 占用而不能保证使其它线程一定能占用 CPU ,执行 yield() 的线程有可能在进入到暂停状态后马上又被执行。
Java 线程池中 submit() execute() 方法有什么区别?
两个方法都可以向线程池提交任务, execute() 方法的返回类型是 void ,它定义在 Executor 接口中 , 而 submit()方法可以返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩展了
Executor 接口,其它线程池类像 ThreadPoolExecutor ScheduledThreadPoolExecutor 都有这些方
法。
说一说自己对于 synchronized 关键字的了解
});
t3 . start (); // 这里三个线程的启动顺序可以任意,大家可以试下!
t2 . start ();
t1 . start ();
}
} synchronized 关键字解决的是多个线程之间访问资源的同步性, synchronized 关键字可以保证被它修 饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中, synchronized 属于重量级锁,效率低下,因为监视器锁( monitor )是依 赖于底层的操作系统的 Mutex Lock 来实现的, Java 的线程是映射到操作系统的原生线程之上的。如果 要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的
synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对 synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。 JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
说说自己是怎么使用 synchronized 关键字,在项目中用到了
synchronized 关键字最主要的三种使用方式:
修饰实例方法 : 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法 : 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)。所以如果一个线程A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象, 因为访问静态 synchronized 方法占用的锁 是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。 修饰代码块 : 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
总结 synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上 锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因 为JVM 中,字符串常量池具有缓存功能!
什么是线程安全? Vector 是一个线程安全类吗?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运 行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一 个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可 以将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的 , 而和相似的ArrayList 不是线程安全的。
  volatile 关键字的作用?
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语
义:
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他
线程来说是立即可见的。
禁止进行指令重排序。
volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读
取; synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile 仅能使用在变量级别; synchronized 则可以使用在变量、方法、和类级别的。
volatile 仅能实现变量的修改可见性,并不能保证原子性; synchronized 则可以保证变量的修改可
见性和原子性。
volatile 不会造成线程的阻塞; synchronized 可能会造成线程的阻塞。
volatile 标记的变量不会被编译器优化; synchronized 标记的变量可以被编译器优化。
常用的线程池有哪些?
newSingleThreadExecutor :创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照
任务的提交顺序执行。
newFixedThreadPool :创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达
到线程池的最大大小。
newCachedThreadPool :创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池
大小完全依赖于操作系统(或者说 JVM )能够创建的最大线程大小。
newScheduledThreadPool :创建一个大小无限的线程池,此线程池支持定时以及周期性执行任
务的需求。
newSingleThreadExecutor :创建一个单线程的线程池。此线程池支持定时以及周期性执行任务
的需求。
简述一下你对线程池的理解
(如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)合理
利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系
统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Java 程序是如何执行的
我们日常的工作中都使用开发工具( IntelliJ IDEA Eclipse 等)可以很方便的调试程序,或者是通过打包工具把项目打包成 jar 包或者 war 包,放入 Tomcat Web 容器中就可以正常运行了,但你有没有想过 Java 程序内部是如何执行的?其实不论是在开发工具中运行还是在 Tomcat 中运行, Java 程序的执行流程基本都是相同的,它的执行流程如下:
先把 Java 代码编译成字节码,也就是把 .java 类型的文件编译成 .class 类型的文件。这个过程的
大致执行流程: Java 源代码 -> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字符码生成器 -> 最终
生成字节码,其中任何一个节点执行失败就会造成编译失败;
class 文件放置到 Java 虚拟机,这个虚拟机通常指的是 Oracle 官方自带的 Hotspot JVM
Java 虚拟机使用类加载器( Class Loader )装载 class 文件;
类加载完成之后,会进行字节码效验,字节码效验通过之后 JVM 解释器会把字节码翻译成机器码
交由操作系统执行。但不是所有代码都是解释执行的, JVM 对此做了优化,比如,以 Hotspot
拟机来说,它本身提供了 JIT Just In Time )也就是我们通常所说的动态编译器,它能够在运行时
将热点代码编译为机器码,这个时候字节码就变成了编译执行。 Java 程序执行流程图如下:

说一说自己对于 synchronized 关键字的了解
synchronized 关键字解决的是多个线程之间访问资源的同步性, synchronized 关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中, synchronized 属于重量级锁,效率低下,因为监视器锁( monitor )是依赖于底层的操作系统的 Mutex Lock 来实现的, Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对 synchronized 较大优
化,所以现在的 synchronized 锁效率也优化得很不错了。 JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
说说自己是怎么使用 synchronized 关键字,在项目中用到了
synchronized 关键字最主要的三种使用方式:
修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。也就是给当前
类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static
表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份,所以对该类的所有对象都加了
锁)。所以如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个
实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象, 因为访问静态
synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前
实例对象锁 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 和 synchronized 方法一样, synchronized(this) 代码块也是锁定当前对象的。 synchronized 关键字 加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁。这里再提一下:
synchronized 关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要
使用 synchronized(String a) 因为 JVM 中,字符串常量池具有缓冲功能!
下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。
面试中面试官经常会说: 单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单利 模式的原理呗!”
双重校验锁实现对象单例(线程安全)
另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
1. uniqueInstance 分配内存空间
2. 初始化 uniqueInstance
3. uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2 。指令重排在单线程环境下不会出先 问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 3
此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance ,但此 时 uniqueInstance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
 讲一下 synchronized 关键字的底层原理
synchronized 关键字底层原理属于 JVM 层面。
synchronized 同步语句块的情况
public class Singleton {
private volatile static Singleton uniqueInstance ;
private Singleton () {
}
public static Singleton getUniqueInstance () {
// 先判断对象是否已经实例过,没有实例化过才进入加锁代码
if ( uniqueInstance == null ) {
// 类对象加锁
synchronized ( Singleton . class ) {
if ( uniqueInstance == null ) {
uniqueInstance = new Singleton ();
}
}
}
return uniqueInstance ;
}
} public class SynchronizedDemo {
public void method () {
synchronized ( this ) {
System . out . println ( "synchronized 代码块 " );
}
}
}
通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录 执行 javac SynchronizedDemo.java 命令生成编译后的 .class 文件,然后执行 javap - c - s - v - l SynchronizedDemo.class 。

从上面我们可以看出:
synchronized 同步语句块的实现使用的是 monitorenter monitorexit 指令,其中
monitorenter 指令指向同步代码块的开始位置, monitorexit 指令则指明同步代码块的结束位置。
当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor 对象存在于每个 Java 对象
的对象头中, synchronized 锁便是通过这种方式获取锁的,也是为什么 Java 中任意对象可以作为锁的
原因 ) 的持有权 . 当计数器为 0 则可以成功获取,获取后将锁计数器设为 1 也就是加 1 。相应的在执行
monitorexit 指令后,将锁计数器设为 0 ,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等
待,直到锁被另外一个线程释放为止。
synchronized 修饰方法的的情况

 

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是
ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法, JVM 通过该 ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

为什么要用线程池?
线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如 已完成任务的数量。
这里借用《 Java 并发编程的艺术》提到的来说一下使用线程池的好处:
降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系
统的稳定性,使用线程池可以进行统一的分配,调优和监控。
实现 Runnable 接口和 Callable 接口的区别
如果想让线程池执行任务的话需要实现的 Runnable 接口或 Callable 接口。 Runnable 接口或 Callable 接 口实现类都可以被ThreadPoolExecutor ScheduledThreadPoolExecutor 执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。
备注: 工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换。
Executors.callable Runnable task Executors.callable Runnable task Object
resule )。
 执行 execute() 方法和 submit() 方法的区别是什么呢?
1) execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
2) submit() 方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象,通过这个 future
对象可以判断任务是否执行成功 ,并且可以通过 future get() 方法来获取返回值, get() 方法会阻塞当前线程直到任务完成,而使用 get long timeout TimeUnit unit 方法则会阻塞当前线程一段时间 后立即返回,这时候有可能任务没有执行完。
如何创建线程池 《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过
ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽 的风险**
Executors 返回线程池对象的弊端如下:
FixedThreadPool SingleThreadExecutor : 允许请求的队列长度为
Integer.MAX_VALUE, 可能堆积大量的请求,从而导致 OOM
CachedThreadPool ScheduledThreadPool : 允许创建的线程数量为
Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM
方式一:通过构造方法实现

方式二:通过 Executor 框架的工具类 Executors 来实现 我们可以创建三种类型的
ThreadPoolExecutor
FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。
当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在
一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
SingleThreadExecutor 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线
程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
CachedThreadPool 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数
量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新
的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复
用。
对应 Executors 工具类中的方法如图所示:

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值