【Java】多线程
多线程需要有一个专门的主体类
要求:
实现特定的接口或继承特定的父类
继承Thread的类即主体类并且复写run()方法(线程的主体方法)
执行的功能都应该在run()方法中定义。run()无法被调用,牵扯到操作系统的的资源调度问题
启动多线程必须使用start()方法。
调用start()方法,但是最终执行run()方法,
为什么不直接使用run()方法?
start()方法实现: 每一个线程类的对象只允许启动一次,如果重复启动,抛出”IllegelThreadStateExpception”
start0():只定义了方法名, Java支持本地操作系统函数的调用JNI(Java Native Inteface)。此方法依赖不同的操作系统实现。
Thread执行分析
任何情况下,只要定义了多线程,启动只能用Thread中的start()方法。
基于Runnable接口实现多线程
接口定义:
从JDK1.8引入Lambda表达式之后就变成了函数式接口
Thread类构造方法:接收Runnable
实现Runnable接口解决单继承的局限
也可以直接利用Lambda进行多线程定义
对于多线程的实现,优先考虑Runnable,然后使用start()方法启动。
Thread与Runnable关系
从代码结构,Runnable更方便,避免单继承的局限,可以更好扩充功能;
Thread与Runnable联系
Thread:Thread类也是Runnable接口的子类
多线程设计使用了代理设计模式的结构,用户自定义的线程主体只负责项目核心功能的实现,其他全部交由Thread类来处理。
进行Thread启动多线程时,调用的是start()方法,然后
找到run()方法,但通过Thread类的构造方法传递了Runnable
接口对象,该接口对象将被Thread类中的target属性保存,在start()方法执行的时候会调用Thread类中的run方法,而这个run()方法去调用Runnable接口子类被覆写的run()方法。
多线程开发本质是在于多个线程可以进行同一资源的抢占,Thread主要描述的是线程,而资源的描述是通过Runnable完成的。
多线程开发
Callable实现多线程
Runnable缺点: 当线程执行完毕后无法获取返回值。
JDK1.5之后新的线程实现接口:Callable接口,函数式接口,可以设置一个泛型,泛型的类型就是返回数据的类型,
Callable与Runnable区别:
Runnable是JDK1.0提出的,Callable是JDK1.5提出的
Runnable只有一个run()方法无返回值
Callable提供有call()方法,可以有返回值
线程运行状态
编写程序的过程:
1、定义线程主体类
2、通过Thread类进行线程启动
但是不意味着调用了start()方法线程就已经开始运行,线程处理有自己的一套运行状态
1、任何一个线程的对象都应使用Thread类进行封装,启动使用的是start(),但是启动的时候若干个线程都将进入就绪状态,并没有执行;
2、进入就绪状态后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停的状态,例如:某个线程执行一段时间就需要让出资源,而后这个线程进入阻塞状态,随后重新回归到就绪状态;
3、当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。
线程的命名和取得
多线程的运行状态是不确定的,在开发过程中,为了可以获得一些需要使用到线程,就需要使用线程的名字来进行操作。Thread类中提供有线程名称的处理。
构造方法:public Thread(Runnable target, String name)
设置名字:public final void setName(String name);
取得名字:public final String getName();
对于线程对象的获得是不可能只依靠一个this来完成,因为线程的状态不可控,但是有一点是明确的,所有线程对象一定要执行run()方法,那么这个时候可以考虑获取当前线程,在Thread类里面提供有获取当前线程的方法
获取当前线程:public static Thread currentThread();
当开发者为线程设置名字的时候就使用设置的名字,如果没有设置则会自动生成一个不重复的名字,这种自动的属性命名主要是依靠了static属性完成的,在Thread类里面定义有如下操作:
Java private static int threadInitNumber; private static synchronized int nextThreadNum(){ return threadInitNumber++ } |
---|
当使用了“mt.run()”直接在主方法之中调用线程类对象中的run()方法所获得的线程对象的名字为“main”,所以可以得出一个结论:主方法也是一个线程。所有的线程都是在进程上的划分,那么现在进程在哪里?每当使用java命令执行程序的时候就表示启动了一个JVM的进程一台电脑上可以同时启动多个JVM进程。
在任何的开发中,主线程可以创建若干个子线程。创建子线程的目的是可以将一些复杂逻辑或者比较耗时的逻辑交由子线程处理 ;
主线程负责处理整体流程,而子线程负责处理耗时操作
线程的强制执行
当满足某些条件之后,某一个线程对yy象可以一直独占资源,直到该线程的程序执行结束。
强制执行:join()
在进行线程强制执行的时候一定要获取强制执行对象之后才可以执行join()调用。
线程礼让
先将资源让出去,让别的线程先执行。
礼让:yield()
礼让执行的时候每次调用yield()都只会礼让一次。
线程优先级
线程优先级越高,越有可能先执行(越有可能先抢占到资源)
设置优先级:setPriority()
获取优先级:getPriority()
在进行优先级定义的时候都是通过int型的数字来完成的,而对于此数字的选择在Thread里定义有3个常量
最高优先级:MAX_PRIORITY=10
中等优先级:NORM_PRIORITY=5
最低优先级:MIN_PRIORITY=1
主线程属于中等优先级,默认创建的线程也是中等优先级。
同步问题
在多线程的处理中,可以利用Runnable描述多个线程操作的资源,而Thread描述线程对象,当多个线程访问同一资源的时候,如果处理不当就会产生数据的错误操作。
线程同步处理
解决同步问题的关键是锁,当一个线程执行操作的时候,其他线程外面等待;
synchronized,利用此关键字可以定义同步方法或同步代码块,在同步代码块的操作里面只允许一个线程执行。
1、利用同步代码块进行处理:
Java synchronized(同步对象){ 同步代码操作; } |
---|
一般要进行同步对象处理的时候可以采用当前对象this进行同步。
加入同步处理后,程序整体的性能下降了。同步实际上会造成性能的降低。
2、利用同步方法解决:只需要在方法定义上使用synchronized关键字即可。
系统中许多的类上使用的同步处理采用的都是同步方法。
线程死锁
死锁是在进行多线程同步处理中有可能产生的一种问题,指的是若干个线程彼此互相等待的状态。
死锁造成的主要原因是因为彼此都在互相等待对方先让出资源。死锁实际上是一种开发中出现的不确定的状态,有的时候代码如果处理不当则会不定期出现死锁,这是属于正常开发中的调试问题。
若干个线程访问同一资源时一定要进行同步处理,而过多的同步会造成死锁。
生产者与消费者基本程序模型