一、线程和进程的对比:
1、不管线程也好,还是进程也好,他都是cpu的一个时间片,表示的就是一个时间段
2、进程是系统中的最小的执行单元,相当于每开一个记事本,开一个qq等等都是一个进程
3、在下面的图中就是开启了一些进程。
进程就是一个应用程序,而这个进程就是系统当中的最小的执行的单元
4、进程是由多个线程组成,而线程是进程中的最小的执行单元,所以说只要有进程,它一定会至少有一个线程。因为进程是无法完成功能的,而进程是由线程去组成。
5、多个线程共享一个进程中所有资源,比如本地栈,栈方法,PC寄存器,上下文
二、种类:
1、单进程和单线程:好比一个人在一张桌子去吃饭,这里一个桌子是一个进程,一个人在上面去吃饭好比是一个线程,而这个线程又是单一的。
2、单进程多线程:好比车道,一个车道上面有多个车辆在跑。也好比多个人在一个桌子上面吃饭,所有的线程共享了进程中的资源。
3、多进程多线程 :每一个人在各自的桌子上吃饭。
而我们主要学的就是多线程。jvm上只能进程线程的创建和运行,所有的调度是由系统来调度的,不是jvm来调度的。
三、线程的运行方式:
1、只要运行一个程序就会有一个线程,比如在运行程序的时候会有一个main方法,这个main方法就是一个主线程,而所有的程序在主线程中都是由上到下来执行的。
加一个子线程的时候就会不按照这样的顺序来执行的了,这时所有的线程可能会同时执行,也有可能不会同时去执行。这就看线程获取时间片的权限。
2、两个子线程在程序中是就绪的状态,等待由系统去调度它。谁先获取到时间片,谁就先执行,否则会继续去等待系统的调度,然后最后到main线程会汇总。所以说这时就不是顺序执行了。
总结:
1、线程不会像普通程序一样顺序执行,而是根据系统分配资源权限来执行。
2、线程由Runnable的就绪状态变为Running的状态的时候必须由系统来调度才可以。
3、系统是通过时间片来控制线程来执行的,时间片是系统调度资源。
4、当我们去实现一个线程的时候,由程序是监控不了的,只有系统来监控让线程去执行的。
创建一个线程------》调用start()变为Runnable----->由系统调度变为Running
四、线程的运行方式:
程序->创建一个线程->start->Runnable
系统调度就绪状态中的线程->Running状态
五、java中怎么创建线程
Thread类:
线程简单的使用:
1、首先创建一个不带线程的例子:这个例子不能会走到run2的方法。因为当调用run1的方法的时候就死循环了。
默认的主线程中的程序执行的顺序是从上到下的。运行的结果如下:
这就是没有用线程是做不到的。
在学习线程的时候不要用Juint,因为juint是有问题的,因为主线程里面才会有子线程
2、使用线程的例子,代码如下:
这样执行的话,也会执行t1,因为启动线程不是调用run方法,而是start方法。
运行的结果如下:
这时t1,t2都执行,上面的run方法是由系统来调度器来调度执行的,由runnable变为Running的时候来执行的,而不需要程序去执行它。
总结:谁先竞争到时间片就谁先执行,是两个不同的线程在主线程中执行不同的功能。
三·、创建游戏线程类,语音线程类,如何去中断线程呢:
1、使用暴力的方式去中断线程是不行的:stop方法是不推荐使用的,因为这里会有数据的安全性问题,比如我在写汉字,一下就给停止了,这时可能会出现数据不安全的问题。
public class TestThread {
public static void main(String[] args) {
GameThread gameThread=new GameThread();
VoiceThread voiceThread=new VoiceThread();
gameThread.start();
voiceThread.start();
voiceThread.stop();//@Deprecated用这个注解标注的代表过时的方法
}
/**
* 游戏线程类
*/
private static class GameThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("执行t1");
}
}
}
/**
* 语音线程类
*/
private static class VoiceThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("执行t2");
}
}
}
}
运行结果如下:这时就一直执行t1。
2、让线程挂起,也是不推荐使用的,这个方法也被抛弃掉了:
运行的结果如下:这时也都打印t1。
总结:
1、还有一个resume苏醒的方法,也是不推荐使用的。因为这几个方法在线程中使用会导致线程死锁的问题。
2、多个线程会同时去执行,只是相互的去抢占资源。
3、线程和主方法是没有关系的,不会等待所有的线程执行完。
4、把线程挂起,和沉睡,都不会去决定一个线程是否先执行,是由系统的调度器来决定的,只是说让其他的线程去执行的占有率变高了。如果存在对象锁的话,这时挂起和沉睡的线程是不会释放锁的。
死锁:当这个线程被挂起的时候,而又去苏醒它的时候,如果在苏醒之前,这个线程已经被激活了,这时就会出现死活的概念。
四、join方法:只有当子线程执行完,才会去执行主线程,代码如下:
public class TestThread {
public static void main(String[] args) {
GameThread gameThread = new GameThread();
VoiceThread voiceThread = new VoiceThread();
gameThread.start();
voiceThread.start();
//voiceThread.stop();//@Deprecated用这个注解标注的代表过时的方法
//voiceThread.suspend();
try {
gameThread.join();
//InterruptedException:线程中断异常
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main end--------");
}
/**
*
*/
private static class GameThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("执行t1");
}
}
}
/**
*
*/
private static class VoiceThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("执行t2");
}
}
}
}
运行的结果如下:
join方法好比讲子线程加入到主线程去执行,join方法针对的是主线程。
举个例子:去下载一个大的文件,这个文件太大了,把它分成四个线程去下载。
每个线程去下载一部分,这四个线程不管谁先下载完都会去等待,然后最终把文件合并在一起,然后真正的下载到本地来。
五、创建线程的另一种方式:实现runnable接口
public class TestTread2 {
public static void main(String[] args) {
//第一个参数是目标对象,第二个参数是线程的名字
Thread t1 = new Thread(new MyRunnable(), "AAAA");
Thread t2 = new Thread(new MyRunnable(), "BBBB");
t1.start();
t2.start();
}
private static class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行Runnable接口");
}
}
}
}
运行的结果如下:
sleep:阻塞线程,代码如下:
public class TestTread2 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(), "AAAA");
Thread t2 = new Thread(new MyRunnable(), "BBBB");
t1.start();
t2.start();
}
private static class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行Runnable接口");
System.out.println("-------------------------");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("*****************************");
System.out.println(Thread.currentThread().getName()+"执行Runnable接口");
}
}
}
}
运行的结果如下:
这时就会有线程的安全的问题。
public class TestTread2 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(), "AAAA");
Thread t2 = new Thread(new MyRunnable(), "BBBB");
t1.start();
t2.start();
}
private static class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行Runnable接口");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行Runnable接口");
}
}
}
}
这样的执行效果如下:睡眠2秒钟,等待其他线程来进入,然后醒来时继续执行,这就是好几个线程在同时执行。
六、线程的生命周期:
①、比如在上面的图中:在程序中有进入区,临界区,退出区,当一个线程进入到进入区的时候,获取到了时间片,然后进入到临界区执行一些操作。
②、在上面的图中,当两个线程,其中一个线程进来把张三改为李四,然后另一个线程进来的时候,拿到的值就不是张三了,所以说在临界区容易出现数据错误的问题。
③、退出区:就是死亡区,线程执行完。
如果有对象锁的话:
就是一个线程进来的时候获取到锁的,去进入到临界区,去执行相应的操作,执行完,再释放锁,然后其他线程去获取锁。
最基本的线程状态:
上面的图就是正常的执行过程。
下面的图是特殊的执行过程:
如果当调用wait方法的时候,会把线程放入到阻塞的队列中去。而上面的几个方法不会把线程放入到阻塞队列里面去,而wait之后会把锁给放开,让其他的线程去执行,wait方法是Object对象里面的方法,只要是对象的话,都有wait方法,wait方法是与synchnorized连用的,使用synchnorized关键字同步块的就会有放入到锁池中,当调用其他线程对象的notify和notiyall的时候会把阻塞队列中线程对象唤醒,notify:是唤醒阻塞队列中的一个线程对象,而notifyall是唤阻塞队列中的所有的线程对象。只有拿到锁的线程才会达到就绪的状态。
synchnorized:保证一个线程在一个时间点上在同一个时间片上去执行。
代码演示下线程的状态,代码如下:
public class ThreadDemo {
public static void main(String[] args) {
StateThread st=new StateThread();
st.start();
}
private static class StateThread extends Thread{
@Override
public void run() {
for(int i=0;i<1000000;i++){
System.out.println("执行线程内容");
}
}
}
}
然后可以看到AAAA线程首先是runnable的状态,可执行的状态
当把线程阻塞的时候:这时就会有waiting的状态,阻塞的状态
总结:
1、我们看不到running状态,因为running状态是由系统来调度的,通过java虚拟机是观察不了的。我们能看到的是阻塞状态和就绪的状态。
2、死亡的状态也看不了,因为当调用join方法也好或者wait方法也好,必须人为的去唤醒它才可以。
3、不是每一个线程会无限制的去执行下去,只是会在有限的时间片去执行它,如果没有执行完仍旧会处于就绪的状态,不会让继续去执行。