一、概述
多线程相较于单线程任务,CPU调度更加灵活,比如在进行耗时的I/O任务时,会向磁盘读取内容,耗时较长,在这段时间内,如果是单线程任务,CPU就会处于空闲状态,而多线程CPU就可以在等待磁盘读写的时间内进行其他任务了。
CPU分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
CPU抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度非常快,看上去就是在同一时刻运行。
多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
线程的五个状态:
创建 创建一个线程对象时就绪,此时线程不可运行,Thread thread = new Thread();
就绪 线程对象创建完毕后,调用start()方法,进入线程队列随时准备运行(等待获取到cpu资源)
运行 start()方法线程运行,线程执行的是run()方法中的内容
堵塞 sleep()、suspend()方法,堵塞时线程不进入线程队列
死亡 stop()方法( 过时)或run()方法执行完毕
线程安全:在并发的情况下,该代码经过多线程使用,线程的调度顺序不影响任何结果,线程不安全指代码可能会因调度顺序出现问题。
二、实现多线程的两种方式
开始之前,先贴一篇文章--->多线程的应用场景
1.Thread类实现多线程
Thread类是一个抽象类,继承了Runnable接口,类中为操作线程的方法,每个Thread类的实例代表一个线程对象。
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(name+"运行,i="+i);
}
}
}
public class ThreadDemo1{
public static void main(String[] args) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
mt1.start();
mt2.start();
}
}
2.Runnable接口实现多线程
Runnable接口中只有一个run()方法。接口主要是为了避免Java的单继承性,即一个子类不能有多个父类。
class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
MyThread2 mt1 = new MyThread2();
MyThread2 mt2 = new MyThread2();
new Thread(mt1,"线程A").start();
new Thread(mt2,"线程B").start();
}
}
两种方法的区别:
区别在于Runnable()能够实现数据共享,也就是允许多个线程同时处理一份数据,而Thread()则不能,且在Thread()中,一个线程对象不能重复调用start()方法,否则会抛出IllegalThreadStateException()异常。在源码中对start()方法有以下说明:
/** *It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
**/
为什么不直接调用run()方法,而是调用start()?
run()方法只是一个普通方法,而start()方法中才是多线程操作,如果直接调用run(),只是当前线程执行run(),并非多线程操作。在start()方法中有一条add()语句,add()即是将线程加入到thread group中。
还可通过Callable 和 Future 创建线程。
线程操作方法:
join()可让线程强制运行,当此线程运行完毕(死亡后),其他线程才可以运行可指定时间,在指定时间内等待线程完成,单位毫秒
sleep()线程休眠,停止运行一段时间后继续运行
interupt()中断线程,中断join()wait()sleep()的线程会清空线程的interrupt status并抛出InterruptedException
setPriority() 线程优先级,优先级高的会优先调度,可从1到10,MAX_PRIORITY=10 MIN_PRIORITY=1 NORMAL_PRIORITY=5,线程默认的优先级为NORMAL
yield() 线程的礼让,调用yield()的线程不执行,让其他线程执行,这个方法源码中有句注释/*It is rarely appropriate to use this method. It may be useful for debugging or testing purposes*/。。。。
三、同步和死锁
当多个线程操作同一份数据时,可能会出现问题。以下面的代码为例:
class Ticket implements Runnable {
public int ticket = 5;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (ticket > 0) {
try {
Thread.sleep(300);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + ticket--);
}
}
}
}
public class Thread01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "线程A").start();
new Thread(ticket, "线程B").start();
new Thread(ticket, "线程C").start();
}
}
会出现票数为0和-1的情况,这样的原因是当ticket为1时,某一线程在ticket--之前休眠了300ms,在这段时间内有其他线程也来处理这一数据,执行了ticket--的操作,这样上一线程再处理时ticket成为0,在ticket--操作就变为负数。
可以用同步的方法解决,加入synchronized代码块或定义synchronized方法。
过多的同步可能会造成死锁,尤其在同步锁中嵌套同步锁。死锁就是两个线程陷入互相等待的境地,使程序不能向下进行。
综上,多个线程共享同一资源时需要同步,但要注意过多的同步会带来死锁问题。