Java基础(多线程1)
一、程序同一时刻只能做一件事情,要让它做多件事情
二、基本概念
2.1、 程序、进程、线程
程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。(任务管理器可找到)
~运行中的QQ,运行中的MP3播放器;
~程序是静态的,进程是动态的;
~进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
~若一个进程同一时间并行执行多个线程,就是支持多线程的;
~线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
~一个进程中的多个线程共享相同的内存单元/内存地址单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便高效。但可能安全性低。
~每个Java程序都有一个隐含的主线程:main方法
2.2、并发和并行
单核CPU,其实是一种假的多线程,因为一个核在一个时间单元内,只能执行一个线程的任务,实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将CPU的时间片分给不同的程序使用,只是由于CPU在线程间的切换非常快,人类感觉是同时运行的。
总结:微观串行,宏观并行。
如果是多核CPU的话,才能更好的发挥线程的效率,现在电脑多核CPU每个核都可以调度运行线程,这时候线程可以并行的。
并发concurrent:同一时间应对多件事情的能力。一个线程轮流交替做多件事情
并行parallel:同一时间动手做多件事情的能力。几个线程做不同事互不干扰
2.3 多线程应用场合
程序需要同时执行两个或多个任务;
需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等;
需要一些后台运行的程序时
三、创建线程
线程通过java.lang.Thread类来体现。
Thread类的特性:
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体;
通过该Thread对象的start()方法来启动这个线程,而非直接调用run().
创建线程的四种方式:
继承Thread类的方式;
实现Runnable接口的方式;
实现Callable接口的方式;
线程池创建线程。
3.1、继承Thread类的
步骤:
1、自定义线程子类,继承Thread类;
2、子类重写Thread类中的run方法 --- run方法中的内容就是线程体
3、创建自定义线程类的对象
4、调用start方法启动线程
run() 和 start() 的区别?
~start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
~run():封装了要被线程执行的代码,可以被调用多次。
注意:启动线程要使用start方法,不要使用run方法。
线程不能重复启动
package day26;/*
* 聊天线程
* */
public class ChatThread extends Thread {@Overridepublic void run() {for (int i = 0; i <100 ; i++) {System.out.println("聊天");//睡眠//异常只能try/catch run方法不支持抛出try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}package day26;/*
* 喝酒线程
* */
public class DrinkThread extends Thread {@Overridepublic void run() {for (int i = 0; i <100 ; i++) {System.out.println("喝酒");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}package day26;/*
* 吃饭线程
* */
public class EatThread extends Thread {@Overridepublic void run() {for (int i = 0; i <100 ; i++) {System.out.println("吃饭");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}package day26;/*
* 创建多线程的方式:
* 1、继承Thread类
* 2、实现Runnable接口
* 3、实现Callable接口
* 4、线程池实现
* */
public class MyTest1 {public static void main(String[] args) {//创建线程类的对象DrinkThread drinkThread = new DrinkThread();ChatThread chatThread = new ChatThread();EatThread eatThread = new EatThread();//启动线程//这里如果改成.run(),就变成顺序执行的了。drinkThread.start();chatThread.start();eatThread.start();}
}
3.2、实现Runnable接口
步骤:
1、定义子类实现Runnable接口;
2、子类中重写Runnable接口中的run方法;
3、通过Thread类构造方法创建线程对象;
4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中;
5、调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
优势:
1、避免了单继承的局限性;
2、多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
package day26;public class DrinkThread1 implements Runnable {@Overridepublic void run() {for (int i = 0; i <100 ; i++) {System.out.println("喝酒");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}package day26;public class ChatThread1 implements Runnable {@Overridepublic void run() {for (int i = 0; i <100 ; i++) {System.out.println("聊天");//睡眠//异常只能try/catch run方法不支持抛出try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}package day26;public class EatThread1 implements Runnable {@Overridepublic void run() {for (int i = 0; i <100 ; i++) {System.out.println("吃饭");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}package day26;public class MyTest2 {public static void main(String[] args) {DrinkThread1 drinkThread1 = new DrinkThread1();EatThread1 eatThread1 = new EatThread1();ChatThread1 chatThread1 = new ChatThread1();//创建线程对象Thread th1 = new Thread(drinkThread1);Thread th2 = new Thread(eatThread1);Thread th3 = new Thread(chatThread1);//匿名内部类Thread th4 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i <100 ; i++) {System.out.println("唱歌");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});//启动线程th1.start();th2.start();th3.start();}
}
如何在多个线程中分享数据:
package day26;public class MyThread implements Runnable {//10只有一份private Integer count = 10;@Overridepublic void run() {System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" + count);}
}package day26;public class MyThread1 extends Thread {//要想让它像MyThread类一样用接口传递分享一份数据,用staticprivate static Integer count = 10;@Overridepublic void run() {System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" + count);}
}package day26;public class MyTest3 {public static void main(String[] args) {MyThread myThread = new MyThread();Thread th1 = new Thread(myThread);Thread th2 = new Thread(myThread);Thread th3 = new Thread(myThread);Thread th4 = new Thread(myThread);th1.start();th2.start();th3.start();th4.start();MyThread1 m1 = new MyThread1();MyThread1 m2 = new MyThread1();MyThread1 m3 = new MyThread1();m1.start();m2.start();m3.start();}
}
3.3、Thread类相关方法
相关方法:
start():启动线程,并执行对象的run()方法;
run():线程在被调用时执行的操作;
getName():返回线程的名称;
setName():设置线程的名称;
currentThread():返回当前线程。
package day26;/*
* 自定义线程类
* getName() --- 获取当前线程的名字
* */
public class MyThread extends Thread {@Overridepublic void run() {for (int i = 1; i <= 100 ; i++) {//System.out.println(getName() + i);System.out.println(Thread.currentThread().getName() + ": " + i);}}
}package day26;/*
* getName() - 获取当前线程的名字
* setName() - 为线程设置名字
* currentThread - 获取当前线程的引用
* */
public class MyTest {public static void main(String[] args) {//创建两个线程的对象MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("线程1");t2.setName("线程2");//比如main方法,没有获取线程引用时Thread.currentThread().setName("主线程");t1.start();t2.start();for (int i = 1; i <= 100 ; i++) {//System.out.println(getName() + i);System.out.println(Thread.currentThread().getName() + ": " + i);}}
}
四、线程的调度和生命周期
4..1、线程的调度
调度策略:
基于时间片轮转
抢占式:高优先级的线程抢占CPU
调度方法:
同优先级线程组成先进先出队列,使用时间片策略
对高优先级,使用优先调度的抢占式策略
Java中线程优先级的范围是1~10,默认的优先级是5
MAX_PRIORITY(10) MIN_PRIOPITY(1) NORM_PRIORITY(5)
涉及的方法:
setPriority(int newPriority):设置线程的优先级
getPriority():获取线程的优先级
yield():线程让步:暂停当前正在执行的线程。把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法。
join():
当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
低优先级的线程也可以获得执行。
sleep(long millis):令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行时,时间到后重排队
stop():强制线程生命期结束。
isAlive():判断线程是否还活着。
4.2、join()方法:
1)
package day26;/*
* t1
* t2 t1.join()
* t3 t2.join()
*
* 如果有多个线程,如何实现多个线程按照顺序运行 -- join
*
* */
public class MyTest1 {public static void main(String[] args) {Thread th1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("t1");}});Thread th2 = new Thread(new Runnable() {@Overridepublic void run() {try {th1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t2");}});Thread th3 = new Thread(new Runnable() {@Overridepublic void run() {try {th2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("t3");}});th1.start();th2.start();th3.start();}
}
package day26;/*
* 自定义线程类
* getName() --- 获取当前线程的名字
* */
public class MyThread extends Thread {@Overridepublic void run() {for (int i = 1; i <= 100 ; i++) {//System.out.println(getName() + i);System.out.println(Thread.currentThread().getName() + ": " + i);}}
}
2)更简便调用join方法
package day26;public class MyThread1 extends Thread {private Thread t;public MyThread1(Thread t){this.t = t;}@Overridepublic void run() {try {if(t != null){t.join();}} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName());}
}
package day26;public class MyTest2 {public static void main(String[] args) {MyThread1 t1 = new MyThread1(null);MyThread1 t2 = new MyThread1(t1);MyThread1 t3 = new MyThread1(t2);t1.setName("线程1");t2.setName("线程2");t3.setName("线程3");t1.start();t2.start();t3.start();}
}
4.3、线程生命周期
新建(NEW):
当一个线程对象被创建,但还未调用start方法时处于新建状态
此时未与操作系统底层线程关联
可运行(RUNNABLE):
调用了start方法,就会由新建进入可运行
此时与底层线程关联,由操作系统调度执行
终结(TERMINATED):
线程内代码已经执行完毕,由可运行进入终结
此时会取消与底层线程关联
阻塞(BLOCKED):
当获取锁失败后,由可运行进入Monitor的阻塞队列阻塞,此时不占用CPU时间
当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行
等待(WAITING):
当获取锁成功后,但由于条件不满足,调用了wait()方法,此时从可运行状态释放锁进入Monitor
。。。。。。。