1 并发与并行
并行:在同一时刻,有多个指令在多个cpu上同时执行。
并发:在同一时刻,有多个指令在单个cpu上交替执行。
2 进程与线程
进程:正在运行的程序。
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
线程:是进程中单个顺序的控制流,是一条执行路径。
单线程:一个进程只有一条执行路径,则称为单线程程序。
多线程:一个进程如果有多条执行路径,则称为多线程程序。
3 实现多线程的方法
3.1方式一:继承Thread类
(1)介绍thread类
thread extends Object
thread implements Runnable
Thread的构造方法
Tread(Runnable target)
Tread(Runnable target,String name)
(2)具体步骤:创建一个类去继承Thread类,这个子类去重写Thread中的run()方法,然后分配并启动子类的实例。
public class Demo1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
public class Test01 {
public static void main(String[] args) {
Demo1 d1 =new Demo1();
Demo1 d2 =new Demo1();
d1.start();
d2.start();
}
}
☆为什么要重写run()方法?
因为run()是用来封装被线程执行的代码。
☆run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用。
start():启动线程,然后由jvm调用此线程的run()方法。
3.2方式二:实现Runnable接口
(1)具体步骤:声明一个实现Runnable接口的类,那个类然后实现run方法,然后可以分配类的实例,在创建Thread对象时作为参数传递,并启动。
(2)Thread的构造方法
Tread(Runnable target)
Tread(Runnable target,String name)
public class Demo2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//不能直接使用getName方法,因为Demo2这个类是实现Runnable这个接口。
//并没有继承Thread这个方法。
//可以通过拿到当前执行的线程。
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class Test2 {
public static void main(String[] args) {
Demo2 d1 =new Demo2();
Thread t1 = new Thread(d1,"火车");
Thread t2 = new Thread(d1,"汽车");
t1.start();
t2.start();
}
}
3.3方式三:实现Callable接口
(1)具体步骤:首先敲写一个实现Callable接口的类。在main方法内new一个这个类的对象,然后把生成的这个实例对象注册到FutureTask类中,然后将FutureTask类的实例注册进入Thread中运行。最后可以采用FutureTask<V>中的get方法获取自定义线程的返回值。
(2)原理:我们可以查看Callable接口的源码,发现只有一个call()方法的定义.FutureTask<V>实现了RunnableFuture<V>的接口,这个RunnableFuture<V>接口也实现了,Runnable这个接口。FutureTask<V>中还有一个需要传入Callable<V>类型参数的构造方法,且会赋值给FutureTask<V>的私有属性。无论我们以怎样的形式去实现多线程,都需要调用Tread类中的Start()方法去向操作系统请求io和cpu等资源,Tread有参构造中需传入一个Runnable类型的接口参数,而FutureTask<V>的实例正可以充当这个参数传入Tread。
ps:get()方法是FutureTask<V>类中的方法,运用可能需要抛异常。
public class Demo3 implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i <10 ; i++) {
System.out.println("多线程方式3 "+i);
}
return "结束了";
}
}
public class Test03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Demo3 d =new Demo3();
FutureTask<String> ft =new FutureTask<>(d);
Thread t1 =new Thread(ft);
t1.start();
String s = ft.get();
System.out.println(s);
}
}
4设置和获取线程的名称
Tread类中提供了两个方法用来设置和获取线程名称
void setName(String name) 将此线程的名称更改为等于参数的name
String getName() 返回此线程的名称
Thread currentThread() 返回对当前正在执行的线程对象
ps:如果在Demo4类中输出中调用了Tread类中的getName方法而在Test04中并没有命名,则系统会按照自己的源码给一个名字,例如“Demo-0:0或 Demo-1 :0”等等。
public class Demo4 extends Thread{
public Demo4() {
}
public Demo4(String name) {
super(name);
}
@Override
public void run() {
for (int i= 0; i <5; i++) {
System.out.println(getName()+":"+i);
}
}
}
public class Test04 {
public static void main(String[] args) {
//命名方法一:调用有参构造
Demo4 d1 =new Demo4("飞机");
Demo4 d2 =new Demo4("高铁");
/*命名方法二:调用无参构造
Demo4 d1 =new Demo4();
Demo4 d2 =new Demo4();
d1.setName("飞机");
d2.setName("高铁");
*/
d1.start(); d2.start();
}
}
5 线程的常见控制方法
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数。
void join() 等待这个线程死亡
Void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,
Java虚拟机将退出
5.1线程休眠
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数。
public class Demo5 extends Thread{ 运行结果
public Demo5() { 刘备+0
} 孙权+0
public Demo5(String name) { 曹操+0
super(name); 孙权+1
} 刘备+1
@Override 曹操+1
public void run() { 孙权+2
for (int i = 0; i < 5; i++) { 曹操+2
/*Tread表示的是当前运行的线程停留1000毫秒。*/ 刘备+2
try { *
Thread.sleep(1000); *
} catch (InterruptedException e) { *
e.printStackTrace();
}
System.out.println(getName()+"+"+i);
}
}
}
public class Test5 {
public static void main(String[] args) {
Demo5 d1 =new Demo5("曹操");
Demo5 d2 =new Demo5("刘备");
Demo5 d3 =new Demo5("孙权");
d1.start();
d2.start();
d3.start();
}
}
总结:这三个人在执行的时候也在抢cpu的使用权。
5.2 线程死亡
void join() 只有该线程执行完之后,才会执行其他线程。
public class Demo5 extends Thread{
public Demo5() {
}
public Demo5(String name) {
super(name);
}
@Override
public void run() {
for (int i= 0; i <5; i++) {
System.out.println(getName()+":"+i);
}
}
}
public class Test5 {
public static void main(String[] args) throws InterruptedException {
Demo5 d1 =new Demo5("皇上");
Demo5 d2 =new Demo5("大阿哥");
Demo5 d3 =new Demo5("二阿哥");
d1.start();
d1.join();
//只有皇上执行完之后,两位阿哥才有机会去执行。
d2.start();
d3.start();
}
}
5.3守护线程
Void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,
Java虚拟机将退出(不是立即退出,会挣扎一会)
public class Demo5 extends Thread{
public Demo5() {
}
public Demo5(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"+"+i);
}
}
}
public class Test5 {
public static void main(String[] args) {
Demo5 d1 =new Demo5("皇上");
Demo5 d2 =new Demo5("大阿哥");
Demo5 d3 =new Demo5("二阿哥");
d3.setDaemon(true);
d2.setDaemon(true);
d1.start();
d2.start();
d3.start();
}
}
6线程的调度
6.1线程调度方式:
(1)分时调度模型:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间片
(2)抢占式调度模型:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选一个,优先级高的线程获取的cpu时间片相对多一些。
Java使用的是抢占式调度模型。
6.2Tread类中获取和设置优先级的方法
ps:线程的优先级高仅仅表示线程获取的cpu时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果。
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
public class Demo6 implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
return "程序完毕";
}
}
public class Test06 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Demo6 d1 =new Demo6();
FutureTask<String> ft =new FutureTask<>(d1);
Thread t1 =new Thread(ft);
t1.setName("飞机");
t1.setPriority(9);
t1.start();
Demo6 d2 =new Demo6();
FutureTask<String> ft2 =new FutureTask<>(d2);
Thread t2 =new Thread(ft2);
t2.setName("高铁");
t2.setPriority(1);
t2.start();
System.out.println(t1.getPriority());
String s = ft2.get();
System.out.println(s);
}
}
7三种实现多线程方式的总结
(1)实现Runnable,Callable接口的方式
好处:拓展性强,实现该接口的同时还可以继承其他的类,而且Callable接口的call方法还会有返回值
缺点:代码复杂,不能直接使用Thread类中的方法
(2)继承Thread类
好处:代码简单,可以直接使用Thread类中的方法
缺点:拓展性较差,不能再继承其他的类。