什么是线程:程序中负责执行的哪一个东东就叫作线程(执行路线,进程内部的执行序列),或者说是进程的子任务。java
继承Thread类;
实现Runnable接口;
实现Callable接口经过FutureTask包装器来建立Thread线程;
使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来管理前面的三种方式)。面试
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则不会编程
任什么时候候只有一个线程能够得到锁,也就是说只有一个线程能够运行synchronized 中的代码缓存
使用notifyall,能够唤醒
全部处于wait状态的线程,使其从新进入锁的争夺队列中,而notify只能唤醒一个。安全
wait() 应配合while循环使用,不该使用if,务必在wait()调用先后都检查条件,若是不知足,必须调用notify()唤醒另外的线程来处理,本身继续wait()直至条件知足再往下执行。多线程
notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,而且要求正确使用。否则可能致使死锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,若是唤醒的线程没法正确处理,务必确保继续notify()下一个线程,而且自身须要从新回到WaitSet中.并发
对于sleep()方法,咱们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。app
sleep()方法致使了程序暂停执行指定的时间,让出cpu该其余线程,可是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程当中,线程不会释放对象锁。框架
当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。jvm
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:
1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的,volatile关键字会强制将修改的值当即写入主存。
2)禁止进行指令重排序。
volatile 不是原子性操做
什么叫保证部分有序性?
当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且结果已经对后面的操做可见;在其后面的操做确定尚未进行;
x = 2; //语句1 y = 0; //语句2 flag = true; //语句3 x = 4; //语句4 y = -1; //语句5
因为flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句一、语句2前面,也不会讲语句3放到语句四、语句5后面。可是要注意语句1和语句2的顺序、语句4和语句5的顺序是不做任何保证的。
使用 Volatile 通常用于 状态标记量 和 单例模式的双检锁
start()方法被用来启动新建立的线程,并且start()内部调用了run()方法,这和直接调用run()方法的效果不同。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
明显的缘由是JAVA提供的锁是对象级的而不是线程级的,每一个对象都有锁,经过线程得到。若是线程须要等待某些锁那么调用对象中的wait()方法就有意义了。若是wait()方法定义在Thread类中,线程正在等待的是哪一个锁就不明显了。简单的说,因为wait,notify和notifyAll都是锁级别的操做,因此把他们定义在Object类中由于锁属于对象。
wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法以前,当前线程必须已经得到该对象的锁。所以,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。
在调用对象的notify()和notifyAll()方法以前,调用线程必须已经获得该对象的锁。所以,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。
调用wait()方法的缘由一般是,调用线程但愿某个特殊的状态(或变量)被设置以后再继续执行。调用notify()或notifyAll()方法的缘由一般是,调用线程但愿告诉其余等待中的线程:“特殊状态已经被设置”。这个状态做为线程间通讯的通道,它必须是一个可变的共享状态(或变量)。
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除然后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。不管如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
类似点:
这两种同步方式有不少类似之处,它们都是加锁方式同步,并且都是阻塞式的同步,也就是说当若是一个线程得到了对象锁,进入了同步块,其余访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.
区别:
这两种方式最大区别就是对于Synchronized来讲,它是java语言的关键字,是原生语法层面的互斥,须要jvm实现。而ReentrantLock它是JDK 1.5以后提供的API层面的互斥锁,须要lock()和unlock()方法配合try/finally语句块来完成。
Synchronized进过编译,会在同步块的先后分别造成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。若是这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。若是获取对象锁失败,那当前线程就要阻塞,直到对象锁被另外一个线程释放为止。
因为ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有如下3项:
1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程能够选择放弃等待,这至关于Synchronized来讲能够避免出现死锁的状况。
2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序得到锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是建立的非公平锁,能够经过参数true设为公平锁,但公平锁表现的性能不是很好。
3.锁绑定多个条件,一个ReentrantLock对象能够同时绑定对个对象。
在多线程中有多种方法让线程按特定顺序执行,你能够用线程类的join()方法在一个线程中启动另外一个线程,另一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
实际上先启动三个线程中哪个都行,
由于在每一个线程的run方法中用join方法限定了三个线程的执行顺序。
public class JoinTest2 { // 1.如今有T一、T二、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"); } }); t3.start();//这里三个线程的启动顺序能够任意,你们能够试下! t2.start(); t1.start(); } }
SynchronizedMap()和Hashtable同样,实现上在调用map全部方法时,都对整个map进行同步。而ConcurrentHashMap的实现却更加精细,它对map中的全部桶加了锁。因此,只要有一个线程访问map,其余线程就没法进入map,而若是一个线程在访问ConcurrentHashMap某个桶时,其余线程,仍然能够对map执行某些操做。
因此,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优点。同时,同步操做精确控制到桶,这样,即便在遍历map时,若是其余线程试图对map进行数据修改,也不会抛出ConcurrentModificationException。
线程安全就是说多线程访问同一代码,不会产生不肯定的结果。
在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么必定是线程安全的。但这种状况并很少见,在多数状况下须要共享数据,这时就须要进行适当的同步控制了。
线程安全通常都涉及到synchronized, 就是一段代码同时只能有一个线程来操做 否则中间过程可能会产生不可预制的结果。
若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。
Yield方法能够暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法并且只保证当前线程放弃CPU占用而不能保证使其它线程必定能占用CPU,执行yield()的线程有可能在进入到暂停状态后立刻又被执行。
两个方法均可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法能够返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字能够保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,由于监视器锁(monitor)是依赖于底层的操做系统的 Mutex Lock 来实现的,Java 的线程是映射到操做系统的原生线程之上的。若是要挂起或者唤醒一个线程,都须要操做系统帮忙完成,而操做系统实现线程之间的切换时须要从用户态转换到内核态,这个状态之间的转换须要相对比较长的时间,时间成本相对较高,这也是为何早期的 synchronized 效率低的缘由。庆幸的是在 Java 6 以后 Java 官方对从 JVM 层面对synchronized 较大优化,因此如今的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减小锁操做的开销。
若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量 的值也和预期的是同样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的状况下也不会出现计算失误。很显然你能够将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它类似的ArrayList不是线程安全的。
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:
volatile标记的变量不会被编译器优化;synchronized标记的变量能够被编译器优化。
(若是问到了这样的问题,能够展开的说一下线程池如何用、线程池的好处、线程池的启动策略)合理利用线程池可以带来三个好处。
第一:下降资源消耗。经过重复利用已建立的线程下降线程建立和销毁形成的消耗。
第二:提升响应速度。当任务到达时,任务能够不须要等到线程建立就能当即执行。
第三:提升线程的可管理性。线程是稀缺资源,若是无限制的建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一的分配,调优和监控。
咱们平常的工做中都使用开发工具(IntelliJ IDEA 或 Eclipse 等)能够很方便的调试程序,或者是经过打包工具把项目打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就能够正常运行了,但你有没有想过 Java 程序内部是如何执行的?其实不管是在开发工具中运行仍是在 Tomcat 中运行,Java 程序的执行流程基本都是相同的,它的执行流程以下:
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字能够保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,由于监视器锁(monitor)是依赖于底层的操做系统的 Mutex Lock 来实现的,Java 的线程是映射到操做系统的原生线程之上的。若是要挂起或者唤醒一个线程,都须要操做系统帮忙完成,而操做系统实现线程之间的切换时须要从用户态转换到内核态,这个状态之间的转换须要相对比较长的时间,时间成本相对较高,这也是为何早期的 synchronized 效率低的缘由。庆幸的是在 Java 6 以后 Java 官方对从 JVM 层面对synchronized 较大优化,因此如今的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减小锁操做的开销。
synchronized关键字最主要的三种使用方式:
下面我已一个常见的面试题为例讲解一下 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; } }
另外,须要注意 uniqueInstance 采用 volatile 关键字修饰也是颇有必要。
uniqueInstance 采用 volatile 关键字修饰也是颇有必要的, uniqueInstance = new Singleton(); 这段代码实际上是分为三步执行:
可是因为 JVM 具备指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,可是在多线程环境下会致使一个线程得到尚未初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,所以返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
使用 volatile 能够禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
synchronized 关键字底层原理属于 JVM 层面。
① synchronized 同步语句块的状况
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 修饰方法的的状况
public class SynchronizedDemo2 { public synchronized void method() { System.out.println("synchronized 方法"); } }
synchronized 修饰的方法并无 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 经过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
线程池提供了一种限制和管理资源(包括执行一个任务)。 每一个线程池还维护一些基本统计信息,例如已完成任务的数量。
这里借用《Java并发编程的艺术》提到的来讲一下使用线程池的好处:
若是想让线程池执行任务的话须要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。二者的区别在于 Runnable 接口不会返回结果可是 Callable 接口能够返回结果。
备注: 工具类Executors
能够实现Runnable
对象和Callable
对象之间的相互转换。(Executors.callable(Runnable task)
或Executors.callable(Runnable task,Object resule)
)。
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:
对应Executors工具类中的方法如图所示:
感谢你看到这里,文章有什么不足还请指正,以为文章对你有帮助的话记得给我点个赞,天天都会分享java相关技术文章或行业资讯,欢迎你们关注和转发文章!