##基本概念
1、 进程是一个正在执行中的程序。
2、线程是进程中的一个独立的控制单元/执行顺序/执行路径,一个进程中至少有一个线程。
3、Java VM启动的进程为java.exe,该进程中至少有一个线程来负责java程序的执行,而且这个线程运行的代码存在于main方法中。这个线程叫做主线程。也就是主线程是由JVM开启的。
4、严格意义上来说,JVM启动的时候不止一个线程,还有负责垃圾回收机制的线程。
5、在某一时刻只能有一个程序在运行(多内核除外),cpu在快速切换,看上去是在同时运行。
6、每次运行结果都不同,因为多个线程都在获取cpu的执行权,随机性,谁抢到谁执行。
7、 按照线程体在计算机系统内存中的状态不同,可以将线程分为创建、就绪、运行、睡眠、挂起和死亡等类型。这些线程状态类型下线程的特征为:
创建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源。
就绪状态:在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源, 只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。
睡眠状态:在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时,该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后,线程重新由JVM线程调度器进行调度和管理。
挂起状态:可以通过调用suspend方法(已过时)将线程的状态转换 为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用resume方法(已过时)恢复线程运行。
死亡状态:当线程运行结束或者调用线程对象的stop方法(已过时)后线程将终止运行,由JVM收回线程占用的资源。
8、拥有执行资格且有执行权的状态叫做运行态;有执行资格没有执行权的状态叫做阻塞状态;没有执行资格且没有执行权的状态叫做睡眠/挂起状态。
##创建线程 1、继承Thread子类,复写run方法,调用start()方法,去执行run方法。
class ThreadTest extends Thread {
public void run() {}
}
ThreadTest threadTest = new ThreadTest(); //已经创建了线程
threadTest.start();
2、声明实现Runnable接口的类,复写run方法,建立Thread线程对象,传递Runnable接口的子类对象作为实参,并调用start()方法,去执行run方法。
class ThreadTest implements Runnable {
public void run() {}
}
ThreadTest threadTest = new ThreadTest(); //并没有创建线程
Thread t= new Thread(threadTest); //这里才创建线程
t.start();
###继承方式和实现方式的区别
1.继承Thread来创建线程类的方法,在继承了Thread后,不能再继承其他类,这样灵活性就不如实现Runnable接口来创建线程类的方法了。所以优先选择实现Runable接口方式,避免了单继承的局限性。
2.正如上面所说的使用实现Runnable接口来创建线程类的方法可以达到资源独立共享。
(在这里说明一下:继承Thread类来创建线程类的方法也可以实现资源共享,只不过比较麻烦。因此,在创建线程类的时候,应优先选择实现Runnable接口来创建线程类的方法。)
##多线程的同步问题
###安全问题 问题原因:当多条语句在操作同一个线程的共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,而另一个线程参与进来执行,导致共享数据的错误。
解决思路:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
解决办法:利用Java中的同步代码块来解决。操作共享数据的语句必须同步。
synchronized(对象){
需要被同步的代码;
}
###Synchronized关键字 对象就像锁,持有锁的线程可以再同步中执行,没有持有锁的线程即使获取CPU的执行权也不能执行。 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
2、然而当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
4、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
5、以上规则对其它对象锁同样适用。
注意:1、函数需要被对象调用,函数都有一个所属对象引用,就是this,所以同步代码块的锁是this。
public void run(){
synchronized(this){ //锁对象为this
...
}
}
2、因为静态方法中没有this,没有本类对象,直接由类调用,所以静态(static)同步函数的锁是Class对象:类名.class。
public static void run(){
synchronized(Method.class){ //类名.class
...
}
}
###同步函数 同步代码块也是封装代码,函数也是封装代码。两者不同的是同步代码具备同步特性。那么只需要将synchronized作为修饰符修饰函数即可。如:
public synchronized void MethodName(){}
###同步的前提 1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一个锁。
3、只需要同步操作共享数据的语句。
###同步优缺点 优点:解决了多线程的安全问题
缺点:多个线程都需要判断锁,消耗了资源
###如何判断程序中的安全问题? 1、明确哪些代码是多线程的运行代码。
2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。