JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;

Posted 猿头猿脑的王狗蛋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;相关的知识,希望对你有一定的参考价值。

线程概述:


1.进程:指的是一个应用程序;而线程是进程的一个执行单元,一个进程可以启动多个线程;

2.对于java程序员来说,JAM就是一个进程,线程分为用户线程和守护线程;main方法就是一个用户线程(主线程),垃圾回收线程就是一个守护线程,java程序至少俩个线程并发,一个是垃圾回收线程,一个是main方法的主线程;

3.进程之间的内存是不会共享的,而对于线程而言,堆内存和方法区内存共享,但是栈内存独立,一个线程拥有一个独立的栈;

4.多线程并发:假设启动10个线程,就会有10个栈,各自运行,互不干扰,这就是多线程并发;

 

实现线程的俩种方式:


1.自定义一个类,直接继承 java . lang . Thread ,并重写Run方法;

  创建线程 --- new 线程对象(继承了Thread);

  启动线程 --- 调用 线程对象 . start ( ) 方法 // start ( ) 会在JVM中开辟一个新的栈空间;

  栗子老师:

public class ThreadPra1 {
    public static void main(String[] args) {
        //创建分支线程对象(属于主线程代码)
        ExtendsThread extendsThread=new ExtendsThread();
        //启动分支线程,开辟新的栈空间(属于主线程代码)
        extendsThread.start();
        //主线程的循环(属于主线程代码)
        for (int i=0;i<=100;i++){
            System.out.println("主线程执行的代码--->"+i);
        }
    }
}
//ExtendsThread代表自定义一个继承Thread 的继承类
class ExtendsThread extends Thread{
    //分支线程的循环(属于分支线程的代码)
    public void run() {
        for (int i=0;i<=100;i++){
            System.out.println("分支线程执行的代码--->"+i);
        }
    }
}
运行实例
主线程执行的代码--->0
主线程执行的代码--->1
主线程执行的代码--->2
主线程执行的代码--->3
主线程执行的代码--->4
主线程执行的代码--->5
主线程执行的代码--->6
主线程执行的代码--->7
主线程执行的代码--->8
主线程执行的代码--->9
主线程执行的代码--->10
主线程执行的代码--->11
主线程执行的代码--->12
主线程执行的代码--->13
主线程执行的代码--->14
分支线程执行的代码--->0
分支线程执行的代码--->1
分支线程执行的代码--->2
分支线程执行的代码--->3
主线程执行的代码--->15
分支线程执行的代码--->4
分支线程执行的代码--->5
分支线程执行的代码--->6
分支线程执行的代码--->7
分支线程执行的代码--->8
分支线程执行的代码--->9
分支线程执行的代码--->10
分支线程执行的代码--->11
分支线程执行的代码--->12
分支线程执行的代码--->13
分支线程执行的代码--->14
分支线程执行的代码--->15
分支线程执行的代码--->16
分支线程执行的代码--->17
分支线程执行的代码--->18
分支线程执行的代码--->19
分支线程执行的代码--->20
分支线程执行的代码--->21
分支线程执行的代码--->22
分支线程执行的代码--->23
分支线程执行的代码--->24
分支线程执行的代码--->25
分支线程执行的代码--->26
分支线程执行的代码--->27
分支线程执行的代码--->28
分支线程执行的代码--->29
分支线程执行的代码--->30
分支线程执行的代码--->31
主线程执行的代码--->16
分支线程执行的代码--->32
主线程执行的代码--->17
主线程执行的代码--->18
主线程执行的代码--->19
主线程执行的代码--->20
主线程执行的代码--->21
主线程执行的代码--->22
主线程执行的代码--->23
主线程执行的代码--->24
主线程执行的代码--->25
主线程执行的代码--->26
主线程执行的代码--->27
主线程执行的代码--->28
主线程执行的代码--->29
主线程执行的代码--->30
分支线程执行的代码--->33
主线程执行的代码--->31
分支线程执行的代码--->34
主线程执行的代码--->32
分支线程执行的代码--->35
主线程执行的代码--->33
分支线程执行的代码--->36
主线程执行的代码--->34
分支线程执行的代码--->37
主线程执行的代码--->35
分支线程执行的代码--->38
主线程执行的代码--->36
主线程执行的代码--->37
主线程执行的代码--->38
主线程执行的代码--->39
主线程执行的代码--->40
主线程执行的代码--->41
主线程执行的代码--->42
主线程执行的代码--->43
主线程执行的代码--->44
主线程执行的代码--->45
主线程执行的代码--->46
主线程执行的代码--->47
主线程执行的代码--->48
主线程执行的代码--->49
分支线程执行的代码--->39
主线程执行的代码--->50
分支线程执行的代码--->40
主线程执行的代码--->51
分支线程执行的代码--->41
主线程执行的代码--->52
分支线程执行的代码--->42
主线程执行的代码--->53
分支线程执行的代码--->43
主线程执行的代码--->54
分支线程执行的代码--->44
主线程执行的代码--->55
分支线程执行的代码--->45
主线程执行的代码--->56
分支线程执行的代码--->46
主线程执行的代码--->57
分支线程执行的代码--->47
分支线程执行的代码--->48
分支线程执行的代码--->49
主线程执行的代码--->58
分支线程执行的代码--->50
主线程执行的代码--->59
分支线程执行的代码--->51
主线程执行的代码--->60
分支线程执行的代码--->52
主线程执行的代码--->61
分支线程执行的代码--->53
主线程执行的代码--->62
分支线程执行的代码--->54
主线程执行的代码--->63
分支线程执行的代码--->55
主线程执行的代码--->64
分支线程执行的代码--->56
主线程执行的代码--->65
分支线程执行的代码--->57
主线程执行的代码--->66
分支线程执行的代码--->58
主线程执行的代码--->67
分支线程执行的代码--->59
主线程执行的代码--->68
分支线程执行的代码--->60
主线程执行的代码--->69
分支线程执行的代码--->61
主线程执行的代码--->70
分支线程执行的代码--->62
主线程执行的代码--->71
分支线程执行的代码--->63
主线程执行的代码--->72
分支线程执行的代码--->64
主线程执行的代码--->73
分支线程执行的代码--->65
主线程执行的代码--->74
分支线程执行的代码--->66
分支线程执行的代码--->67
分支线程执行的代码--->68
分支线程执行的代码--->69
分支线程执行的代码--->70
分支线程执行的代码--->71
分支线程执行的代码--->72
分支线程执行的代码--->73
分支线程执行的代码--->74
分支线程执行的代码--->75
分支线程执行的代码--->76
分支线程执行的代码--->77
分支线程执行的代码--->78
分支线程执行的代码--->79
分支线程执行的代码--->80
分支线程执行的代码--->81
分支线程执行的代码--->82
分支线程执行的代码--->83
分支线程执行的代码--->84
分支线程执行的代码--->85
分支线程执行的代码--->86
分支线程执行的代码--->87
分支线程执行的代码--->88
分支线程执行的代码--->89
分支线程执行的代码--->90
分支线程执行的代码--->91
分支线程执行的代码--->92
分支线程执行的代码--->93
分支线程执行的代码--->94
分支线程执行的代码--->95
分支线程执行的代码--->96
分支线程执行的代码--->97
分支线程执行的代码--->98
分支线程执行的代码--->99
主线程执行的代码--->75
分支线程执行的代码--->100
主线程执行的代码--->76
主线程执行的代码--->77
主线程执行的代码--->78
主线程执行的代码--->79
主线程执行的代码--->80
主线程执行的代码--->81
主线程执行的代码--->82
主线程执行的代码--->83
主线程执行的代码--->84
主线程执行的代码--->85
主线程执行的代码--->86
主线程执行的代码--->87
主线程执行的代码--->88
主线程执行的代码--->89
主线程执行的代码--->90
主线程执行的代码--->91
主线程执行的代码--->92
主线程执行的代码--->93
主线程执行的代码--->94
主线程执行的代码--->95
主线程执行的代码--->96
主线程执行的代码--->97
主线程执行的代码--->98
主线程执行的代码--->99
主线程执行的代码--->100

Process finished with exit code 0

2.编写一个类,实现 java . lang . Runnable 接口,重写 Run 方法;

   创建线程;

   启动线程;

public class ThreadPra1 {
    public static void main(String[] args) {
        //创建线程对象
        Thread thread=new Thread(new ImplementsRunnable());
        //启动分支线程
        thread.start();
    }
}
//ImplementsRunnable代表实现了Runnable接口的类
class ImplementsRunnable implements Runnable{
    public void run() {
        System.out.println("new Thread(new ImplementsRunnable())+重写Run方法--->创建线程(start Thread)");
    }
}

运行结果:
--------------------------
new Thread(new ImplementsRunnable())+重写Run方法--->创建线程(start Thread)

Process finished with exit code 0

注意:

1)第二个实现线程的方式更常用一些,因为一个类实现了一个接口的同时,还可以继承其他的类(面向接口编程);

2)通过匿名内部类也可以创建线程对象(实现 Runnable 接口):

public class ThreadPra1 {
    public static void main(String[] args) {
        //创建线程对象
        Thread thread=new Thread(){
            @Override
            public void run() {
                System.out.println("new Thread + 匿名内部类--->创建线程(start Thread)");
            }
        };
        //启动分支线程
        thread.start();
    }
}

运行结果:
----------------------------
new Thread + 匿名内部类--->创建线程(start Thread)

Process finished with exit code 0

 

线程生命周期:


 

 

Thread 常用方法:


1.String getName ( ) // 返回该线程的名称。

2.void setName(String name) // 改变线程名称

3.void sleep(long millis) 

// 使当前线程进入” 阻塞状态 “(休眠),放弃占有CPU时间片,让给其他线程使用(出现在main方法中,就是让主线程进行休眠)

4.void interrupt ( ) // 唤醒休眠线程(异常机理)

5.void join ( ) // 等待该线程终止

6.void run ( )  // 以不启动新线程的方式运行 run ( )

7.Thread . currentThread ( ) // 获取当前线程的引用

 

终止线程的三种方式:


1.Thread . stop ( ) 

//下下之选,容易丢失数据

2.Thread . interrupt ( ) 

//调用 interrupt ( )  方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程;Thread . interrupt ( ) 可以成功的唤醒正在休眠的线程;

3.使用标志位终止进程:

public class ThreadPra1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread=new MyThread();
        Thread thread=new Thread(myThread);
        thread.setName("分支线程");
        thread.start();
        //希望5秒后终止分支线程
        Thread.sleep(5000);
        myThread.run=false;
    }
}
class MyThread implements Runnable {
    //打一个布尔标记
    boolean run = true;
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (run) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在执行");
            } else{
                //在这里可以选择保存一些我们需要保存的数据
                return;
            }
        }
    }
}
运行实例
分支线程正在执行
分支线程正在执行
分支线程正在执行
分支线程正在执行
分支线程正在执行

Process finished with exit code 0

 

线程安全问题:


1.在多线程并发的环境下,当满足以下三个条件时,会存在线程安全问题

条件一,多线程并发;

条件二,有共享数据;

条件三:共享数据有修改行为;

2.怎么解决线程安全问题?

线程同步机制 --- 程序排队执行,会牺牲一部分效率;

3.这里会涉及到俩个模型:

1)异步编程模型:线程t1和线程t2,各自执行各自的,其实就是多线程并发(效率较高)

2)同步编程模型:当一个线程正在执行时,其他线程必须等此线程执行完毕才能执行,线程排队执行(效率较低);

 

线程同步机制:


#就下面这个代码讲讲如何通过 synchronized  实现线程同步:

此程序模拟两台机器对同一个账户进行取款,账户原余额1w,对其进行两次取款,每次5000元,由于多线程并发存在的隐患,可能导致第二次取款后,余额依然是5000

public class ThreadPra1 {
    public static void main(String[] args) throws InterruptedException {
        Account account=new Account("act-001",10000);
        Thread t1=new AccountThread(account);
        Thread t2=new AccountThread(account);
        t1.start();
        t2.start();
    }
}
class Account{
    private String acton;
    private double balance;
    public Account(String acton, double balance) {
        this.acton = acton;
        this.balance = balance;
    }
    public String getActon() {
        return acton;
    }
    public void setActon(String acton) {
        this.acton = acton;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    public void withdraw(double money){
        this.setBalance(balance-money);
    }
}
class AccountThread extends Thread{
    private Account account;
    public AccountThread(Account account) {
        this.account = account;
    }
    public void run() {
        account.withdraw(5000);
        System.out.println("账户"+account.getActon()+"取款成功,余额:"+account.getBalance());
    }
}

synchronized 的三种用法:

1.代码块锁://锁 Account (this)账户,因为此余额为1w的账户为两个线程的共有资源,所以当对账户进行取钱操作时,俩个线程将会排队取款(先拿到锁的先取款)

public void withdraw(double money){
        synchronized (this){
            this.setBalance(balance-money);
        }
    }

注意:synchronized 直接锁  AccountThread 中的取款5000代码也能达到同步线程的目的,但是扩大了同步的范围,会使效率降低;

2.实例方法上 synchronized://这种锁属于对象级别的锁,一个对象一把锁,一百个不同的对象就是100把锁

public synchronized void withdraw(double money){
    this.setBalance(balance-money);
}

这种方式的优缺点:

缺点:第一,只能锁 this 对象 ;第二,可能会导致无故扩大同步范围,效率降低,所以使用较少;

优点:代码写的少了,节俭了一些,所以如果共享的对象就是this,并且需要同步的代码块就是整个方法体,建议使用这种方式;

3.静态方法上 synchronized:

//这种锁属于类锁,一种类就一把锁,一百个对象也还是一把锁;

#java 中的三种变量,哪种存在线程安全问题?

1)局部变量永远都不会存在线程安全问题,因为局部变量是不共享的(一个线程一一个栈);

2)实例变量在堆中,堆只有1个,所以堆是多线程共享的,导致实例变量可能会存在线程安全问题;

3)静态变量在方法区中,方法区只有1个,所以方法区是多线程共享的,导致静态变量可能会存在线程安全问题;

 

随笔:


1.main方法结束,有没有可能程序不会结束?

main方法结束只代表主线程结束了,主栈空了,其他的栈,可能还在压栈弹栈,所以程序在main方法结束之后可能不会结束;

2.启动成功的线程会自动调用run方法;run方法在分支栈的底部(最先压栈),main方法在主栈的底部(最先压栈),所以run方法和main方法可以说是同级的

3.Thread . sleep ( ) 方法的一个面试题:

  哪个线程会进入休眠?

package com.bjpowernode.javase.threadTest;
public class ThreadPra1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new MyThread();
        thread.setName("分支");
        thread.start();
        thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"线程在执行");
    }
}
class MyThread extends Thread{
    public void run() {
            System.out.println(Thread.currentThread().getName()+"线程在执行");
    }
}

运行结果:
-----------------------------
分支线程在执行
main线程在执行

Process finished with exit code 0

由结果可以看出, thread . sleep ( 100 ) 使主线程休眠了,得出结论:

sleep ( ) 属于静态方法,调用 引用 . sleep ( ) 相当于 调用 Thread . sleep ( )  ,而 Thread . sleep ( ) 会使当前线程进入休眠,mian方法种的当前线程当然就是main线程,所以最后mian线程进入了休眠; 

 

以上是关于JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;的主要内容,如果未能解决你的问题,请参考以下文章

Java总结——通过Callable接口实现多线程,生产者消费者问题,多线下载(复制)文件

POSIX 多线程程序设计

java多线线程 1

写出java多线程程序设计中常用类及方法名,并分别说明它们的作用。

Java多线程程序设计初步入门

笔记之_Java整理IO流