线程概述
什么是线程?
几乎 所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。
也可以这么描述:一个程序运行后至少有一个进程,一个进程可以包含多个线程,但至少要包含一个线程
多线程的优势
- 多线程编程简单,效率高(能直接共享数据和资源,多进程不能)(系统城建进程时需要为该进程重新分配系统资源,但创建线程则代价小很多,因此使用多线程来实现多任务并发比多进程效率高。
- 适合于开发服务程序(如Web服务、聊天服务等)。
- 适合于开发有多种交互接口的程序
- 适合于有人机交互又有计算量的程序
- 减轻编写交互频繁、涉及面多的程序的困难(如网络监听窗口)
- 程序的吞吐量会得到改善
- 有多个处理器的系统,可以并发运行不同的线程(否则,任何时刻只有一个线程在运行,只是宏观上看是并发运行)
线程的创建和启动
继承Thread类创建线程类
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行
- 创建Thread子类的实例,即创建了线程对象。
- 调用线程对象的start()方法来启动该线程
package thread;
/**
* @author Mike
* @use 集成thread类创建线程
*/
public class FirstThread extends Thread {
private int i;
public void run(){
for (;i<100;i++){
//当线程集成thread类时,直接使用this即可获取当前线程
//thread对象的getname()返回当前线程的名字
//因此可以直接调用getname()方法返回当前线程的名字
System.out.println(getName() + " " +i);
}
}
public static void main(String[] args) {
for (int i=0;i<100;i++){
//调用thread的current()方法获取当前线程
System.out.println(Thread.currentThread().getName()+ " " +i);
if (i==20){
new FirstThread().start();
new FirstThread().start();
}
}
}
}
实现Runnable 接口创建线程类
- 定义Runnable接口创建线程类
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
- 调用线程对象的start()方法来启动该线程
package thread;
/**
* @author Mike
* @use 实现Runnable接口创建线程类
*/
public class SecondThread implements Runnable {
private int i;
@Override
public void run() {
for (;i<100;i++){
System.out.println(Thread.currentThread().getName()+ " "+ i);
}
}
public static void main(String[] args) {
for (int i = 0;i<100;i++){
System.out.println(Thread.currentThread().getName()+ " "+ i);
if (i == 20){
SecondThread st = new SecondThread();
//通过new Thread(target,name)方法创建新线程
new Thread(st,"线程1").start();
new Thread(st,"线程2").start();
}
}
}
}
使用Callable 和 Future 创建线程
- 创建Callable接口的实现类,并且实现call()方法,该call()方法将作为线程的执行主体,可以理解为有返回值的run()方法
- 使用FutureTask对象作为Thread对象的target创建并启动新线程
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
package thread;
import javax.swing.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author Mike
* @use 使用Callable和Future创建线程
*/
public class ThirdThread {
public static void main(String[] args) {
//创建Callable对象
ThirdThread tt=new ThirdThread();
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
int i = 0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
}
//call有返回值
return i;
});
for (int i = 0;i < 100; i++){
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
if (i == 20){
new Thread(task,"有返回值的线程").start();
}
}
try{
System.out.println("子线程的返回值:" + task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
线程的生命周期
线程对象调用了start()方法之后,该线程处于就绪状态
就绪状态–>阻塞状态
- 线程调用sleep()方法主动放弃所占有的处理器资源
- 线程调用了一个阻塞式IO方法,在该方法返回前,该线程被阻塞
- 线程wait()在等待notify()时会处于阻塞状态
- 程序调用了线程的suspend方法将线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。
阻塞状态–>就绪状态
- 调用sleep()方法的线程经过了指定时间
- 线程调用的阻塞式IO方法已经返回
- 线程成功的获得了试图取得的同步监视器
- 被notify()唤醒
- 处于挂起状态的线程被调用了resume()恢复方法
运行状态–>就绪状态
yield()方法
线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。就绪和运行之间的状态转换通常不受程序控制,而是由系统线程调度所决定。
线程死亡
- run()方法执行完毕,线程正常结束
- 线程抛出一个未捕获的Exception 或 Error
- 直接调用该线程的stop()方法来结束该线程–该方法容易导致死锁,通常不推荐使用。
- 不要对处于死亡状态的线程调用start()方法,程序只能对新建的线程调用start()方法,对新建状态的线程两次调用start()方法也是错误的。都会引发IllegalThreadStateException异常
控制线程
wait(),notify()
两个方法配套使用,wait()使得线程进入阻塞状态
- 当wait()时,必须经过对应的notify(),才能重新进入就绪
- wait(long time),括号中的参数是毫秒值,超出时间或者notigy()调用都能进入就绪状态
还有一些涉及到同步的内容在写同步的时候再说吧~
sleep()
sleep()方法可以让当前正在执行的线程暂停一段时间,并进入阻塞状态
1.sleep(long time)让当前正在执行的线程暂停time毫秒,并进入阻塞状态
2.还有一个sleep(long millis,int nanos)不常用,后面的nanos是微秒
package thread;
import java.util.Date;
/**
* @author Mike
* @use 线程睡眠
*/
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<10;i++){
System.out.println("当前时间: "+ new Date());
Thread.sleep(1000);//注意此处是毫秒为单位
}
}
}//停一秒输出一个时间
join()
当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。
- join():等待join进来的线程执行完成
- join(long mills):等待join进来的线程执行mills毫秒,如果她还没执行完。就不再等了,继续执行。
package thread;
/**
* @author Mike
* @use 加入线程
*/
public class JoinThread extends Thread {
public JoinThread(String name){
super(name);
}
//重写run()方法,定义线程执行体
@Override
public void run() {
for ( int i = 0;i < 100;i++){
System.out.println(getName() + " " +i);
}
}
public static void main(String[] args) throws InterruptedException {
new JoinThread("新线程").start();
for (int i = 0 ;i< 100;i++){
if (i == 20){
JoinThread jt = new JoinThread(("join进来的线程"));
jt.start();
jt.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
//从结果上来看,刚开始时新线程和main线程在并发执行,有新的线程join进来后,main处于等待状态,
// 新线程和join线程并发执行
//等join线程和新线程执行完之后继续执行main线程
//有个问题,join线程先执行完是没问题的,但是为什么新线程会在main线程执行完之前执行完
}
yield()
和sleep()方法相似,他也可以让当前正在执行的线程暂停,但他不会阻塞该线程,只是将该线程转入就绪状态
关于sleep()和yield()的区别
- sleep()方法暂停当前的线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给更高优先级或者相同优先级的线程执行机会
- sleeep()方法声明抛出了InterruptException异常,所以调用sleep方法时要么捕获该异常,要么抛出异常,而yield()方法则没有抛出异常
- sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行
package thread;
/**
* @author Mike
* @use 线程让步
*/
public class YieldTest extends Thread {
public YieldTest(String name){
super(name);
}
@Override
public void run() {
for (int i = 0;i<50;i++){
System.out.println(getName()+ " "+i);
if (i == 20){
Thread.yield();
}
}
}
public static void main(String[] args) {
YieldTest yt1 = new YieldTest("高级");
yt1.setPriority(Thread.MAX_PRIORITY);
yt1.start();
YieldTest yt2 = new YieldTest("低级");
yt2.setPriority(Thread.MIN_PRIORITY);
yt2.start();
}
}
线程优先级setPriority(int newPriority)
此处注意优先级并不是绝对的概念,并不是优先级高的一定要执行完才会执行优先级低的,也并不一定时优先级高的一定先执行,这是一种概率问题,比如优先级高的线程可能有80%的概率会调用执行,优先级低的有20%,但还是有可能会先调用优先级低的。
后台线程
调用setDaemon(true)方法可以把指定线程设置成后台线程,如果前台线程都死亡,后台线程会自动死亡
package thread;
/**
* @author Mike
* @use 后台线程
*/
public class DaemonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++){
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
DaemonThread dt = new DaemonThread();
dt.setDaemon(true);
dt.start();
for (int i = 0 ;i < 10 ; i++){
System.out.println(Thread.currentThread().getName()+" "+ i );
}
}
}
//此处发现
下面的链接是下一部分关于线程同步的
线程同步学习