一、方式1:继承Thread类
步骤:
- 创建一个继承于Thread类的子类
- 重写Thread类的run()方法 ----> 此线程执行的操作声明在方法体中
- 创建当前Thread子类的对象
- 通过实例对象调用start()方法,启动线程 ----> Java虚拟机会调用run()方法
注意main()方法是主线程
1. 创建线程:
//自定义线程类
public class MyThread extends Thread {
// 共享数据要放在run()方法外边才能被共享且声明为static,否则就是每个线程都会调用run()方法,都会单独拥有一个run()方法里的独享数据,而非共享数据
//eg: static int trick = 100;
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
// 执行的操作
}
}
2. 调用线程:
public class TestMyThread {
public static void main(String[] args) {
//创建自定义线程对象1
MyThread mt1 = new MyThread("子线程1");
//开启子线程1
mt1.start();
//创建自定义线程对象2,分别创建对象开启线程,不可以数据共享,若要共享需要创建static变量
MyThread mt2 = new MyThread("子线程2");
//开启子线程2
mt2.start();
}
}
3. 创建Thread类的匿名子类的匿名对象
// 创建Thread类的匿名子类的匿名对象,并启动线程
new Thread(){
public void run(){
// 执行的操作
}
}.strat(); // 启动线程
二、方式2:实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现接口中的run()方法 ----> 线程执行的操作声明在此方法中
- 创建实例对象
- 将此对象作为参数传到Thread类的构造器中,创建Thread类的实例
- 通过Thread的实例对象调用strat()方法,启动线程 ----> Java虚拟机会调用run()方法
最终还是通过Thread实现的
1. 创建线程:
public class MyRunnable implements Runnable {
// 共享数据要放在run()方法外边才能被共享,否则就是每个线程都会调用run()方法,都会单独拥有一个run()方法里的独享数据,而非共享数据
//eg: int trick = 100;
@Override
public void run() {
// 执行的操作
}
}
2. 调用线程
public class TestMyRunnable {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象1
Thread t1 = new Thread(mr, "长江1");
t1.start();
//创建线程对象2,注意两个线程传入的是同一个对象,可以实现数据共享
Thread t2 = new Thread(mr, "长江2");
t2.start();
}
}
3. 创建Runnable接口的匿名子类的匿名对象
new Thread(new Runnable(){
public run(){
// 执行的操作
}
}).start(); // 开启线程
三、继承Thread类 VS 实现Runnable接口
1. 共同点:
- 都是通过Thread类中定义的start()来启动线程。
- 都是通过Thread类或其子类的实例对象来创建线程。
2. 不同点:
- Thread是继承
- Runnable是实现
3. Runnable好处:
- 通过实现的方式,避免了类的单继承的局限性
- 自动共享数据,更适合处理有共享数据的业务逻辑
- 实现了逻辑代码(在run()方法中)和数据(在创建线程的方法中)的分离
四、Thread类常用方法和生命周期
1. 构造器
构造器 | 描述 |
---|---|
public Thread() | 分配一个新的线程对象。 |
public Thread(String name) | 分配一个指定名字的新的线程对象。 |
public Thread(Runnable target) | 指定创建线程的目标对象,它实现了Runnable接口中的run方法 |
public Thread(Runnable target,String name) | 分配一个带有指定目标新的线程对象并指定名字。 |
2. 常用方法——线程设置
构造器 | 描述 |
---|---|
public void run() | 此线程要执行的任务在此处定义代码。 |
public void start() | 导致此线程开始执行; Java虚拟机调用此线程的run方法。 |
public String getName() | 获取当前线程名称。 |
public void setName(String name) | 设置该线程名称。 |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类 |
public static void sleep(long millis) | 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。 |
public static void yield() | 主动释放当前线程的执行权,一旦执行,就释放CPU的执行权,yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。 |
3. 常用方法——阻塞线程
构造器 | 描述 |
---|---|
public final boolean isAlive() | 测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。 |
void join() | 阻塞其他线程,在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态。等待该线程终止。 |
void join(long millis) | 阻塞其他线程,阻塞其他线程的时间最长为 millis 毫秒。如果millis时间到,将不再等待。 |
void join(long millis, int nanos) | 阻塞其他线程,阻塞其他线程的时间最长为 millis 毫秒 + nanos 纳秒。 |
public final void stop() | 已过时,不建议使用。强行结束一个线程的执行,直接进入死亡状态。run()即刻停止,可能会导致一些清理性的工作得不到完成,如文件,数据库等的关闭。同时,会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。 |
void suspend() / void resume() | 这两个操作就好比播放器的暂停和恢复。二者必须成对出现,否则非常容易发生死锁。suspend()调用会导致线程暂停,但不会释放任何锁资源,导致其它线程都无法访问被它占用的锁,直到调用resume()。已过时,不建议使用。 |
4. 常用方法——优先级
每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。
构造器 | 描述 |
---|---|
MAX_PRIORITY(10)三个优先级常量之一 | 最高优先级 |
MIN _PRIORITY (1)三个优先级常量之一 | 最低优先级 |
NORM_PRIORITY (5)三个优先级常量之一 | 普通优先级,默认情况下main线程具有普通优先级。 |
public final int getPriority() | 返回线程优先级 |
public final void setPriority(int newPriority) | 改变线程的优先级,范围在[1,10]之间。 |
5. 线程的生命周期
jdk1.5 之前的5种状态:
jdk1.5 之后的6种状态:
- 将准备和运行合并为一个Runnable状态
- 将阻塞细分为:计时等待、锁阻塞、无线等待3种状态
五、解决线程的安全问题
当多个线程同时操作同一个共享数据时,就会出现线程的安全问题。
方式一:同步代码块
/**
* 1.同步监视器,俗称锁。哪个线程获得了锁,哪个线程就能执行该代码块
* 2.同步监视器可以是任何一个类的对象。但是,多个线程必须共用同一个同步监视器,且该对象是唯一的(只有一个实例对象)。
* >可以自定义一个专用于线程锁的类,然后在这里创建对象作为锁使用。
* >可以使用this充当锁,this指向当前对象。但是要注意当前类是否只创建了一个对象(保证唯一性)
* >在继承Thread的方式中不可以使用this,因为每一个线程都需要创建对象,不满足唯一性
* >在继承Thread的方式中,创建对象作为锁也需要声明为static的才可以,static Object obj = new Pbject();
* >可以使用“当前类.class”充当锁。因为“Class clz = 类.class”是一个类,仅加载一次,是全局唯一的(反射),该方式在实现Runnable中也可以使用
*
*/
synchronized(同步监视器){
// 需要被同步的代码,即操作共享数据的代码
}
方式二:同步方法
/*
*用synchronized 直接修饰操作同步代码的方法
* 1.在非static的方法中,默认锁是:this。锁无法修改,因而在继承Thread方法中不适用,可以改成同步代码块方式,手动添加将当前类做为锁
* 2.在static的方法中,默认的锁是:当前类.class。即当前类本身。
*
*/
// 示例代码:在run()方法中调用show()方法
public synchronized void show(){
// 操作共享数据的代码
}