线程和进程的理解
1.什么是进程?什么是线程?
进程是一个应用程序(一个进程是一个软件)
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程.
2、对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后。
会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾.
最起码,现在的java程序中至少有两个线程并发,
一个是垃圾回收线程,一个是执行main方法的主线程。
3.进程和线程是什么关系?举个例子
进程可以看做是现实生活当中的公司。
线程可以看做是公司当中的某个员工。
注意:
进程A和进程B的内存独立不共享。
线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈.
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,
互不干扰,各自执行各自的,这就是多线程并发。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
4. 思考一个问题:
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在
压栈弹栈.
5.分析一个问题:对于单核的cPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的.
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的.
t1不会影响t2。t2也不会影响t1.这叫做真正的多线程并发。
单核的cpu不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发"的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于
CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做!!!
线程的生命周期:
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法.
//定义线程类
public class MyThread extends Thread{
public void run() {
}
}
//创建线程对象
MyThread t = new MyThread();
//启动线程
t.start();
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法.
//定义一个可运行的类。
class MyRunnable implements Runnable{
@Override
public void run() {
}
}
//创建线程对象
Thread t=new Thread(new MyRunnable());
//启动线程
t.start();
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。
6.关于线程对象的生命周期?
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
实现线程的四种方式
实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread ,重写run方法。
怎么创建线程对象? new就行了。
怎么启动线程呢?调用线程对象的start()方法。
public class ThreadTest02 {
public static void main(String[] args) {
//这里是main方法,这里的额代码属于主线程,在主栈中运行。
//新建一个分支线程对象
MyThread t = new MyThread();
//启动线程
//t.run();//不会启动线程,不会分配新的分支栈。(这种方式就是单线程)
// start()方法的作用是:启动一个分支就程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来, start()方法就結束了。线程就启动成功了。
//启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
// run方法在分支栈的栈底部。main方法在主栈的栈底部。run和main是平级的。
t.start();
//下面的代码还是在主线程中运行
for (int i =0;i < 1000;i++){
System.out.println("主线程--->"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这段程序运行在分支线程中(分支栈)。
for(int i = 0; i < 1000; i++){
System.out.println("分支线程--->" + i);
}
}
}
上述代码start的内存图
上述代码run的内存图
实现线程的第二种方式。编写一个类实现java.lang.Runnable接口。
public class ThreadTest03 {
public static void main(String[] args) {
//创建一个可运行的对象
//MyRunnable r =new MyRunnable();
//将可运行的对象封装成一个线程对象
//Thread t=new Thread(r);
Thread t =new Thread(new MyRunnable());
t.start();
for (int i =0;i < 100;i++){
System.out.println("主线程--->"+i);
}
}
}
//这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("分支线程--->" + i);
}
}
}
实现线程的第三种方式:采用匿名内部类
public class ThreadTest04 {
public static void main(String[] args) {
//创建线程对象,采用匿名内部类方式
Thread t= new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println("分支线程--->" + i);
}
}
});
t.start();
for(int i = 0; i < 100; i++){
System.out.println("主线程--->" + i);
}
}
}
实现线程的第四种方式
实现线程的第四种方式:实现callable接口。 ( JDK8新特性。)
这种方式实现的线程可以获取线程的返回值.
之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,可能 会有一个执行结果,怎么能拿到这个执行结果呢?使用第四种方式:实现callable接口方式.
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
public class ThreadTest15 {
public static void main(String[] args) throws Exception {
//第一步:创建一个"未来任务类"对象
//参数非常重要,需要给一个Callable接口实现类对象。
FutureTask task=new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call()方法相当于run方法,只不过这个有返回值
//线程执行一个任务,执行完成之后会有一个执行结果
//模拟执行
System.out.println("call method begin!");
Thread.sleep(1000*10);
System.out.println("call method end!");
int a=100;
int b=200;
return a+b;//自动装箱(300结果变成Integer)
}
});
//创建线程对象
Thread t=new Thread(task);
//启动线程
t.start();
//这里是main方法,这是在主线程中。
//在主线程中,怎么获取t线程的返回結果?
//get()方法的执行会导致"当前线程阻塞"
Object obj = task.get();
System.out.println("线程执行结果:"+ obj);
// main方法这里的程序要想执行必须等待get()方法的結束
//而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
//另一个线程执行是需要时间的。
System.out.println("hello world!");
}
}
线程对象的获取
1、怎么获取当前线程对象?
Thread t= Thread.currentThread();
返回值t就是当前线程。
2、获取线程对象的名字
String name=线程对象.getName();
3、修改线程对象的名宇
线程对象.setName(“线程名字”);
4、当线程没有设置名字的时候,默认的名字有什么规律?
Thread-0
Thread-1
public class ThreadTest05 {
public static void main(String[] args) {
//currentThread就是当前线程对象
//这个代码出现在main方法当中,所以当前线程就是主线程
Thread currentThread=Thread.currentThread();
System.out.println(currentThread.getName());//main
//创建线程对象
MyThread2 t = new MyThread2();
//设置线程的名字
//t.setName("tt");
//获取线程的名字
String tName=t.getName();//线程默认的名字 Thread-0
System.out.println(tName);
MyThread2 t2=new MyThread2();
System.out.println(t2.getName());//默认的名字是 Thread-1
//启动线程
t.start();
}
}
class MyThread2 extends Thread{
public void run(){
for (int i=0;i<100;i++){
System.out.println("分支线程--->"+i);
}
}
}
线程的睡眠
关于线程的sleep方法: sleep–进入阻塞状态
static void sleep(long millis)
1.静态方法: Thread.sleep(1000);
2.参数是毫秒
3.作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片.让给其它线程使用。
这行代码出现在A线程中, A线程就会进入休眠。
这行代码出现在B线程中, B线程就会进入休眠。
4. Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
public class ThreadTest06 {
public static void main(String[] args) {
//让当前线程进入休眠,睡眠5秒
//当前线程是主线程!!!
/*
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
//5秒之后执行这里的代码
//System.out.println("hello world!");
for (int i =0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadTest07 {
public static void main(String[] args) {
//创建线程对象
Thread t=new MyThread3();
t.setName("t");
t.start();
//调用sleep方法
try {
//问题:这行代码会让线程t进入休眠状态吗?
// 不会 sleep()方法是静态方法,实质上还是通过类名调用。
t.sleep(1000*5);//在执行的时候还是会转换成:Thread.sleep(1000*5);
//这行代码的作用是:让当前线程进入休眠,也就是main线程进入休眠
System.out.println("hello world!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread3 extends Thread{
public void run(){
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
终止线程的睡眠
注:这个不是中断线程的执行,是终止线程的睡眠
public class ThreadTest08 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable2());
t.setName("t");
t.start();
//希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了)
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止t线程的睡眠(这种中断线程睡眠的方式依靠了java的异常处理机制)
t.interrupt();
}
}
class MyRunnable2 implements Runnable{
//重点:run() 当中的异常不能throws ,只能try catch
//因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->begin");
try {
Thread.sleep(1000*60*60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->end");
}
}
在java中怎么强行终止一个线程的执行。
以下这种方式存在很大的缺点: 容易丢失数据。因为这种方式是直接存线程杀死了。
线程没有保存的数据得会丢失。不建议使用。
public class ThreadTest09 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后强行终止t线程
t.stop();//已过时,不建议使用
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
合理的终止线程的执行:
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 r=new MyRunnable4();
Thread t=new Thread(r);
t.setName("t");
t.start();
//模拟5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程
//想要什么时候终止t的执行,把标记修改为false就结束了。
r.run=false;
}
}
class MyRunnable4 implements Runnable{
//打一个布尔标记
boolean run=true;
@Override
public void run() {
for (int i=0;i<10;i++){
if (run){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
// return就结束了,在结束之前还有什么没保存的。
//在这里可以保存呀。
//save....
//终止当前线程
return;
}
}
}
}