我们先说一下为什么要讲多线程和高并发?
原因是,你想拿到一个更高的薪水,在面试的时候呈现出了两个方向的现象:
第一个是上天
- 项目经验
- 高并发 缓存 大流量 大数据量的架构设计
第二个是入地
- 各种基础算法,各种基础的数据结构
- JVM OS 线程 IO等内容
多线程和高并发,就是入地里面的内容。
基本概念
我们先从线程的基本概念开始,给大家复习一下,不知道有多少同学是基础不太好,说什么是线程都不知道的,如果这样的话,花时间去补初级内容的课。
什么是叫一个进程? 什么叫一个线程?
Program app ->QQ.exe
**进程:**做一个简单的解释,你的硬盘上有一个简单的程序,这个程序叫QQ.exe,这是一个程序,这个程序是一个静态的概念,它被扔在硬盘上也没人理他,但是当你双击它,弹出一个界面输入账号密码登录进去了,OK,这个时候叫做一个进程。进程相对于程序来说它是一个动态的概念
**线程:**作为一个进程里面最小的执行单元它就叫一个线程,用简单的话讲一个程序里不同的执行路径就叫做一个线程
示例:什么叫做线程
package com.mashibing.juc.c_000;
import java.util.concurrent.TimeUnit;
public class T01_WhatIsThread {
private static class T1 extends Thread {
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e)
{ e.printStackTrace();
}
System.out.println("T1");
}
}
}
public static void main(String[] args) {
//new T1().run();
new T1().start();
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e)
{ e.printStackTrace();
}
System.out.println("main");
}
}
}
观察上面程序的数据结果,你会看到字符串“T1”和“Main”的交替输出,这就是程序中有两条不同的执行路径在交叉执行,这就是直观概念上的线程,概念性的东西,理解就好,没有必要咬文嚼字的去背文字的定义。
创建线程的几种方式
package com.mashibing.juc.c_000;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class T02_HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
static class MyCall implements Callable<String> {
@Override
public String call() {
System.out.println("Hello MyCall");
return "success";
}
}
//启动线程的5种方式
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRun()).start();
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();
Thread t = new Thread(new FutureTask<String>(new MyCall()));
t.start();
ExecutorService service = Executors.newCachedThreadPool();
service.execute(()->{
System.out.println("Hello ThreadPool");
});
service.shutdown();
}
}
分享一道面试题
请你告诉我启动线程的三种方式 ?
你说第一个:new Thread().start(); 第二个: new Thread(Runnable).start() 这没问题 ;那第三个呢,要回答线程池也是用的这两种之一,他这么问有些吹毛求疵的意思,你就可以说通过线程池也可以启动一个新的线程 3:Executors.newCachedThreadPool()或者FutureTask + Callable
我们来认识几个线程的方法
package com.mashibing.juc.c_000;
public class T03_Sleep_Yield_Join {
public static void main(String[] args) {
//testSleep();
//testYield();
testJoin();
}
/*Sleep,**意思就是睡眠,当前线程暂停一段时间让给别的线程去运行。**Sleep**是怎么复活的?由你的睡眠时间而定,等睡眠到规定的时间自动复活***/
static void testSleep() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e)
{ e.printStackTrace();
}
}
}).start();
}
/*Yield,就是当前线程正在执行的时候停止下来进入等待队列,回到等待队列里在系统的调度算法里头呢还是依然有可能把你刚回去的这个线程拿回来继续执行,当然,更大的可能性是把原来等待的那些拿出一个来执行,所以yield的意思是我让出一下CPU,后面你们能不能抢到那我不管*/
static void testYield() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("------------B" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
}
/*join, 意思就是在自己当前线程加入你调用Join的线程(),本线程等待。等调用的线程运行完了,自己再去执行。t1和t2两个线程,在t1的某个点上调用了t2.join,它会跑到t2去运行,t1等待t2运行完毕继续t1运行(自己join自己没有意义) */
static void testJoin() {
Thread t1 = new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(()->{
try {
t1.join();
} catch (InterruptedException e) { e.printStackTrace();
}
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e)
{ e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
线程状态
常见的线程状态有六种:
当我们new一个线程时,还没有调用start()该线程处于新建状态
线程对象调用 start()方法时候,他会被线程调度器来执行,也就是交给操作系统来执行了,那么操作系统来执行的时候,这整个的状态叫Runnable,Runnable内部有两个状态**(1)Ready就绪状态/(2)Running运行状态**。就绪状态是说扔到CPU的等待队列里面去排队等待CPU运行,等真正扔到CPU上去运行的时候才叫Running运行状态。(调用yiled时候会从Running状态跑到Ready状态去,线程配调度器选中执行的时候又从Ready状态跑到Running状态去)
如果你线程顺利的执行完了就会进去**(3)Teminated****结束状态**,(需要注意Teminated完了之后还可不可以回到new状态再调用start?这是不行的,完了这就是结束了)
在Runnable这个状态里头还有其他一些状态的变迁**(4)TimedWaiting等待、(5)Waiting等待、(6)Blocked阻塞**,在同步代码块的情况就下没得到锁就会阻塞状态,获得锁的时候是就绪状态运行。在运行的时候如果调用了o.wait()、t.join()、LockSupport.park()进入Waiting状态,调用o.notify()、o.notifiAll()、LockSupport.unpark()就又回到Running状态。TimedWaiting按照时间等待,等时间结束自己就回去了,Thread.sleep(time)、o.wait(time)、t.jion(time)、LockSupport.parkNanos()、LockSupport.parkUntil()这些都是关于时间等待的方法。
问题1:哪些是JVM管理的?哪些是操作系统管理的?
上面这些状态全是由JVM管理的,因为JVM管理的时候也要通过操作系统,所以呢,那个是操作系统和那个是JVM他俩分不开,JVM是跑在操作系统上的一个普通程序 。
问题2:线程什么状态时候会被挂起?挂起是否也是一个状态?
Running的时候,在一个cpu上会跑很多个线程,cpu会隔一段时间执行这个线程一下,在隔一段时间执行那个线程一下,这个是cpu内部的一个调度,把这个状态线程扔出去,从running扔回去就叫线程被挂起,cpu控制它。
来看一下ThraedState这段代码。
package com.mashibing.juc.c_000;
public class T04_ThreadState {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(this.getState());
for(int i=0; i<10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e)
{ e.printStackTrace();
}
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread t = new MyThread();
//怎么样得到这个线程的状态呢?就是通过getState()这个方法
System.out.println(t.getState());//他是一个new状态
t.start();//到这start完了之后呢是Runnable的状态
try {
t.join();
} catch (InterruptedException e)
{ e.printStackTrace();
}
//然后join之后,结束了是一个Timenated状态
System.out.println(t.getState());
}
}