1. 多线程概念
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2. 一个线程的生命周期
1、线程的5种状态:新建(New),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)
线程变化的状态转换图例如以下:
1)新建状态(New):新创建了一个线程对象。
2)就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()方法。该状态的线程位于可执行线程池中,变得可执行,等待获取CPU的使用权。
3)执行状态(Running):就绪状态的线程获取了CPU。执行程序代码。
4)堵塞状态(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。
堵塞的情况分三种:
(一)等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。则JVM会把该线程放入锁池中。
(三)其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。JVM会把该线程置为堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。
5)死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。
1)线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但无论如何,当我们new了这个对象后。线程就进入了初始状态;
2)当该对象调用了start()方法,就进入可执行状态;
3)进入可执行状态后,当该对象被操作系统选中。获得CPU时间片就会进入执行状态;
4)进入执行状态后情况就比较复杂了
(1)run()方法或main()方法结束后,线程就进入终止状态;
(2)当线程调用了自身的sleep()方法或其它线程的join()方法,就会进入堵塞状态(该状态既停止当前线程,但并不释放所占有的资源)。当sleep()结束或join()结束后。该线程进入可执行状态,继续等待OS分配时间片;
(3)线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到可执行状态,这时与其它进程处于同等竞争状态,OS有可能会接着又让这个进程进入执行状态;
(4)当线程刚进入可执行状态(注意,还没执行),发现将要调用的资源被synchroniza(同步),获取不到锁标记。将会马上进入锁池状态,等待获取锁标记(这时的锁池里或许已经有了其它线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可执行状态。等待OS分配CPU时间片;
(5)当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的全部资源,与堵塞状态不同)。进入这个状态后。是不能自己主动唤醒的,必须依靠其它线程调用notify()或notifyAll()方法才干被唤醒(因为notify()仅仅是唤醒一个线程,但我们由不能确定详细唤醒的是哪一个线程。或许我们须要唤醒的线程不可以被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池。等待获取锁标记。
3. 创建一个线程
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
3.1实现 Runnable 接口
创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。
实现了 Runnable 接口重写接口的run()
方法
- run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。 - 在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:
public Thread(Runnable targer)
此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。(start()
可以协调系统的资源)例如:
RunnableDemo runnableDemo = new RunnableDemo();
new Thread(runnableDemo).start();
- 一旦调用start()方法,则会通过JVM找到run()方法。
- 相较于通过继承
Thread
类来实现多线程,实现Runnable
接口更加灵活:
- 避免点继承的局限,一个类可以继承多个接口。
使用Runnable实现多线程可以达到资源共享目的
3.2继承Thread类
创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
- 继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
- 该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
Thread 方法
1 public void
start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public voidrun()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final voidsetName(String name)
改变线程名称,使之与参数 name 相同。
4 public final voidsetPriority(int priority)
更改线程的优先级。
5 public final voidsetDaemon(boolean on)
将该线程标记为守护线程或用户线程。
6 public final voidjoin(long millisec)
等待该线程终止的时间最长为 millis 毫秒。
7 public voidinterrupt()
中断线程。
8 public final booleanisAlive()
测试线程是否处于活动状态。
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。
下面的方法是Thread类的静态方法。
1 public static void
yield()
暂停当前正在执行的线程对象,并执行其他线程。
2 public static voidsleep(long millisec)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3 public static booleanholdsLock(Object x)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4 public static ThreadcurrentThread()
返回对当前正在执行的线程对象的引用。
5 public static voiddumpStack()
将当前线程的堆栈跟踪打印至标准错误流。
3.3通过 Callable 和 Future 创建线程
- 实现Callable接口的好处:
- 可以定义返回值
- 可以抛出异常
4. 多线程的使用
有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
5. 线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是
1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
6. 停止线程
7. 线程休眠(阻塞)(sleep)
sleep
(时间)指定当前线程阻塞的毫秒数;- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,
sleep不会释放锁
模拟倒计时
public class countdown implements Runnable {
@Override
public void run() {
int count = 10;
while (count >= 0) {
try {
Thread.sleep(1000);//线程阻塞1000ms=1s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(count--);//每隔一秒打印秒数
}
}
}
打印当前系统时间
public class SystemCurrentTime {
public static void main(String[] args) throws InterruptedException {
//获取系统当前时间
Date startTime = new Date(System.currentTimeMillis());
while (true) {
Thread.sleep(1000);
System.out.println("当前时间:"+new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
}
}
}
8. 合并线程(Join)
- 合并线程,优先执行该线程,替他线程进入阻塞状态,待此线程执行完成,在执行其他线程。
public class JoinOfThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我是VIP"+i);
}
}
}
public static void main(String[] args) throws InterruptedException {
JoinOfThread joinOfThread = new JoinOfThread();
Thread thread = new Thread(joinOfThread);
thread.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i == 80) {
thread.join();//main线程阻塞,VIP插队执行
}
System.out.println("main" + i);
}
}
9. 线程状态(Thread.State)
Thread thread = new Thread()//创建一个线程
Thread.State
threadState =thread.getState()
;//获取线程当前状态
public class State{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程结束");
});//创建一个线程
//观察状态
Thread.State threadState = thread.getState();//获取线程当前状态
System.out.println(threadState);//NEW
//启动线程
thread.start();
threadState = thread.getState();
System.out.println(threadState);//Run
while (threadState != Thread.State.TERMINATED) {//只要线程不停止,就一直输出状态
Thread.sleep(100);
threadState = thread.getState();//更新线程状态
System.out.println(threadState);//输出状态
}
}
}
10. 守护线程(daemon)
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待…
//上帝(守护线程)
class God implements Runnable{
@Override
public void run() {
while (true) {//模拟守护线程,线程自己不会结束
System.out.println("上帝守护着你");
}
}
}
//人类(用户线程)
class Human implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("平凡的人类");
}
System.out.println("========goodbye========");
}
}
//测试守护线程
//上帝守护着你
public class Daemon {
public static void main(String[] args) {
/**
* 用户线程全部执行完成,守护线程才会结束
*/
Thread god = new Thread(new God());
//默认false表示用户线程,正常的线程都是用户线程..
god.setDaemon(true);//设置God为守护线程
god.start();//开启守护线程
Thread human = new Thread(new Human());
human.start();//启动用户线程
}
}
- 最终守护线程随着用户线程的死亡而结束