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接口实现多线程,生产者消费者问题,多线下载(复制)文件