什么是进程,线程:
这是个老生常谈的问题了,总结一下,看任务管理器,这里的进程就是所谓的进程,就是说一个程序运行起来就是一个进程,一个进程中可以包括多个线程。多个线程协同合作完成一个功能。
我们学习多线程主要学习什么内容呢?
1.线程的使用方式
2.线程的生命周期
3.线程的通信
4.线程的同步
下面我们来一一介绍这些
1.线程的使用方式
线程的创建方式以共有四种
**一,继承Thread类:**然后他有以下几个步骤
①继承Thread类
②重写run方法
③new一个子类,调用start方法
public class ThredTest
{
public static void main(String[] args) {
System.out.println("main before");
myThread myThread = new myThread();
myThread.start();
System.out.println("main after");
}
}
class myThread extends Thread{
@Override
public void run() {
for(int i=1;i<=100;i++){
if(i%2==0){
//设置当前线程名
Thread.currentThread().setName("求偶数:");
// 获取向前线程名
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
结果不是顺序执行,而是执行完了main方法种的两个输出有执行的求偶数的方法,这就是多线程,通过start方法开启线程,调用run方法种的代码,main方法也是一个线程,这个时候这两个线程(一个是main方法,一个是子类的run方法等待CPU分配资源去执行,呗分配资源的概率是随机的,有可能每次都分给main方法,就像上面的执行结果)
二。实现Runnable接口
①实现Runnable接口
②重写方法
③将实现了Runnabe接口的子类的作为参数,new一个Thread()对象
④调用new的Thread类的start
public class ThredTest
{
public static void main(String[] args) {
System.out.println("main before");
myRunnable myRunnable = new myRunnable();
Thread testRunnable = new Thread(myRunnable);
testRunnable.start();
System.out.println("main after");
}
}
class myRunnable implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
if(i%2!=0){
Thread.currentThread().setName("求奇数:");
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
看结果还是多线程运行的
**思考一个问题:**如果我不调用start方法,而是调用run方法会不会同样能够开启多线程
public static void main(String[] args) {
System.out.println("main before");
myRunnable myRunnable = new myRunnable();
Thread testRunnable = new Thread(myRunnable);
//调用run方法
testRunnable.run();
System.out.println("main after");
}
这可不是巧合 ,是运行多次都是这一个结果,那为什么呢?原因是start方法种有开启线程的方法,方法里面默认会调用方法,如果只是调用run方法们就跟面向对象种调用子类的方法一个概念都是单线程的。
**再思考另一个问题:**为什么实现Runnable接口的的类也需要将逻辑代码写在run方法种,又为什么需要将实现了Runnable接口的类当作参数传给Thread类的构造器方法。那就看下源码
start方法种调用了start0()方法
如果target不为空,就调用target的run方法,如果是继承Thread类的方法(就是第一种实现多线程的方法)就会调用自己重写的run方法,这里是实现Runnble接口的方式。好的那么target是什么呢,看下Thread类的构造方法。
target就是作为参数传给构造方法的实现了Runnable方法的类的实例
**总结:**如果是继承了Thread,run方法是子类重写的方法,如果是实现接口的,就是target(实现接口的类的实例)的run方法,总的来说都是自己重写的方法方法
最后看一个好完了:Thread也是实现了Runnable接口
是不是很有意思,这就让我们更好的理解继承和接口的使用了,java支持单继承多实现,接口其实也是为了解决java单继承的问题出现的,想要具有更多的功能方法,再单继承的原则下,只能是实现接口。
三。实现Callable接口
①创建一个类实现Callable接口
②实现call方法
③new一个FutureTask,构造函数的参数是实现类Callable接口的类
④new 一个Thread类构造函数的构造函数的参数是FutureTask类的实例
⑤调用start方法
public class ThredTest {
public static void main(String[] args) {
TestCallable t=new TestCallable();
FutureTask f=new FutureTask(t);
new Thread(f).start();
try {
Object o=f.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class TestCallable implements Callable{
@Override
public Object call() throws Exception {
int sum=0;
for (int i=1;i<100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+i);
sum+=i;
}
}
return sum;
}
}
注意: call()方法是带返回值的并且是可以抛异常的,如果需要获取call方法的返回值,就调用FutureTask的get()方法就可以,但是要处理异常try{}catch{},其实FutureTask也实现了Runnable接口
四。使用线程池
在实际的公司种很少手动创建线程,都是使用线程池,因为手动的,使用线程池,需要开启线程的时候,直接去线程池里面拿,用完放回线程池,效率比较高,可以避免频繁创建销毁、实现重复利用。
corePoolSize:核心池的大小
maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长时间后会终止
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
public class ThredTest {
public static void main(String[] args) {
myRunnable myRunnable = new myRunnable();
TestCallable testCallable = new TestCallable();
FutureTask f=new FutureTask(testCallable);
ExecutorService service=Executors.newFixedThreadPool(10);
ThreadPoolExecutor executor= (ThreadPoolExecutor)service;
System.out.println(service.getClass());
service.submit(f);
service.execute(myRunnable);
service.shutdown();
}
}
//设置线程池参数
public static void main(String[] args) {
myRunnable myRunnable = new myRunnable();
TestCallable testCallable = new TestCallable();
FutureTask f=new FutureTask(testCallable);
ExecutorService service=Executors.newFixedThreadPool(10);
ThreadPoolExecutor executor= (ThreadPoolExecutor)service;
executor.setCorePoolSize();
executor.setKeepAliveTime();
executor.setMaximumPoolSize();
executor.setThreadFactory();
executor.setRejectedExecutionHandler();
System.out.println(service.getClass());
service.submit(f);
service.execute(myRunnable);
service.shutdown();
}
Thread类的一些其他方法
(一)。 yield()线程让步,首先他是一个静态方法,通过Thread.yield()方法调用,是的调用方法的线程进入就绪状态,方法的初衷是将cpu资源让给优先级更高,或者优先级相同的线程去执行,但是他是将线程的状态转换为了就绪状态,所以有可能还是继续调用自己,没有达到理想效果。
public class ThredTest {
public static void main(String[] args) {
myRunnable myRunnable = new myRunnable();
myThread myThread=new myThread();
Thread mt=new Thread(myRunnable);
myThread.start();
mt.start();
}
}
class myRunnable implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
if(i%2!=0){
if(i==20){
Thread.yield();
}
Thread.currentThread().setName("求奇数:");
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
class myThread extends Thread{
@Override
public void run() {
for(int i=1;i<=100;i++){
if(i%2==0){
if(i==40){
Thread.yield();
}
Thread.currentThread().setName("求偶数:");
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
刚开始是交替执行,后来求偶数执行一直到i40,cpu交给求奇数执行,但是当i20的时候,求奇数让出cpu资源后又被自己强占到了,继续执行。
(二)。join方法, 线程插队,当一个线程种调用join方法,使另一个线程插队执行的时候,该线程处于阻塞状态,当插队的线程执行完成以后,该线程变成就绪状态,去抢占资源执行。(这里有两个点需要注意,一个是join的方法全部执行完,其他资源才可以执行,另一个注意的点就是,其他别插队的线程,是先阻塞状体,在就绪状态。)
public class ThredTest {
public static void main(String[] args) {
myRunnable myRunnable = new myRunnable();
myThread myThread = new myThread();
Thread mt = new Thread(myRunnable);
mt.start();
myThread.start();
}
}
class myRunnable implements Runnable {
@Override
public void run() {
ThreadThree threadThree = new ThreadThree();
threadThree.start();
System.out.println("开启第三个线程");
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
try {
threadThree.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread.currentThread().setName("求奇数:");
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class myThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
Thread.currentThread().setName("求偶数:");
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
class ThreadThree extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
Thread.currentThread().setName("求偶数Three:");
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
这里证明一个问题,当存在三个及以上线程时,其中一个线程被join方法插队,插队的方法继续和没有插队的方法竞争cpu,插队的方法只是会影响被插队的方法,不会影响其他的方法,就是说被插队的方法要等到插队的方法全部执行完才执行,但是插队的方法不是优先级最高的,他还要和其他线程(除了别插队的线程)竞争cpu去执行。就好像食堂排队打饭,其中一队有人插队,他只会影响有人插队的那一队,不会影响其他的队伍,这时候你就是cpu 决定要排那一队,排有人插队的也可以,怕其他队也可以。(希望我说明白了)
(三)。sleep()方法: 休眠多少时间,单位毫秒,休眠的时候阻塞状态,睡眠结束就绪状态,等待竞争cpu资源后,进行执行,通过Thread.sleep()调用,并且要处理异常。
(四)。stop(): 强制线程生命期结束,不推荐使用
(五)。boolean isAlive():返回boolean,判断线程是否还活着
线程的调度
(此处引用尚硅谷课程老师的课件中的内容,如有侵权,请联系删除,祝天下没有难学的技术)
线程的优先级
(此处引用尚硅谷课程老师的课件中的内容,如有侵权,请联系删除,祝天下没有难学的技术)
线程的生命周期
线程的生命周期也是十分重要的一个知识点,设计到线程之间的调度问题,如图。
需要注意的是,当new完一个线程的时候,就进入了新建状态,调用start()方法后,进入就绪状态,进入就绪转台以后就可以抢占cpu资源然后去执行了,就绪状态和运行状态是可以直接相互转换的。运行到阻塞,阻塞到就绪是单向的不能相互转换的。阻塞状态最后都是重新回到就绪状态然后,抢占cpu执行权然后执行的,都要经过就绪状态