1. 什么是多线程
多线程允许我们"同时"执行多段代码.
- 进程:程序的集合
- 线程:一个进程包含多个线程,java默认两个线程:main和GC线程
- java并不能开启线程,它是运行在JVM虚拟机,不能操作硬件,它底层通过C语言,开启的
- 并发编程的本质是:充分利用资源
//获取CPU的核数
//CPU密集型 IO 密集型
System.out.println(Runtime.getRuntime().availableProcessors());
并发运行:线程是并发运行的,线程调度会统一规划CPU时间,将 CPU的时间划分为若干片段,然后尽可能的均匀分配给所有要并发运行的线程,每个线程在获得CPU时间片后,CPU就来运行它的任务,当时间片用完后,CPU会离开并执行获取到CPU时间片的线程,所以所有线程并非真正的"同时"运行着代码,而都是走走停停的,这种微观上走走停停,宏观上感觉是同时运行的。
- CPU 单核 ,多个线程快速切换,其实是并发运行
- CPU多核,多个线程同时运行,是并行
2. 线程的两种创建方式
第一种:继承Thread并重写run方法来定义线程任务
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/*
* 注意,启动线程不要调用run方法,而是要
* 调用start方法.
* 线程开始并发运行时会自动调用run方法.
*/
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你找谁?");
}
}
}
class MyThread2 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("马冬梅!!!");
}
}
}
注意:启动线程不要调用run方法,而是要调用start方法.
线程开始并发运行时会自动调用run方法.
第二种 实现Runnable接口单独定义线程任务
public class ThreadDemo2 {
public static void main(String[] args) {
//创建任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建线程
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable{
public void run() {
for(int i=0;i<1000;i++){
System.out.println("你找谁?");
}
}
}
class MyRunnable2 implements Runnable{
public void run() {
for(int i=0;i<1000;i++){
System.out.println("马冬梅!!!");
}
}
}
第三种 实现Callable接口,重写call方法
public class CalableDemo implements Callable<Boolean>{
public static void main(String[] args) throws InterruptedException, ExecutionException {
CalableDemo cl1 = new CalableDemo();
CalableDemo cl2 = new CalableDemo();
CalableDemo cl3 = new CalableDemo();
//执行服务
ExecutorService es = Executors.newFixedThreadPool(3);
//提交线程,返回值
Future<Boolean> fu1 = es.submit(cl1);
Future<Boolean> fu2 = es.submit(cl2);
Future<Boolean> fu3 = es.submit(cl3);
System.out.println(fu1.get()+" "+fu2.get()+" "+fu3.get());
es.shutdown();
}
@Override
public Boolean call() throws Exception {
//可以抛出异常
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+":我是谁,我在哪!");
}
return true;
}
}
//Callable的第二种开启方式
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Mythread mythread = new Mythread();
//FutureTask间接实现了Runnable接口
FutureTask futureTask = new FutureTask(mythread);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//有缓存,还是只输出一个"Call()"
//get方法因为等待获取返回值,有可能造成阻塞,
//把它写在最后一行,避免影响 其它;
//或者 采用异步通信
String string = (String)futureTask.get();
System.out.println(String);
}
}
class Mythread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("call()");
return "123";
}
}
3. 第一种创建线程的方式有两个不足:
1:由于java是单继承,这就导致若继承了Thread,就不能再继承其他类,在实际开发中非常不方便,因为无法重用其他类的某些方法.
2:由于继承Thread后重写run方法定义了线程要执行的任务,这就导致线程与线程要执行的任务有一个必然的耦合关系,不利于线程重用.
4. 第三种有两个好处
1.可以抛出异常
2.有返回值
拓展知识点:
程序是静态的,启动起来成了进程,进程是动态的,进程需要线程来执行任务