一 线程的创建和启动
方式1:继承Thread类
编程简单,但是Java是单继承的.无法再继承其他类.
1.自定义一个类继承Thread类,这个类就是多线程类.
2.[重写]Thread类里面的run方法,把需要执行的自定义线程代码放入这个run方法.
3.创建线程类对象
4.开启线程,使用start()方法
先创建一个桌子类,继承自Thread
public class DeskThread extends Thread{
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("Desk" + i);
}
}
}
再创建另一个椅子类,也继承自Thread
public class ChairThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Chair" + i);
}
}
}
在主线程中将上述两个线程启动
public class TestThread {
public static void main(String[] args) {
System.out.println("Strat");
Thread desk = new DeskThread();
desk.setName("ChairThread");
desk.start();
Thread chair = new ChairThread();
chair.start();
System.out.println("End");
}
}
运行结果:
Strat
Desk0
Desk1
Chair0
Chair1
Chair2
Chair3
Chair4
Chair5
Chair6
Chair7
Chair8
Chair9
End
Desk2
Desk3
Desk4
Desk5
Desk6
Desk7
Desk8
Desk9
Process finished with exit code 0
可以发现运行结果中,桌子线程 椅子线程交替出现, End字符串也并不一定是最后出现,说明了主线程 椅子线程 桌子线程 三个线程互相竞争CPU资源,也就是并行处理。如果将start()改为run(),就只剩主线程一个线程了,就是串行处理了。
只用重写run方法,而不要重写start方法
方式2:实现Runnable接口
Runnable接口只有一个抽象方法:run(),这样的创建线程的方式风险性更低,也更解耦.
而且由于是函数式接口,可以使用Lambda表达式.
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
1.自定义一个类,[实现]Runnable接口
2.重写Runnable接口中唯一的方法run(),把线程的代码写入到run()
3.创建Thread类对象,并且把[实现]Runnable接口的自定义类对象作为参数传入到Thread构造方法中
这里可以看下Thread类的构造方法接受那些参数,一般用这两种:Thread(Runnable target),Thread(Runnable target, String name)
4.调用Thread类对象的start方法,开启线程
[优点]: 可以继承其他类,更方便多个线程共享一个资源 ,只有一个抽象方法,使用风险小.
[缺点]: 编程稍微复杂.需要将定义的实现类作为参数传入到Thread的构造方法中.
首先编写一个实现Runnable接口的类,桌子线程
public class DeskTask implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "::第" + i + "个");
}
}
}
再编写另一个类做对比,椅子线程
public class ChairTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "::第" + i + "个");
}
}
}
在主线程里面,要创建Thread类对象,将上述两个线程分别“注入”到线程当中.最后启动线程
public class TestRunnable {
public static void main(String[] args) {
System.out.println("Start");
Thread t1 = new Thread(new DeskTask(), "桌子线程");
t1.start();
Thread t2 = new Thread(new ChairTask(),"椅子线程");
t2.start();
System.out.println("End");
}
}
结果:
Start
桌子线程::第0个
End
桌子线程::第1个
椅子线程::第0个
桌子线程::第2个
椅子线程::第1个
桌子线程::第3个
桌子线程::第4个
桌子线程::第5个
椅子线程::第2个
椅子线程::第3个
椅子线程::第4个
椅子线程::第5个
椅子线程::第6个
椅子线程::第7个
椅子线程::第8个
椅子线程::第9个
桌子线程::第6个
桌子线程::第7个
桌子线程::第8个
桌子线程::第9个
Process finished with exit code 0
创建方式3:实现Callable接口
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
JDK1.5 之后的新特性
需要导入包
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
1.自定义一个类,[实现]Callable接口
2.实现Callable接口中唯一方法call().
3.call()方法的特点:有返回值,抛出异常,支持泛型,如果没有返回值,如果没有抛出检查异常,建议使用方式1或2
4.创建一个FutureTask对象,将上面的实现类对象作为参数传入FutureTask类对象,创建Thread类对象,将FutureTask类对象作为参数传入Thread类对象.
5.调用Thread类对象的start方法,开启线程
FutureTask 实现了RunnableFuture接口,
RunnableFuture 继承了 Runnable 接口和 Future 接口
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
二 Thread 常用方法
Thread(); 无参构造方法,
Thread(Runnable target); 分配新的Thread对象
Thread(Runnable target, String name); 分配新的Thread对象
Thread(String name); 分配新的Thread对象
Thread(ThreadGroup group, Runnable target); 分配新的Thread对象,指定所属线程组
Thread(ThreadGroup group, Runnable target, String name); 分配新的Thread对象,以便将target作为运行对象.指定所属线程组,并指定名称
Thread(ThreadGroup group, String name);
run(); 线程体
start(); 开启自定义线程,执行线程中的run方法
setName(String name); 修改线程的名字
getName(); 获取线程的名字
static sleep(); 通过Thread类名调用,这里需要处理一些异常.要求当前线程停止固定毫秒
static currentThread(); 返回当前的线程对象,返回值:[线程名,优先级,线程组名]
getPriority(); 返回当前线程的优先级CPU执行的优先级,不是绝对的
setPriority(); 设置线程的优先级
[注]: 线程优先级等级越高,执行概率越高
三 线程的生命周期
线程的生命周期:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。
这张图还是挺重要的,能够帮助我们理解JVM处理Thread的过程.其中 t 是主线程 Thread 是我们新建的线程, o 是同步监听器对象.
四 线程同步
方式1 同步代码块
使用synchronized关键字:
synchronized(同步监视器) {
需要同步的代码块
}
1.同步监视器必须是引用数据类型,不能是基本数据类型,也最好不是String和Integer.
2.被锁住的对象可以改变值,但不会改变引用,所以建议用final修饰.
3.sleep()不会释放锁对象,不会开锁.
4.线程安全,但开销大.效率低,甚至出现“死锁”的现象.
方式2 同步代码
直接对方法进行同步
public void run() {
method();
}
public synchronized void method() {
需要同步的代码
}
1.不要将run()置为同步方法,否则就变成串行了
2.此时同步监视器是 this. 所以一个同步方法被锁定,当前类的所有同步方法都被锁定
方式3 Lock
JDK1.5的新特性,具体方法在另一篇博客中有介绍https://www.cnblogs.com/dolphin0520/p/3923167.html