文章目录
线程创建
方法一 继承Thread类重写run方法
- 创建一个类继承Thread类
- 重写run()方法
- 创建Thread子类的对象
- 通过此对象调用start()
常用方法
- start() : 启动当前线程,调用当前线程的run()方法。
- run() : 通常需要重写此方法,将要创建的线程要执行的操作声明在此方法中。
- currentThread() : 静态方法,返回执行当前代码的线程。
- getName()/setName() : 获取/设置当前线程的名字。
- yield() : 释放当前CPU的执行权。但是释放后也有可能又被当前线程抢到。
- join() : 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
- stop() : 已过时。强制结束当前线程
- sleep(long millitime) : 在指定的millitime毫秒内,当前线程是阻塞状态
- isAlive() : 判断当前线程是否存活
线程优先级
-
MAX_PRIORITY : 10
MIN_PRIORITY : 1
NORM_PRIORITY : 5
-
如何获取和设置当前线程的优先级
getPriority()
setPriority()
方法二 实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
比较创建线程的两种方式
开发中:优先选择实现Runnable接口的方式。
原因:1. 实现的方式没有类的单继承的局限性。
2.实现的方式更适合来处理多个线程有共享数据的情况。
联系:Thread类也实现了Runnable接口
相同点:都需要重写run方法,将线程所要执行的逻辑声明在run中。
方法三:JDK5新增的创建方法——实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class NumThread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class Method3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
NumThread nt = new NumThread();
FutureTask futureTask = new FutureTask(nt);
Thread th = new Thread(futureTask);
th.start();
Object o = futureTask.get();
System.out.println(o);
}
}
如何理解实现Calable接口比实现Runnable接口创建多线程更强大?
- call方法有返回值
- call方法可以抛出异常,被外面的操作捕获
方法四:线程池
背景
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
##### 思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
好处
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(不用每次都创建新的线程)
- 便于线程管理
- corePoolSize:核心池的大小
- maximmumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- …
线程的生命周期
线程安全问题
经典的多个窗口卖车票问题
class MyThread implements Runnable {
private int sum = 100;
@Override
public void run() {
while (true) {
if(sum > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为 " + sum);
sum--;
} else {
break;
}
}
}
}
public class Test3 {
public static void main(String[] args) {
MyThread t = new MyThread();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
运行上述代码会出现如下这种情况:
...
Thread-2: 卖票,票号为 0
Thread-0: 卖票,票号为 -1
问题
卖票过程中出现重票、错票问题----出现了线程安全问题。
问题出现的原因
某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
如何解决
当线程a在操作车票时,其他线程不能参与进来,直到线程a操作完成时,其他线程才可以操作。
##### 方式一 同步代码块
synchronized(同步监视器) {
//需要被同步的代码
}
说明:1.操作共享数据(多个线程共同操作的变量,比如车票),即为需要同步的代码。
2.同步监视器,俗称,锁。任何一个类的对象都可以充当锁。
要求:多个线程共用同一把锁。
好处:解决了线程安全的问题
局限性:操作代码的同时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程方式中,慎用this,考虑使用当前类充当监视器
方式二 同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法设为同步的。声明方法时用synchronized。
-
同步方法仍然涉及到同步监视器,同步监视器是this。
-
非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是但前类本身
面试题
synchronized与lock的异同:
相同:二者都能解决线程安全的问题
不同:synchronized机制在执行完相应的同步代码后自动的释放同步监视器
lock需要手动的启动同步(lock()),手动的结束同步(unlock)
sleep()和wait()方法的异同
相同点:都可以使当前线程进入阻塞状态
不同点:1)声明位置不同:Thread类中声明sleep,Object类中声明wait
2)使用的要求不同:sleep可以在任何需要的场景下调用,wait必须在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁