多线程Thread VS Runnable两个案例
线程的启动方式
线程的启动方式,最直观的有两种:
- 继承Thread类
- 实现Runable接口
下面来看一段代码:
1. 继承了Thread类的HelloWorld
/**
* @author sy
* Thread 版 HelloWorld
*/
public class ThreadTest {
public static void main(String[] args) {
Thread thread1 = new HelloWorld("我是老大线程");
Thread thread2 = new HelloWorld("我是老二线程");
Thread thread3 = new HelloWorld("我是老三线程");
thread1.start();
thread2.start();
thread3.start();
}
}
class HelloWorld extends Thread {
private String name;
public HelloWorld(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("本线程名字:" + name + ",Hello World!");
}
}
控制台输出:
第一次执行:
本线程名字:我是老大线程,Hello World!
本线程名字:我是老二线程,Hello World!
本线程名字:我是老三线程,Hello World!
第二次执行:
本线程名字:我是老大线程,Hello World!
本线程名字:我是老三线程,Hello World!
本线程名字:我是老二线程,Hello World!
2.实现了Runnable接口的
/**
* @author sy
*/
public class RuannableTest {
public static void main(String[] args) {
HelloWorld2 helloWorld2 = new HelloWorld2();
Thread thread = new Thread(helloWorld2,"我是老大线程");
Thread thread2 = new Thread(helloWorld2,"我是老二线程");
Thread thread3 = new Thread(helloWorld2,"我是老三线程");
thread.start();
thread2.start();
thread3.start();
}
}
class HelloWorld2 implements Runnable {
@Override
public void run() {
System.out.println("Hello World!"+ "当前线程名字:" + Thread.currentThread().getName());
}
}
控制台结果:
第一次执行:
Hello World!当前线程名字:我是老大线程
Hello World!当前线程名字:我是老三线程
Hello World!当前线程名字:我是老二线程
第二次执行:
Hello World!当前线程名字:我是老大线程
Hello World!当前线程名字:我是老二线程
Hello World!当前线程名字:我是老三线程
3.二者区别:
①一个是通过继承方式去实现多线程,一个是通过实现接口去实现,在工作中,肯定是实现接口的方式去实现多线程比较灵活,扩展性更高一些。
②在继承了Thread的HelloWorld类中,我们需要手动去创建一个私有属性name,然后通过构造函数的方式来给线程命名,而实现了runnable接口的类可以直接通过Thread.currentThread().getName()的方式来获取新创建的线程对象中构造的name属性。
③都是通过start方法去启动线程,但是继承了Thread类的创建实例可以用Thread这个父类去new自己对应的新类对象,再去调用start方法,而实现了接口的对象类,是将新创建的对象类作为参数传进Thread thread = new Thread(xxxx)里。
4.线程的启动“顺序”:
可以从控制台结果看出:每个线程启动的时间是不定的,虽然程序代码是按顺序执行的,先执行了thread1.start(),然后是2,然后是3,于是线程1先启动,但是此时线程1并没有获取到CPU资源,而线程2强占先机,获取到了CPU资源,此时就是2先输出了。而下来2可能执行完了,此时让出CPU资源,线程1再次夺回CPU资源,此时线程1执行,最后是线程3执行了,所以这样的输出结果是2,1,3。如果你执行一次的顺序是1,2,3,多执行几次即可看到结果。
5.继承Thread类需要注意的点
同一个Thread对象,不允许调用两次start方法,否则会报出illegalThreadStateException异常。
例如:
Thread thread1 = new HelloWorld("我是老大线程");
thread1.start()
thread1.start()
通过“卖票”更深一步了解线程
还是两种方式去实现卖票,比如现在构想一个场景是卖票,一共有5张票,同时去让3个窗口去卖票,用多线程去实现!
1.继承Thread类的:
public class TicketThread{
public static void main(String[] args) {
//创建三个窗口,模拟卖票
Thread mt1 = new MyThread("窗口1");
Thread mt2 = new MyThread("窗口2");
Thread mt3 = new MyThread("窗口3");
//启动这三个线程,也是窗口,
mt1.start();
mt2.start();
mt3.start();
}
}
class MyThread extends Thread{
/**
* 一共有5张火车票
*/
private int ticksCount = 5;
/**
* 窗口,也是线程的名字
*/
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
super.run();
while (ticksCount>0){
ticksCount--; //如果还有一张票,就卖掉一张
System.out.println(name + "卖了1张票,剩余票数为:" + ticksCount);
}
}
}
控制台结果:
/**
窗口1卖了1张票,剩余票数为:4
窗口2卖了1张票,剩余票数为:4
窗口2卖了1张票,剩余票数为:3
窗口2卖了1张票,剩余票数为:2
窗口2卖了1张票,剩余票数为:1
窗口1卖了1张票,剩余票数为:3
窗口2卖了1张票,剩余票数为:0
窗口3卖了1张票,剩余票数为:4
窗口3卖了1张票,剩余票数为:3
窗口1卖了1张票,剩余票数为:2
窗口3卖了1张票,剩余票数为:2
窗口1卖了1张票,剩余票数为:1
窗口3卖了1张票,剩余票数为:1
窗口1卖了1张票,剩余票数为:0
窗口3卖了1张票,剩余票数为:0
*/
2.实现Runnable接口的
public class ticketRunnable {
public static void main(String[] args) {
MyThread2 mt = new MyThread2();
Thread t1 = new Thread(mt,"窗口1");
Thread t2 = new Thread(mt,"窗口2");
Thread t3 = new Thread(mt,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread2 implements Runnable{
/**
* 买票的票数
*/
private int ticketCount = 5;
@Override
public void run() {
while(ticketCount > 0){
ticketCount--;
System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余票数为:" + ticketCount);
}
}
}
控制台结果:
/**
窗口1卖了1张票,剩余票数为:4
窗口1卖了1张票,剩余票数为:3
窗口3卖了1张票,剩余票数为:1
窗口3卖了1张票,剩余票数为:0
窗口2卖了1张票,剩余票数为:2
*/
3.卖票两者区别:
①继承Thread类: 我们可以从控制台看到结果,明明是三个窗口去卖5张票(三个窗口加起来一共卖5张,而不是每个窗口各自有5张票),然而执行结果确实每个窗口都卖了5张票,也就是每个线程都执行了5次。这样明显是不符合结果的。
②实现了Runnable接口的:控制台可以看到3个窗口,一个卖了5张票,解决了Thread那种冲突,但是还有个问题,就是剩余票数并不是按照4,3,2,1,0这样的顺序卖完的,而是窗口1在第四行卖完了票,而窗口3在第五却又输出了一个1,这是为什么呢?
其实这点在上面解释过了,线程的启动确实是按照代码顺序去启动,但是强占CPU资源却是不固定的,线程1抢到了第一次的CPU资源,于是卖出1张票,并且执行完了打印输出语句,线程1再次抢到CPU资源继续卖票与打印语句此时还有3张票,接下来是线程2强占了CPU资源,但是还没来得及输出打印语句,就让出了CPU资源,线程3开始占用CPU资源,卖了一张票,并且打印输出语句,控制台第四行线程3再次占用CPU资源,卖完了最后一张票,最终让出CPU资源,让线程2,也就是窗口2执行完了打印语句。。。所以线程2的打印语句才会出现在控制台第5行。
4.出现两种不同情况的原因:
①继承了Thread类卖票为什么会出现3个线程卖了15张票呢?
因为每次新起一个线程,都是新创建了一个对象,而每个对象里的票不是共享的,每次创建一个新的对象,他自己的属性中就含有5张票,所以会出现15张的情况。
②实现Runnable接口的类:
创建一个实例,作为参数传进不同的三个线程中,作为一个共享资源去“卖票”。所以不会有15张票的情况。
线程的生命周期
盗用慕课一张图:
创建:对应的代码就是new了一个Thread对象。
就绪状态:创建了线程对象后,调用了start()方法。注意:此时线程只是进入了
线程队列,等待获取CPU服务,具备了运行的条件,但不一定已经运行了。
阻塞状态:一个正在执行的线程在某些情况下,由于某种原因而暂停让出了CPU资源,暂停
了自己的执行,便进入阻塞状态,例如在线程中掉用sleep()方法,wait(),或者join()
终止:线程的run()方法执行完毕,线程便进入终止状态。
守护线程
java线程有两类
1.用户线程:运行在前台,执行具体的任务
程序的主线程、连接网络的子线程都是用户线程
2.守护线程:运行在后台,为其他线程服务。
特点:一旦所有用户线程结束运行,守护线程会随着JVM一起结束工作
应用:数据库连接池中的监测线程、JVM虚拟机启动后的监测线程。
最常见的守护线程:垃圾回收线程。
jstack生成线程快照
jdk中自带的分析线程的工具,目录如下:
我们可以通过CMD去启动查看jstack的帮助,前提是配置了jdk环境变量:
一般命令直接输入 jstack pid 即可[-l]可以省略
我用debug打住了刚才java程序的一个断点,然后获取任务管理器的pid:
再次打开cmd来输入命令
cmd中生成的快照如下:
"NettythreadDeathWatcher-2-1" #12 daemon prio=1 os_prio=-2 tid=0x32d0a000 nid=0x35ec waiting on condition [0x3359f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at io.netty.util.ThreadDeathWatcher$Watcher.run(ThreadDeathWatcher.java:152)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"DestroyJavaVM" #11 prio=5 os_prio=0 tid=0x03073000 nid=0x4780 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"JPS thread pool" #10 prio=5 os_prio=0 tid=0x32be7000 nid=0x12a0 waiting on condition [0x3330f000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x13d0e9b0> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:460)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362)
at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"JPS thread pool" #9 prio=5 os_prio=0 tid=0x32be2400 nid=0x528 waiting on condition [0x3327f000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x13d0e9b0> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:460)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362)
at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"JPS thread pool" #8 prio=5 os_prio=0 tid=0x31ea9000 nid=0x2914 runnable [0x3210f000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x13d23688> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0x13d0fe58> (a java.util.Collections$UnmodifiableSet)
- locked <0x13d0a880> (a sun.nio.ch.WindowsSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:752)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
at org.jetbrains.jps.service.impl.SharedThreadPoolImpl.lambda$executeOnPooledThread$0(SharedThreadPoolImpl.java:42)
at org.jetbrains.jps.service.impl.SharedThreadPoolImpl$$Lambda$2/26379843.run(Unknown Source)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- <0x13d53080> (a java.util.concurrent.ThreadPoolExecutor$Worker)
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x314ed400 nid=0x2550 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C1 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x314e6800 nid=0x2214 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x314e5c00 nid=0x295c waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x314e2400 nid=0x4434 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x314ce400 nid=0x3a4 in Object.wait() [0x317cf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x13bc9100> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x13bc9100> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
Locked ownable synchronizers:
- None
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x314b8000 nid=0x1c1c in Object.wait() [0x3173f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x13bc92a0> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x13bc92a0> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Locked ownable synchronizers:
- None
"VM Thread" os_prio=2 tid=0x314b2800 nid=0xd4 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x3151dc00 nid=0x2684 waiting on condition
JNI global references: 315
//""中的是线程的名字,一般有主线程是“main”这样的
"VM Periodic Task Thread"
//这个是线程的状态,在上面的图中对应着每个不同的状态,下面就是等待的状态
java.lang.Thread.State: WAITING (on object monitor)
//带有daemon标识的就是守护线程,下面这个就是。
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x314b8000 nid=0x1c1c in Object.wait() [0x3173f000]
//线程是否处于同步块中
Locked ownable synchronizers:
- None
至此完……2017-12-10