java多线程基础整理
一般都知道,线程是被包含在进程里的,一个进程可以有多个线程同时存在。
进程是资源分配的最小空间,线程是cpu调度的最小单位。
进程和线程的区别:
1、线程不能看做独立应用,而进程可看做独立应用。
2、进程有独立的地址空间,互相不影响,线程只是进程的不同执行路径。
3、线程没有独立的地址空间,多进程的程序比多线程程序健壮。
4、进程的切换比线程的切换开销大。
java中线程和进程的关系:
1、java对操作系统提供的功能进行封装,包括进程和线程。
2、运行一个java程序会产生一个进程,进程包含至少一个进程。
3、每个进程对应一个JVM实例,多个线程共享JVM里的堆。
4、java采用单线程编程模型,程序会自动创建主线程。
5、主线程可以创建子线程,原则上要后于子线程完成执行。
Runnable是一个函数式接口,内部只有一个public abstract void run()的抽象方法,所以它本身是不带有多线程的功能的。Thread是实现了Runnable接口的类,它的start方法才使得run()里的代码支持多线程特性。
由于java的单一继承原则,推荐使用Runnable。例如可以将业务类实现Runnable接口,将业务逻辑封装在run方法里,将给业务类作为参数传递给Thread类,可以实现多线程的特性。但是,如果通过继承Thread类的方式实现多线程,那么业务类将无法继承其他类。
另外:Runnable里的资源可以共享,下面使用代码比较说明:
使用Thread:
public class MyThread extends Thread { int count = 5; @Override public void run() { while (count > 0){ System.out.println(Thread.currentThread().getName()+ ":" + count); count--; } } }
public class ThreadDome { public static void main(String[] args){ Thread thread1 = new MyThread(); Thread thread2 = new MyThread(); Thread thread3 = new MyThread(); thread1.start(); thread2.start(); thread3.start(); } }
运行结果:
Thread-0:5 Thread-0:4 Thread-0:3 Thread-0:2 Thread-1:5 Thread-1:4 Thread-0:1 Thread-1:3 Thread-1:2 Thread-1:1 Thread-2:5 Thread-2:4 Thread-2:3 Thread-2:2 Thread-2:1
使用Runnable:
public class MyRunnable implements Runnable { private int count = 5; @Override public void run() { while (count > 0){ System.out.println(Thread.currentThread().getName() +":"+ count); count--; } } }
public class RunnableDome { public static void main(String[] args){ MyRunnable myRunnable1 = new MyRunnable(); Thread thread1 = new Thread(myRunnable1); Thread thread2 = new Thread(myRunnable1); Thread thread3 = new Thread(myRunnable1); thread1.start(); thread2.start(); thread3.start(); } }
结果:
Thread-0:5 Thread-1:5 Thread-0:4 Thread-0:2 Thread-1:3 Thread-0:1
上述比较主要是说明,通过Runnable参数创建的Thread,Runnable里的变量是共享的。但是同时也需要注意线程安全了。
先说一下结论:它们都可以实现启动run方法里的逻辑,区别是,run方法调用的主线程,start方法调用的是新创建的线程。
1、代码说明:
先是run方法:
public class ThreadTest { public static void attack(){ System.out.println("Current thread is :" + Thread.currentThread().getName()); } public static void main(String[] args){ Thread r = new Thread(){ @Override public void run() { attack(); } }; System.out.println("Current main thread is :" + Thread.currentThread().getName()); r.run(); } }
结果:
Current main thread is :main
Current thread is :main
可以发现,当前线程是main
start方法:
public class ThreadTest { public static void attack(){ System.out.println("Current thread is :" + Thread.currentThread().getName()); } public static void main(String[] args){ Thread r = new Thread(){ @Override public void run() { attack(); } }; System.out.println("Current main thread is :" + Thread.currentThread().getName()); r.start(); } }
结果:
Current main thread is :main
Current thread is :Thread-0
可以发现,当前线程是新创建的Thread-0.
2、为什么会这样?
首先,run方法是Runnable的一个抽象方法,Thread类重写这个方法的源码如下:
@Override public void run() { if (target != null) { target.run(); } }
target就就是当做参数传入的Runnable接口。如果是通过参入Runnable 参数实现的Thread,就调用Runnable的run方法;而如果没有传入Runnable接口,那么targer会为空,这时意味着,run方法被Thread的继承类重写或是被匿名内部类重写,则会调用相应的重写run方法。
但是,通过上述代码没有找到新线程的创建的逻辑,是因为,新线程的创建在Thread类的start方法里,代码:
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
继续查看start0的方法:
private native void start0();
这里是调用了外部的源码,继续查找(查看源码网址:链接):
{"start0", "()V", (void *)&JVM_StartThread},
调用jvm包里的JVM_StartThread方法(链接):
其中关键:
native_thread = new JavaThread(&thread_entry, sz);
继续查看:thread_entry方法:
static void thread_entry(JavaThread* thread, TRAPS) { HandleMark hm(THREAD); Handle obj(THREAD, thread->threadObj()); JavaValue result(T_VOID); JavaCalls::call_virtual(&result, obj, KlassHandle(THREAD, SystemDictionary::Thread_klass()), vmSymbols::run_method_name(), vmSymbols::void_method_signature(), THREAD); }
最终在创建线程之后,调用run方法(vmSymbols::run_method_name())。
所以start的逻辑就是:先调用底层代码创建线程,然后返回调用run方法。
再多线程的运行环境中,代码并非顺序执行了,当线程运行后的代码跟线程的处理结果有关时,该如何准确的得到呢?
这里给出三种处理方式:1、循环等待 2、join方法阻塞等待 3、使用Callable接口,并用FutureTask或线程池处理。
1、循环等待
import lombok.SneakyThrows; public class CycleWait { static String value; @SneakyThrows public static void main(String[] args){ Thread thread = new Thread(){ @SneakyThrows//lombok注解,直接抛出非运行时异常 @Override public void run() { System.out.println("run start!"); Thread.currentThread().sleep(3000); value = "success!"; System.out.println("run done!!"); } }; thread.start(); while(value == null){ Thread.currentThread().sleep(100); } System.out.println(value); } }
如果没有进行循环等待,最终打印出的value会是null
结果:
run start! run done!! success!
2、使用join方法
import lombok.SneakyThrows; public class CycleWait { static String value; @SneakyThrows public static void main(String[] args){ Thread thread = new Thread(){ @SneakyThrows @Override public void run() { System.out.println("run start!"); Thread.currentThread().sleep(3000); value = "success!"; System.out.println("run done!!"); } }; thread.start(); thread.join(); System.out.println(value); } }
结果:
run start! run done!! success!
3、使用Callable接口,并用FutureTask或线程池处理。
使用Callable接口并用FutureTask处理,这里的FutureTask实现了Runnable,相当于使用Runnable创建线程类。
先创建继承Callable的实现类
import java.util.concurrent.Callable; public class MyCallable implements Callable<String> { @Override public String call() throws Exception { System.out.println("call start..."); Thread.currentThread().sleep(1000); System.out.println("call over!!"); return "Callable success!!"; } }
a、FutureTask调用:
import java.util.concurrent.FutureTask; public class FutureTaskDemo { @SneakyThrows public static void main(String[] args){ FutureTask futureTask = new FutureTask(new MyCallable()); new Thread(futureTask).start(); System.out.println("return:" + futureTask.get()); } }
结果:
call start... call over!! return:Callable success!!
这里FutureTask中的get方法,实现自Future接口,是一个阻塞方法,知道线程处理完成才会结束该方法调用。
b、线程池调用
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ThreadPoolDemo { @SneakyThrows public static void main(String[] args){ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); Future<String> future = newCachedThreadPool.submit(new MyCallable()); System.out.println(future.get()); } }
在Thread类中的State枚举类下,线程有6个状态:(新建)new、(运行)Runnable、(阻塞)blocked、9(无限期等待)waiting、(限期等待)timed_waiting、(结束)terminated
1、New——创建后尚未启动的线程状态,start之间的线程状态。
2、Runnable——包含操作系统线程状态中的Running和Ready,start之后,线程进入线程池等待cpu调用,在等待cpu调用时就是Ready状态,cpu调用时就是Running状态。
3、Waiting——不会被cpu调用,需要被其他线程显示的唤醒
会造成waiting状态的方法:
没有设置等待时间的Object.wait()方法
没有设置等待时间的Thread.join()方法
LockSupport.park()方法
4、timed_waiting——在一定时间后会由系统自动唤醒
会造成timed_waiting状态的方法:
设置了等待时间的Object.wait()方法
设置了等待时间的Thread.join()方法
LockSupport.parkNames()方法
LockSupport.parkUnitil()方法
5、Blocked——等待获取排他锁
6、Terminated——线程已经结束执行
1、sleep
public static native void sleep(long millis) throws InterruptedException;
是线程类的静态方法,让线程进入一定时间的等待状态。
只会让出cup运行时间,不会放弃同步锁。
2、wait、notify和notifyAll
public final void wait() throws InterruptedException { wait(0); } public final native void wait(long timeout) throws InterruptedException; public final native void notify(); public final native void notifyAll();
Object类的实例方法。
调用有参数的wait方法的线程会等待一定时间。
调用没有参数的wait方法的线程会进入等待池中无限等待,直到被notify或notifyAll唤醒。
wait方法会释放同步锁,带不带参数都会。
wait和notify方法只能在书写在synchronized方法(块)中,而且只能被当前synchronized锁对象调用。
notify和notifyAll的区别是,notifyAll会唤醒所有等待池中的线程进入锁池,而notify只会随机选中一个唤醒。
首先介绍两个概念:锁池和等待池
锁池:假设线程A已经占用了某个实例对象的锁,线程B、C在此时想调用该对象的synchronized方法或块,那么B、C会被阻塞,进入锁池,等待锁的释放。
等待池:假设线程A调用了某个对象的wait方法,线程A就会释放该对象的锁,并进入等待池,进入等待池的线程不会去竞争该对象的锁。直到被notify方法调用,进入锁池。
所以,当线程调用wait方法时,该线程会进入等待池中,直到被其他线程的notify或notifyAll方法唤醒,进入锁池,等待cpu调用。
调用wait或notify的书写方式:
根据synchronazied的加锁对象,调用该锁的方法。
实例:
synchronized (object){ System.out.println("wait start ...."); object.wait(); System.out.println("wait over!!"); }
如果加的是锁是this,可直接书写方法:
synchronized (this){ System.out.println("wait start ...."); wait(); System.out.println("wait over!!"); }
注意事项:
a、在某个锁的synchronized方法(块),只能调用该锁的wait和notify方法。
如:错误实例
synchronized (object){ System.out.println("wait start ...."); this.wait(); System.out.println("wait over!!"); }
object锁中,只能调用object的wait方法,不能调用this的wait方法。该调用会报异常:java.lang.IllegalMonitorStateException
b、一个锁的wiat方法只能用同一锁的notify方法唤醒。
即:只有object.notify()方法才能唤醒,object.wait()方法挂起的线程。
c、使用notify方法唤醒其他线程时,notify所在线程会先执行完毕,wait方法所在线程才会执行,并按照进入等待池中的先后顺序执行。
实例:
import lombok.SneakyThrows; public class WaitTest { static Object object = new Object(); @SneakyThrows public static void main(String[] args){ Runnable waitRunnable = new Runnable() { @SneakyThrows @Override public void run() { synchronized (object){ System.out.println(Thread.currentThread().getName()+" start ...."); object.wait(); System.out.println(Thread.currentThread().getName()+" over!!"); } } }; Runnable notifyRunnable = new Runnable() { @SneakyThrows @Override public void run() { synchronized (object){ System.out.println("notify start ...."); object.notifyAll(); Thread.sleep(3000); System.out.println("notify已阻塞....."); System.out.println("notify over!!"); } } }; Thread wait = new Thread(waitRunnable,"wait"); Thread wait1 = new Thread(waitRunnable,"wait1"); Thread wait2 = new Thread(waitRunnable,"wait2"); Thread notify = new Thread(notifyRunnable); wait1.start(); wait2.start(); wait.start(); Thread.sleep(100); notify.start(); } }
结果:
wait1 start .... wait2 start .... wait start .... notify start .... notify已阻塞..... notify over!! wait over!! wait2 over!! wait1 over!!
以上结果可知:
notify执行完毕,被wait的线程才执行,并且按照执行wait的先后顺序再次执行。
3、yield
public static native void yield();
当调用Thread.yield方法时,会给线程调度器一个当前线程愿意让出cpu使用的暗示,但是线程调用可能会忽略。如果让出,会重写等待cpu调度。
yield方法,不会释放同步锁。
4、interrupt
interrupt()是线程中断方法,该方法调用时不会立即中断线程,而是将中断标记更改为true,之后会有两种情况。
1.当线程状态变为阻塞时,该线程会抛出InterruptedException异常,线程借助异常中断。
2.若线程标记更改为中断后没有遇到阻塞的情况,不会对线程造成影响。
该方法是Thread类的实例方法。
5、join
A线程执行过程中调用B线程的join方法,A线程会阻塞,等待B线程执行完毕。
该方法是Thread的实例方法,不会释放对象锁。
有3种重载的形式:
——join() :
等待被join的线程执行完成
——join(long millis) :
等待被join的线程的时间最长为millis毫秒,若在millis毫秒内,被join的线程还未执行结束,则不等待。
——join(long millis , int nanos) :
等待被join的线程的时间最长为millis毫秒加nanos微秒,若在此时间内,被join的线程还未执行结束,则不等待。
实例:
import lombok.SneakyThrows; public class CycleWait { static String value; @SneakyThrows public static void main(String[] args){ Thread thread = new Thread(){ @SneakyThrows @Override public void run() { System.out.println("run start!"); Thread.currentThread().sleep(3000); value = "success!"; System.out.println("run done!!"); } }; thread.start(); thread.join(); System.out.println(value); } }
结果:
run start! run done!! success!
主线程等待子线程执行完毕。
两个已经淘汰的方法
a.shop(),现使用interrupt()方法
该方法用于终止线程,被调用之后会立即终止该线程。并同时释放对象锁。
弊端:
1.当shop()不是本线程调用时,其他线程调用该方法,可能导致原有代码未得到执行,从而导致会业务逻辑不完整。
2.破坏原子逻辑,既破坏了加锁的初衷
该方法是Thread的实例方法,会释放对象锁。
b.suspend(),resume(),先使用wait(),notify(),notityAll()方法
该方法用于挂起,恢复线程。
弊端:由于该方法不会释放对象锁,所以很容易造成死锁
该方法是Thread的实例方法,不会释放对象锁。
这里要补充的一点是,调用wait方法后线程进入等待池,调用notify之后进入锁池,拿到对象锁之后并不是直接进入运行状态,而是进入ready状态,等待cpu调度。