依稀记得
第一次面试的时候
面试官问:线程和进程都是什么啊,它俩有什么区别吗
我:
知识盲区了家人们
今天!翻身农奴把歌唱!!
今天也是认真学习的一天!!
一、什么是线程?
咳咳,总听别人提起什么线程啊什么进程的,但是总是傻傻分不清楚
我们来看看官方的解释:
进程是指一个内存中运行的应用程序,
每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。
比如在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() 后立即退出临界区,以唤醒其他线程让其获得锁
可恶的线程!!