进程与线程的区别
- 进程是资源分配的最小单位,线程是程序执行的最小单位
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段,堆栈段和数据段。线程是共享进程中的数据的,使用相同的地址空间,CPU切换一个线程的花费远小于切换一个进程的花费,创建一个线程的开销也远小于进程。
- 线程之间通信更加方便,同一进程下的线程共享全局变量,静态变量等数据,而进程之间的通信需要以通信的方式进行。
- 多进程程序更加健壮,多线程只要一个线程死掉,整个进程挂掉。
- 一个程序至少有一个进程,一个进程至少有一个线程。
Java实现多线程
- Java中可以通过三种方式实现多线程
- 继承Thread类
- 实现Runable接口
- 实现Callable接口
继承Thread类
实例:
1234567891011121314151617181920212223242526/*** @program: 02_Basic* @author: cdx* @create: 2018-09-02 16:23**/public class CreateThreadByExtendsThread extends Thread{private String name;public CreateThreadByExtendsThread(String name) {this.name = name;}public void run() {for (int i = 0; i < 3; i++)System.out.println(this.name + " create thread by extends Thread!");}public static void main(String[] args) {Thread threadA = new CreateThreadByExtendsThread("A");Thread threadB = new CreateThreadByExtendsThread("B");threadA.start();threadB.start();}}运行结果:
123456B create thread by extends Thread!A create thread by extends Thread!B create thread by extends Thread!A create thread by extends Thread!A create thread by extends Thread!B create thread by extends Thread!
实现Runable接口
- 实例
|
|
运行结果
123456A create thread by implements Runable!A create thread by implements Runable!A create thread by implements Runable!B create thread by implements Runable!B create thread by implements Runable!B create thread by implements Runable!
实现Callable接口
实例
123456789101112131415161718192021222324252627282930313233343536373839import java.util.concurrent.Callable;/*** @program: 02_Basic* @author: cdx* @create: 2018-09-02 16:45**/public class CreateThreadByImplCallable implements Callable<String> {private String name;public CreateThreadByImplCallable(String name) {this.name = name;}public String call() throws Exception {for (int i = 0; i < 3; i++) {System.out.println(this.name + " create thread by implements Calable!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}return name;}public static void main(String[] args) {Callable<String> threadA = new CreateThreadByImplCallable("A");Callable<String> threadB = new CreateThreadByImplCallable("B");try {System.out.println("the result of threadA: " + threadA.call());System.out.println("the result of threadB: " + threadB.call());} catch (Exception e) {e.printStackTrace();}}}运行结果
1234567A create thread by implements Calable!A create thread by implements Calable!the result of threadA: AB create thread by implements Calable!B create thread by implements Calable!B create thread by implements Calable!the result of threadB: B
Thread和Runnable的区别
- Runable适合多个相同的程序代码的线程去处理同一个资源
- Runnable可以避免Java中单继承的限制
- Runable增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
线程池只能放入实现Runable或Callable接口的类线程,不能直接放入继承Thread的类
- main方法本身是一个线程,Java中所有线程都是同时启动的,执行取决于对CPU资源的竞争
- Java中每个程序至少运行两个线程, main和jvm。
Runable和Callable的区别
- Callable规定的方法为call,Runable规定的方法为run
- call方法可以抛出异常,run方法不可以抛出异常
- Callable的任务执行后可返回值,运行Callable任务可以拿到一个Future对象,而Runable是不能返回值的。
Callable缺点在于繁琐,实现Callable对象+实现call方法+ExecotorService获取Future对象
- Future表示异步计算的结果,提供了检查计算是否完成的方法,以等待计算的完成并检查结果。
- 通过Future对象可以了解任务的执行情况,可以取消任务的执行,可以获取任务的执行结果
线程状态切换
- 新建状态(New):使用new关键字新建一个线程对象
- 就绪状态(Runable):线程对象创建之后,其他线程调用了该对象的start()方法,该状态的线程位于可运行线程池中,变的可运行,需要竞争获取CPU时间片。
- 运行状态(Running):就绪状态的线程获取了CPU的时间片,执行程序代码。
- 阻塞状态(Blocking):阻塞状态是因为线程因为某种原因,暂时停止运行,知道线程进入就绪状态,才有机会转换到运行状态,。阻塞状态分为三种情况:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中,会释放持有的锁。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了IO请求时,JVM会把该线程晋升为阻塞状态,当sleep状态超时,join等待线程终止或者超时时,或者IO处理完毕,线程重新转入就绪状态。sleep不会释放所持有的锁。
- 死亡状态(Dead):线程执行完毕或者因异常退出了run()方法,该线程结束生命周期。
线程调度
线程优先级
- Java中线程存在优先级,优先级别高的线程会获得更多的运行机会。
Java中优先级取值范围为1-10,Thread类中的优先级
1234567891011121314/*** The minimum priority that a thread can have.*/public final static int MIN_PRIORITY = 1;/*** The default priority that is assigned to a thread.*/public final static int NORM_PRIORITY = 5;/*** The maximum priority that a thread can have.*/public final static int MAX_PRIORITY = 10;Thread类的setPriority和getPriority可以设置和获取线程的优先级
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/*** Changes the priority of this thread.* <p>* First the <code>checkAccess</code> method of this thread is called* with no arguments. This may result in throwing a* <code>SecurityException</code>.* <p>* Otherwise, the priority of this thread is set to the smaller of* the specified <code>newPriority</code> and the maximum permitted* priority of the thread's thread group.** @param newPriority priority to set this thread to* @exception IllegalArgumentException If the priority is not in the* range <code>MIN_PRIORITY</code> to* <code>MAX_PRIORITY</code>.* @exception SecurityException if the current thread cannot modify* this thread.* @see #getPriority* @see #checkAccess()* @see #getThreadGroup()* @see #MAX_PRIORITY* @see #MIN_PRIORITY* @see ThreadGroup#getMaxPriority()*/public final void setPriority(int newPriority) {ThreadGroup g;checkAccess();if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {throw new IllegalArgumentException();}if((g = getThreadGroup()) != null) {if (newPriority > g.getMaxPriority()) {newPriority = g.getMaxPriority();}setPriority0(priority = newPriority);}}/*** Returns this thread's priority.** @return this thread's priority.* @see #setPriority*/public final int getPriority() {return priority;}每个线程都有默认的优先级,主线程的默认的优先级为NORMAL
- 线程的优先级具有继承关系
- 一般使用三个静态常量作为优先级来完成与操作系统的线程优先级的映射
线程睡眠
123456789101112131415161718192021222324252627282930313233343536373839/*** Causes the currently executing thread to sleep (temporarily cease* execution) for the specified number of milliseconds plus the specified* number of nanoseconds, subject to the precision and accuracy of system* timers and schedulers. The thread does not lose ownership of any* monitors.** @param millis* the length of time to sleep in milliseconds** @param nanos* {@code 0-999999} additional nanoseconds to sleep** @throws IllegalArgumentException* if the value of {@code millis} is negative, or the value of* {@code nanos} is not in the range {@code 0-999999}** @throws InterruptedException* if any thread has interrupted the current thread. The* <i>interrupted status</i> of the current thread is* cleared when this exception is thrown.*/public static void sleep(long millis, int nanos)throws InterruptedException {if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos >= 500000 || (nanos != 0 && millis == 0)) {millis++;}sleep(millis);}- sleep方法使线程转到阻塞状态,以毫秒为单位,当睡眠结束后,就转为Runnable状态,平台移植性比较好,睡眠过程中不会释放持有的锁。
线程等待
Object类的wait()方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或者notifyAll()唤醒方法。
1234567891011121314151617181920212223242526272829303132333435363738/*** Causes the current thread to wait until another thread invokes the* This method should only be called by a thread that is the owner* of this object's monitor. See the {@code notify} method for a* description of the ways in which a thread can become the owner of* a monitor.** @throws IllegalMonitorStateException if the current thread is not* the owner of the object's monitor.* @throws InterruptedException if any thread interrupted the* current thread before or while the current thread* was waiting for a notification. The <i>interrupted* status</i> of the current thread is cleared when* this exception is thrown.* @see java.lang.Object#notify()* @see java.lang.Object#notifyAll()*/public final void wait() throws InterruptedException {wait(0);}/*** Wakes up a single thread that is waiting on this object's* @throws IllegalMonitorStateException if the current thread is not* the owner of this object's monitor.* @see java.lang.Object#notifyAll()* @see java.lang.Object#wait()*/public final native void notify();/*** Wakes up all threads that are waiting on this object's monitor* @throws IllegalMonitorStateException if the current thread is not* the owner of this object's monitor.* @see java.lang.Object#notify()* @see java.lang.Object#wait()*/public final native void notifyAll();
- Obj.wait()与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。
- 相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。
- Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制
实例
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public class SimpleWaitAndNotify {final static Object object = new Object();public static class T1 extends Thread {public void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ":T1 start! ");try {System.out.println(System.currentTimeMillis() + ":T1 wait for object");object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(System.currentTimeMillis() + ":T1 end!");}}}public static class T2 extends Thread {public void run() {synchronized (object) {System.out.println(System.currentTimeMillis() + ":T2 start! notify ont thread");object.notify();System.out.println(System.currentTimeMillis() + ":T2 end!");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {Thread thread1 = new T1();Thread thread2 = new T2();thread1.start();thread2.start();}}/*1535885737176:T1 start!1535885737176:T1 wait for object1535885737176:T2 start! notify ont thread1535885737176:T2 end!1535885739177:T1 end!*/wait()和sleep()区别
- 共同点
- 多线程环境中,都可以在程序中调用阻塞当前线程指定的毫秒数
- wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出异常。
- 不同点
- sleep是Thread类的方法,wait是Object类的方法
- 每个对象都有一个锁来控制同步访问。synchronize关键字可以和对象的锁交互,实现线程的同步。sleep方法没有释放锁,wait方法释放了锁。
- wait,notify,notifyAll只能在同步控制方法或者同步控制块中使用,sleep可以在任意地方使用。
- 共同点
线程让步
- Thread.yield()方法,导致当前正在执行的线程对象,将执行机会让给相同或者更高优先级的线程。
- 使用目的为让相同优先级的线程之间进行适当的轮转执行,实际中yield让出时间片的线程有可能获得时间片继续执行。
- yield不会导致线程转入等待、睡眠、阻塞状态。大多数情况下,yield将导致线程从运行状态转入就绪状态,也可能没有效果。
- sleep和yield的区别
- sleep使得当前线程进入阻塞状态,执行sleep的线程在指定的时间内肯定不会被执行,yield只是使当前的线程进入就绪状态,执行yield之后该线程有可能竞争到时间片从而马上执行。
- 方法使当前运行中的线程休眠一段时间,进入阻塞状态,这段时间的长短是由程序设定的。yield方法使当前程序让出CPU占有权,让出时间无法控制。
- sleep允许较低优先级的线程获得运行机会,yield只会将执行机会让给同级的或者高级的线程。
线程加入
Thread.join()方法,等待其他线程终止。在当前的线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个线程运行结束,当前线程由阻塞状态转为就绪状态。
当主线程需要子线程的计算结果进行后续计算时,使用join,否则当子线程耗时较长时,主线程会先结束。
1234567891011121314151617181920public class JoinMain {public volatile static int i = 0;public static class AddThread extends Thread {public void run() {i = 0;while (i < 1000000) {i++;}}}public static void main(String[] args) throws InterruptedException {AddThread addThread = new AddThread();addThread.start();addThread.join();System.out.println(i);}}// 1000000
线程唤醒
- notify():唤醒在此对象监视器上等待的单个线程,如果多个线程等待,则随机唤醒一个线程。
- 线程通过调用该对象的wait方法进入对象的监视器上处于等待状态
- notifyAll():唤醒所有等待线程
线程同步
synchronized关键字
对象锁
- 某个对象实例内,synchronized a method可以防止多个线程同时访问这个对象的synchronized方法。
- 如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,则其他线程就不能访问这个对象的任何一个synchronized方法
- 不同对象实例的synchronized方法是互不干扰的,其他线程可以统统是访问相同类的另一个对象中的synchronized方法。
- synchronized关键字在方法中的某个代码块上,同样表示对该对象加锁。
类锁
- synchronized static aMethod{} 防止多个线程同时访问这个类的synchronized static方法。其对类的所有对象实例起作用。
synchronized关键字是不能继承的,父类中的synchronized方法在继承类中不是自动创建为synchronized方法,需要显示指定。
synchronized关键字可有作用在instance变量,object引用,static函数和类名称字面常量上。
- synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当做锁
- 每个对象只有一个锁与之相关
- 实现同步需要很大的系统开销作为代价,甚至可能造成死锁,尽量避免无谓的同步控制。
总结
- 线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
- 线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法
- 对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
- 对于同步,要时刻清醒在哪个对象上同步,这是关键。
- 编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
- 当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
- 死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小,一旦程序发生死锁,程序将死掉。
线程数据传递
传统开发模式中,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但是在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大区别。由于线程的运行和结束都是不可预料的,因此,在传递和返回函数时无法像函数一样通过函数参数和return语句来返回数据。线程之间的数据传递可以通过构造方法,变量和方法,回调函数来完成。
通过构造方法来传递数据
在线程的构建方法中传入数据,线程运行之前数据就已经确定,不会造成数据在线程运行之后才传入的线性。但是传递的数据比较多时,成本很高。同时由于Java没有默认参数,因此需要使用重载,使得构造方法本身很复杂,同时数量很多。
|
|
通过变量和方法
向对象中传入数据一般有两次机会,第一次是在建立对象时时通过构造方法将数据传入,第二次是在类中定义一些列的public的方法和变量。然后再建立完对象之后,通过对象实例逐个赋值。
|
|
通过回调函数传递数据
前两种都是main方法中向线程类中传递数据,实际中,有些数据需要从线程类传递到main函数中,如下:
|
|