JAVA线程——华清远见

发布于:2023-01-23 ⋅ 阅读:(494) ⋅ 点赞:(0)

依稀记得

第一次面试的时候

面试官问:线程和进程都是什么啊,它俩有什么区别吗

我:

 知识盲区了家人们

今天!翻身农奴把歌唱!!

今天也是认真学习的一天!!


一、什么是线程?

咳咳,总听别人提起什么线程啊什么进程的,但是总是傻傻分不清楚  

我们来看看官方的解释:


进程是指一个内存中运行的应用程序,

每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。

比如在Windows系统中,一个运行的exe就是一个进程。


线程是指进程中的一个执行流程,一个进程中可以运行多个线程。

比如java.exe进程中可以运行很多线程。


总的来说,线程总是属于某个进程,进程中的多个线程共享进程的内存。

二、多线程的优势

俗话说的好,众人拾柴火焰高,集体的力量还是很强大的。

一个进程如果由多个线程完成的话,效率会不会更高呢?

实践出真知!!


CPU时间片:cpu在每一个线程上运行的时间,时间片到了,到另一个线程中。(手动划重点!)

假设 一段代码需要4s中完成,此时我们只有一个线程(就是线程t1哦),但是系统中有三个线程,目标是完成线程!!

一段4s中的程序需要10s中才能执行完成。 


 这次我们有两个线程!!

 一段程序如果启动多个线程执行,同样的4s的程序需要5s中结束。


如果一个程序(一个进程)启动多个线程,并发的执行程序,大大缩短执行时间。

三、线程创建和启动方式

1、继承Thread类

创建线程:创建一个自定义类继承Thread类,重写run方法

public class MyThread extends Thread {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 2000; i++) {
			try {
				Thread.sleep(1000);
				System.out.println(i);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
    }
}

开启线程:创建该类对象,并调用start()方法,等待cpu的工作

public class Test {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyThread mt = new MyThread();
		mt.start();
    }
}

2、实现Runnable接口

创建线程:创建一个类实现Runnable接口,实现run方法

public class MyThread2 implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 20; i++) {
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

开启线程:创建该类对象,再创建Thread类对象,并将该类对象通过构造函数传入Thread类对象。使用Thread类对象调用start方法。

public class Test {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyThread2 mt2 = new MyThread2();
		Thread thread = new Thread(mt2);
		thread.start();
    }
}

还有一种匿名内部类的写法(实际为实现Runnable接口的方式的变形)

public class Test {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int i = 10; i < 20; i++) {
					System.out.println(i);
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
}

3、使用Callable和FutureTask创建线程

创建Callable接口的实现类,并实现call()方法。

public class MyCallable implements Callable<String>{

	@Override
	public String call() throws Exception {
		// TODO Auto-generated method stub
		for (int i = 0; i <= 100; i++) {
			System.out.println("加载进度:"+i+"%");
			Thread.sleep(200);
		}
		return "加载完成,正在进入...";
	}

}

然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。

使用FutureTask对象作为Thread对象的目标创建并启动线程(因为FutureTask实现了Runnable接口)。

调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

public class Test {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		MyCallable myCallable = new MyCallable();
		FutureTask<String> ft = new FutureTask<String>(myCallable);
		Thread thread = new Thread(ft);
		thread.start();
		System.out.println(ft.get());
	}

}

四、三种线程创建方式的比较

既然有三种创建线程的方式,那肯定会有最简单最便利的一种吧

一起look look~


采用继承Thread类方式

优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。

缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。


采用实现Runnable接口方式

优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。


采用实现Callable接口方式

Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值可以throws异常,前者线程执行体run()方法无返回值不能throws异常,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

优点:线程只是实现Runnable或实现Callable接口,还可以继承其他类。这种方式下,多个线程可以共享一个目标对象,非常适合多线程处理同一份资源的情形。

缺点:编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。


我选第一种!!后面怎么越来越麻烦了

一环套一环的

不会真的有小呆瓜喜欢第三种吧

不过三种创建方式都得掌握

考试要考!!划重点!

五、线程状态都有哪些

新建状态:当new创建线程对象时

就绪状态(可运行状态):调用start()方法后。所需资源都已经获取到,就差cpu资源,运行状态,时间片到了,cpu调度到其他线程,此线程处于就绪状态

运行状态:就绪状态获取到cpu资源后进入到运行状态

不可运行状态(等待状态,阻塞状态,休眠状态):线程等待某个资源,被某个资源阻塞,比如i/o

终止状态:线程运行结束。

 二话不说就是背!!

 六、线程常见方法

1、Thread.currentThread()

获得当前线程

2、Thread.currentThread().getName()

获得当前线程的名称

3、Thread.currentThread().setName(’’)

设置当前线程的名称

4、sleep()

在指定时间内让当前执行的线程暂停执行一段时间,让其他线程有机会继续执行,但不会释放对象锁,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据,不推荐使用。sleep() 使当前线程进入阻塞状态,在指定时间不会执行,因此sleep()方法常用来暂停线程的执行,当sleep()结束后,然后转入到 Runnable(就绪状态),这样才能够得到执行的机会。

5、yield()

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。

6、join()

join()是Thread类的一个方法,根据jdk文档的定义,join()方法的作用,是等待这个线程结束,即当前线程等待另一个调用join()方法的线程执行结束后在往下执行。通常用于在main主线程内,等待其它调用join()方法的线程执行结束再继续执行main主线程。

7、wait()、notify和notifyAll()

在synchronized 代码块执行,说明当前线程一定是获取了锁的。当线程执行wait()方法的时候,会释放当前的锁,然后让出CPU,进入等待状态。只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁


可恶的线程!!

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到