本文目录:
1.几个基本的概念
2.创建线程的两种方法
3.线程相关的常用方法
4.多线程安全问题和线程同步
4.1 多线程安全问题
4.2 线程同步
4.3 同步代码块和同步函数的区别以及锁是什么
4.4 单例懒汉模式的多线程安全问题
5.死锁(DeadLock)
本文涉及到的一些概念,有些是基础知识,有些在后文会展开详细的说明。
进程(Process):一个程序运行起来时在内存中开辟一段空间用来运行程序,这段空间包括heap、stack、data segment和code segment。例如,开一个QQ就表明开了一个QQ进程。
线程(Thread):每一个进程中都至少有一个线程。线程是指程序中代码运行时的运行路径,一个线程表示一条路径。例如QQ进程中,发送消息、接收消息、接收文件、发送文件等各种独立的功能都需要一个线程来执行。
进程和线程的区别:从资源的角度来考虑,进程主要考虑的是CPU和内存,而线程主要考虑的是CPU的调度,某进程中的各线程之间可以共享这个进程的很多资源。
从粒度粗细来考虑,进程的粒度较粗,进程上下文切换时消耗的CPU资源较多。线程的粒度要小的多,虽然线程也会切换,但因为共享进程的上下文,相比进程上下文切换而言,同进程内的线程切换时消耗的资源要小的多的多。在Java中,除了java运行时启动的JVM是一个进程,其他所有任务都以线程的方式执行,也就是说java应用程序是单进程的,甚至可以说没有进程的概念。
线程组(ThreadGroup):线程组提供了一些批量管理线程的方法,因此通过将线程加入到线程组中,可以更方便地管理这些线程。
线程的状态:就绪态、运行态、睡眠态。还可以分为存活和死亡,死亡表示线程结束,非死亡则存活,因此存活包含就绪、运行、睡眠。
中断睡眠(interrupt):将线程从睡眠态强制唤醒,唤醒后线程将进入就绪队列等待cpu调度。
并发操作:多个线程同时操作一个资源。这会带来多线程安全问题,解决方法是使用线程同步。
线程同步:让线程中的某些任务原子化,即要么全部执行完毕,要么不开始执行。通过互斥锁来实现同步,通过监视这个互斥锁是否被谁持有来决定是否从睡眠态转为就绪态(即从线程池中出去),也就是是否有资格去获取cpu的执行权。线程同步解决了线程安全的问题,但降低了程序的效率。
死锁:线程全睡眠了无法被唤醒,导致程序卡死在某一处无法再执行下去。典型的是两个同步线程,线程1持有A锁,且等待B锁,但线程2持有B锁且等待A锁,这样的僵局会造成死锁。但需要注意的是,死锁并非都是因为僵局,只要两边的线程都无法继续向下执行代码(或者两边的线程池都无法被唤醒,这是等价的概念,因为锁等待也会让进程进入睡眠态),则都是死锁。
还需需要明确的一个关键点是:CPU对就绪队列中每个线程的调度是随机的(对我们人类来说),且分配的时间片也是随机的(对人类来说)。
2.创建线程的两种方法Java中有两种创建线程的方式。
创建线程方式一:
继承Thread类(在java.lang包中),并重写该类的run()方法,其中run()方法即线程需要执行的任务代码。
然后new出这个类对象。这表示创建线程对象。
调用start()方法开启线程来执行任务(start()方法会调用run()以便执行任务)。
例如下面的代码中,在主线程main中创建了两个线程对象,先后并先后调用start()开启这两个线程,这两个线程会各自执行MyThread中的run()方法。
class MyThread extends Thread { String name; String gender; MyThread(String name,String gender){ this.name = name; this.gender = gender; } public void run(){ int i = 0; while(i<=20) { //除了主线程main,其余线程从0开始编号,currentThread()获取的是当前线程对象 System.out.println(Thread.currentThread().getName()+"-----"+i+"------"+name+"------"+gender); i++; } } } public class CreateThread { public static void main(String[] args) { MyThread mt1 = new MyThread("malong","Male"); MyThread mt2 = new MyThread("Gaoxiao","Female"); mt1.start(); mt2.start(); System.out.println("main thread over"); } }