多线程原理
相当于玩游戏机
,只有一个游戏机
(cpu),可是有很多人要玩
,于是,start是排队
!等CPU选中你
就是轮到你,你就run()
,当CPU的运行的时间片执行完
,这个线程就继续排队
,等待下一次的run()
调用start()后
,线程会被放到等待队列
,等待CPU调度
,并不一定要马上开始执行
,只是将这个线程置于可动行状态
然后通过JVM,线程Thread会调用run()方法
,执行本线程的线程体
。
先调用start后调用run,这么麻烦,为了不直接调用run?就是为了实现多线程的优点,没这个start不行
多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发
start() 和 run()
1 start()
方法来 启动线程
,真正实现了多线程运行
。这时无需等待run方法体代码执行完毕
,可以直接继续执行下面的代码
;通过调用Thread类的start()方法来启动一个线程
, 这时此线程是处于就绪状态
, 并没有运行
。 然后通过此Thread类调用方法run()
来完成其运行
操作的, 这里方法run()称为线程体
,它包含了要执行的这个线程的内容
, Run方法运行结束, 此线程终止
。然后CPU再调度其它线程
。
2 run()
方法当作普通方法
的方式调用。程序还是要顺序执行
,要等待run方法体执行完毕后,才可继续执行下面的代码
; 程序中只有主线程
——这一个线程, 其程序执行路径还是只有一条
, 这样就没有达到写线程的目的。
class Runner1 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("进入Runner1运行状态——————————" + i);
}
}
}
class Runner2 implements Runnable { // 实现了Runnable接口,jdk就知道这个类是一个线程
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("进入Runner2运行状态==========" + i);
}
}
}
public class test0 {
public static void main(String[] args) {
Runner1 runner1 = new Runner1();
Runner2 runner2 = new Runner2();
// Thread(Runnable target) 分配新的 Thread 对象。
Thread thread1 = new Thread(runner1);
Thread thread2 = new Thread(runner2);
thread1.run();
thread2.run();
}
}
这时的运行结果是,线程1执行完毕后,线程2才执行
,因为这里并没有给线程排队
,而是直接调用的run(
)方法,相当于调用了一个普通的函数
一样, thread1.run()和thread2.run()顺序执行
public class test0 {
public static void main(String[] args) {
Runner1 runner1 = new Runner1();
Runner2 runner2 = new Runner2();
// Thread(Runnable target) 分配新的 Thread 对象。
Thread thread1 = new Thread(runner1);
Thread thread2 = new Thread(runner2);
thread1.start();
thread2.start();
}
}
当线程使用start()方法
时,就会出现两个线程的run()方法交替执行的状态
常见的几种方法
继承Thread类重写run()方法
Thread
本质上也是实现了Runnable接口
的一个实例,它代表一个线程的实例
,并且,启动线程的唯一方法
就是通过Thread类的start()实例方法
。start()
方法是一个native方法
,它将启动一个新线程,并执行run()方法
。这种方式实现多线程很简单,通过自己的类直接extend Thread
,并复写run()方法
,就可以启动新线程并执行自己定义的run()方法
public class MyThread extends Thread{
@OverWrite
public void run(){
//编写自己的线程代码
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println("Thread name : " + Thread.currentThread().getName());
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread myThread1 = new MyThread();
myThread1.setName("0");
MyThread myThread2 = new MyThread();
myThread2.setName("1");
myThread1.start();
myThread2.start();
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
ThreadDemo myThread1 = new ThreadDemo();
myThread1.setName("0");
MyThread myThread2 = new MyThread();
myThread2.setName("1");
myThread1.start();
myThread2.start();
}
}
class ThreadDemo extends Thread {
@SneakyThrows
@Override
public void run() {
Thread.sleep(100);
boolean flag = false;
for (int i = 3; i < 20; i++) {
flag = false;
for (int j = 2; j <= Math.sqrt(i); j++) {
if (i % j == 0) {
flag = true;
break;
}
}
if (flag == false) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
new Thread("thread1") {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}.start();
new Thread("thread2") {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}.start();
}
}
实现Runnable接口,重写run()方法
如果自己的类已经extends另一个类
,就无法直接extends Thread
,此时,必须实现一个Runnable接口
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
为了启动MyThread,需要首先实例化一个Thread
,并传入自己的MyThread实例
public class ThreadDemo02 {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread , "0").start();
}
}
public class ThreadTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}, "thread1").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}, "thread2").start();
}
}
实现Callable接口,重写call()方法,通过FutureTask包装器来创建线程
a: 创建Callable接口的实现类
,并实现Call方法
b: 创建Callable实现类的实现
,使用FutureTask类包装Callable对象
,该FutureTask对象封装了Callable对象的Call方法的返回值
c: 使用FutureTask对象作为Thread对象的target
创建并start()线程
d: 调用FutureTask对象的get()
来获取子线程执行结束的返回值
public class ThreadDemo03 {
public static void main(String[] args) {
Callable myCallable = new MyCallable<>();
FutureTask futureTask = new FutureTask<>(myCallable);
new Thread(oneTask).start();
System.out.println(futureTask.get());
}
}
class MyCallable implements Callable<Integer>{
//重写call方法
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
return 0;
}
}
这种方式需要借助FutureTask类来支持
。优于方法1、2的一点是可以添加返回值,并且可以抛出异常
但是需要注意的一点是,如果需要获取返回值result.get()这个方法是阻塞的
。所以你在创建线程和调用result.get()方法之间没有耗时操作
或者 没有其他线程
效果和顺序执行没有区别,还不如不创建线程。
public class TestCallable1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CallableDemo callableDemo = new CallableDemo();
FutureTask futureTask = new FutureTask<>(callableDemo);
new Thread(futureTask).start();
List<Integer> lists = (List<Integer>)futureTask.get(); //获取返回值
for (Integer integer : lists) {
System.out.print(integer + " ");
}
}
}
class CallableDemo implements Callable<List<Integer>>{
@Override
public List<Integer> call() throws Exception {
List<Integer> lists = new ArrayList<>();
for(int i = 0 ; i <= 10 ; i ++) {
if(i%2==0) {
lists.add(i);
}
}
return lists;
}
}
使用线程池创建线程
ExecutorService、Callable、Future
这个对象实际上都是属于Executor框架中的功能类
。
可返回值的任务
必须实现Callable接口
,类似的,无返回值的任务
必须实现Runnable接口
。
执行Callable任务后,可以获取一个Future的对象
,在该对象上调用get
就可以获取到Callable任务返回的Object
了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了
。
public class RunnableDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
// 创建一个固定大小的线程池:
ExecutorService es = Executors.newFixedThreadPool(3);
for (int i = 0; i < 6; i++) {
es.submit(new RunnableDemo());
}
// 关闭线程池:
es.shutdown();
}
}
public class ThreadDemo05{
private static int POOL_NUM = 10; //线程池数量
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i = 0; i < POOL_NUM; i++){
RunnableThread thread = new RunnableThread();
//Thread.sleep(1000);
executorService.execute(thread);
}
//关闭线程池
executorService.shutdown();
}
}
class RunnableThread implements Runnable {
@Override
public void run(){
System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName() + " ");
}
}
newFixedThreadPool
创建固定大小数量线程池
,数量通过传入的参数决定
newSingleThreadExecutor
创建一个线程容量的线程池
,所有的线程依次执行
,相当于创建固定数量为1的线程池
newCachedThreadPool
创建可缓存的线程池
,没有最大线程限制
(实际上是Integer.MAX_VALUE
)。如果用空闲线程等待时间超过一分钟,就关闭该线程
newScheduledThreadPool
创建计划(延迟)任务线程池
,线程池中的线程可以让其在特定的延迟时间之后执行
,也可以以固定的时间重复执行(周期性执行)
。相当于以前的Timer类的使用
newSingleThreadScheduledExecutor
创建单线程池延迟任务
,创建一个线程容量的计划任务
工作中学到
springboot 多线程实现
多线程可能带来的问题
同步与并发问题
并发:在同一时刻,有多个线程同时访问 某一个(一些)资源,带来数据的不安全性 、不稳定性、不确定性。
生活中例子
下课时,多个同学同时抢占同一坑位。
同步:用于解决并发问题,给予线程权限,允许具有权限的线程执行。
实现同步方法-锁
性质:唯一(static)
使用原则:锁越少越好 ,避免死锁发生
实现方法:
1.synchronized
2.Lock Condition
3.synchronized 方法名() {} :锁就是当前对象
多线程的同步:countdownlatch
生活中例子
坑位有锁,需锁开启。下课时,多个同学抢占同一坑位,仅有一位同学—甲有钥匙(锁即权限),在甲开锁(获取锁)占坑后,锁坑门(同步)。在甲完成如厕动作期间,其他同学仅能在厕所外等待。
执行权限
CPU通过时间片给予线程执行权限及时间,具有权限的线程才能执行
经典面试题
线程run方法执行顺序
线程执行start()的时候,会先执行自己的run()方法,只有当自己的run()为空的时候,才会去执行父类的runnable
new Thread(
new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
}
}
) {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2");
}
}
}.start();
我们把上面代码精简一下,可以得到如下代码:
new Thread(runnable.run()){ //只有当自己的run()为空的时候才会被执行
@OverWrite
run() //自己的run第一个被执行
}.start();
当target(Runnable)不为空的时候,run方法才会被执行
。
首先 Thread执行start()
的时候,会先去执行自己的run()方法
,只有当自己的run()为空
的时候,才会去执行父类的runnable
,因此上文被执行的是System.out.println("2"); 父类此时被子类覆盖了
。
如何一个类既继承了Thread类,又实现了Runnable接口。运行start方法的时候是执行重写Thread类里面的run()方法还是执行重写Runnable接口里面的run()方法?如下程序输出什么?
public static void main(String[] args) {
new Thread(()-> System.out.println("Runnable")){
@Override
public void run() {
System.out.println("Thread");
}
}.start();
}
输出的是 Thread