一、进程与线程
二、线程的实现
创建新执行线程有两种方法:
1.将类声明为Thread的子类,该子类重写Thread类的run方法。
2.声明实现Runnable接口的类,该类然后实现run方法。
1.继承Thread类
通过继承Thread类来创建并启动多线程的一般步骤如下:
1】定义一个类,继承Thread
2】重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
3】创建Thread子类的实例,也就是创建了线程对象
4】启动线程,即调用线程的start()方法
注:
创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。
而运行的指定代码就是这个执行路径的任务。
jvm创建的主线程的任务都定义在主函数中。
而自定义的线程它的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的,所以Thread类也是对任务的描述。
这个任务就是通过Thread类中的run方法来体现,也就是说,run方法就是封装自定义线程运行任务的函数。
run方法中定义的就是线程要运行的任务代码。
小结:开启线程是为了运行指定代码,所以只有继承thread类并复写run方法,将运行的代码定义在run方法中即可。
测试:
public class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(name+":"+i);
}
super.run();
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread("A");
MyThread t2 = new MyThread("B");
// t1.run();
// t2.run();
//线程的启动是通过start()
t1.start();
t2.start();
}
}
扩展:
1.可以通过Thread的getName获取线程的名字,Thread-编号(从0开始)
2.主线程的名字就是main:Thread.currentThread().getName()
getName():返回该线程的名字
currentThread():返回对当前正在执行的线程对象的引用
注意:线程的启动是用start()方法
调用start()和run()有什么区别。
(1)start()方法启动线程,真正实现了多线程运行,这时无需等待run()方法体代码执行完毕而直接继续执行下面的代码。然后通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。然后通过此Thread类调用run()方法来完成其运行操作,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run()方法运行结束,此线程终止,而CPU再运行其它线程。
(2)run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。而如果直接用Run方法,这只是调用一个方法而已,程序中依然只有主线程--这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。
如果使用run()方法,打印结果是:(这并不是并发的)
A:0
A:1
A:2
A:3
A:4
A:5
A:6
A:7
A:8
A:9
B:0
B:1
B:2
B:3
B:4
B:5
B:6
B:7
B:8
B:9
如果使用start()方法,打印结果是:(谁抢到CPU资源谁先执行,实现了并发机制)
A:0
B:0
B:1
B:2
B:3
B:4
A:1
B:5
A:2
B:6
A:3
B:7
A:4
A:5
B:8
B:9
A:6
A:7
A:8
A:9
2.实现Runable接口
通过实现Runnable接口创建并启动线程一般步骤如下:
1】定义Runnable接口的实现类
2】重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
3】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
4】依然是通过调用线程对象的start()方法来启动线程
测试:
public class MyRunnable implements Runnable{
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(name+":"+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyRunnable r1 = new MyRunnable("A");
MyRunnable r2 = new MyRunnable("B");
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
打印结果:
A:0
B:0
A:1
B:1
A:2
B:2
A:3
B:3
A:4
B:4
A:5
B:5
A:6
A:7
A:8
A:9
B:6
B:7
B:8
B:9
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
@Override
public void run() {
if (target != null) {
target.run();
}
}
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
3.实现Callable接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
三、线程的状态
线程在一定条件下,状态会发生变化。java语言定义了5种线程状态,在任意一个时间点,一个线程只能有且只有其中一种状态。
1、新建(New):创建后尚未启动的线程处于这个状态。
2、可运行(Runnable):Runnable包括了操作系统线程状态中的Running和ready,也就是处于此状态的线程有可能执行,也有可能正在等待着CPU为它分配执行时间。
3、无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,它们要等待被其他线程显式地唤醒。以下方法会让线程陷入无限期的等待状态。
进入方法 | 退出方法 |
---|---|
没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
LockSupport.park() 方法 | LockSupport.unpark(Thread) |
4、限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态。
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 | 时间结束 |
设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | LockSupport.unpark(Thread) |
LockSupport.parkUntil() 方法 | LockSupport.unpark(Thread) |
调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。
5、阻塞(Blocked):线程被阻塞了,。在程序等待进入同步区域的时候,线程将进去这种状态。
“阻塞状态”与“等待状态”的区别是:
“阻塞状态”在等待着获取到一个排他锁,这个事件将在另一个线程放弃这个锁的时候发生;
而“等待状态”则是在等待一段时间,或者唤醒动作的发生
6、结束(Terminated):已终止线程的线程状态,线程已经结束执行。
四、线程的常用方法
下表列出了Thread类的一些重要方法:
序号 | 方法描述 |
---|---|
1 | public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() 中断线程。 |
8 | public final boolean isAlive() 测试线程是否处于活动状态。 |
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。
序号 | 方法描述 |
---|---|
1 | public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
2 | public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
3 | public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
4 | public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 |
5 | public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。 |
五、线程之间的协作
参考:网址
join()
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
wait() notify() notifyAll()
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
它们都属于 Object 的一部分,而不属于 Thread。
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException。
使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
public class WaitNotifyExample {
public synchronized void before() {
System.out.println("before");
notifyAll();
}
public synchronized void after() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
WaitNotifyExample example = new WaitNotifyExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
before
after
wait() 和 sleep() 的区别
- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
- wait() 会释放锁,sleep() 不会。
await() signal() signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
使用 Lock 来获取一个 Condition 对象。
public class AwaitSignalExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
AwaitSignalExample example = new AwaitSignalExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
before
after
六、线程的优先级
1.优先级顺序设置
1-MIN_PRIORITY
10-MAX_PRIORITY
5-NORM_PRIORITY
如果什么都不设置默认值是5.
package threadtest;
class ThRun implements Runnable{
@Override
public void run() {
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":"+i);//获得线程的名称
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo03 {
public static void main(String[] args) {
Thread t1 = new Thread(new ThRun(),"A");
Thread t2 = new Thread(new ThRun(),"B");
Thread t3 = new Thread(new ThRun(),"C");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.NORM_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
}
}
C:0
B:0
A:0
C:1
A:1
B:1
C:2
B:2
A:2
C:3
A:3
B:3
C:4
B:4
A:4
七、同步与死锁
1.同步
解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待次线程完成之后才可以继续执行
线程进行同步,有以下两种方法:
(1)、同步代码块
synchronized(要同步的对象){
要同步的代码块;
}
(2)、同步方法
public synchronized void 方法名称(){
要同步的操作
}
测试一:使用代码块
class MyThreadDemo implements Runnable{
//三个窗口卖五张票
private int ticket = 5;
@Override
public void run() {
for(int i = 0;i < 10; i++){
if (ticket > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("车票:"+ ticket--);
}
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
MyThreadDemo m = new MyThreadDemo();
//三个窗口
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.start();
t2.start();
t3.start();
}
}
车票:5
车票:5
车票:5
车票:4
车票:3
车票:4
车票:2
车票:1
车票:1
说明没有资源共享,这需要同步,代码修改如下:
class MyThreadDemo implements Runnable{
//三个窗口卖五张票
private int ticket = 5;
@Override
public void run() {
for(int i = 0;i < 10; i++){
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("车票:"+ ticket--);
}
}
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
MyThreadDemo m = new MyThreadDemo();
//三个窗口
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.start();
t2.start();
t3.start();
}
}
车票:5
车票:4
车票:3
车票:2
车票:1
测试二:使用同步方法
class MyThreadDemo implements Runnable{
//三个窗口卖五张票
private int ticket = 5;
@Override
public void run() {
for(int i = 0;i < 10; i++){
tell();
}
}
public synchronized void tell(){
if (ticket > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("车票:"+ ticket--);
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
MyThreadDemo m = new MyThreadDemo();
//三个窗口
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.start();
t2.start();
t3.start();
}
}
车票:5
车票:4
车票:3
车票:2
车票:1
2.死锁