文章目录
- 1.多线程概述
- 2. 线程和进程的关系
- 3.分析程序存在几个线程
- 4. 实现线程的三种方式
- 5.Thread和Runnable的区别
- 6.线程的生命周期
- 7.线程中的常用方法
- 8.线程的sleep方法
- 9. 终止线程睡眠
- 10.关于synchronized关键字
- 11.守护线程
- 12.定时器作用和如何实现
1.多线程概述
什么是进程?什么是线程?
进程是一个应用程序(一个进程是一个软件)。
线程是一个进程中的执行单元或者执行场景。
2. 线程和进程的关系
举个例子:
阿里巴巴:进程A
马云:阿里巴巴的一个线程a
童文红:阿里巴巴的一个线程b
京东:进程B
刘强东:京东的一个线程a
奶茶妹妹:京东的一个线程b
进程A和进程B的内存独立不共享。
一个进程可以启动多个线程。
那么线程a和线程b呢?
在java语言中,线程a和线程b堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动十个线程,会有十个栈空间,每个栈与每个栈之间各自执行各的,互不干扰,这就是多线程并发。
思考一个问题:使用了多线程机制后,main方法结束,是不是整个程序也不会结束?
main方法结束,只是主栈空了,其他的栈(线程)可能还在压栈弹栈。
如图:
3.分析程序存在几个线程
分析下面代码有几个线程?
public class csdnTest {
public static void main(String[] args) {
System.out.println("main begin!");
m1();
System.out.println("main over!");
}
private static void m1(){
System.out.println("m1 begin!");
m2();
System.out.println("m1 over!");
}
private static void m2(){
System.out.println("m2 begin!");
m3();
System.out.println("m2 over!");
}
private static void m3(){
System.out.println("m3 begin!");
System.out.println("m3 over!");
}
}
答案是:一个。
4. 实现线程的三种方式
第一种方式:
/*
实现线程的第一种方式:编写一个类,继承Thread,重写run方法
*/
public class ThreadTest01 {
public static void main(String[] args) {
//创建一个分支栈的对象
elseThread elseThread=new elseThread();
//启动线程
//start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这段代码任务完成后就,瞬间就结束了
//启动成功的线程会自动调用run()方法,并且run方法在分支栈的底部(压栈)
//run方法在分支栈的底部,main方法在主栈的栈底部,run和main是平级的
//elseThread.run(); 不会启动线程,不会分配新的分支栈(这种方式就是单线程)只是一个普通方法的调用
elseThread.start();//这行代码不结束,下面的代码永远不会执行
//这里的代码还是在主线程中
for (int b=0;b<1000;b++){
System.out.println("主线程————————"+b);
}
}
}
class elseThread extends Thread{
public void run() {
for (int a=0;a<1000;a++){
System.out.println("支线程——————"+a);
}
}
}
运行结果:出现以下结果和线程抢占cpu时间片有关,主线程和支线程共同抢夺cpu时间片(也就是执行权),具体原理参考下面线程生命周期详解。
第二种方式:
/*
实现线程的第二种方式,编写一个类,实现Runnable接口,实现run方法,这种方式比较常用,因为一个类实现了一个接口,还可以继承其他的
类,但是一个类继承了一个类,就无法继承其他的类了
*/
public class ThreadTest02 {
public static void main(String[] args) {
s s=new s();
Thread thread=new Thread(s);
thread.start();
for (int b=0;b<1000;b++){
System.out.println("主线程————————"+b);
}
}
}
class s implements Runnable{
@Override
public void run() {
for (int a=0;a<1000;a++){
System.out.println("支线程——————"+a);
}
}
}
运行结果:
第三种方式:
/*
采用匿名内部类的方法
*/
public class ThreadTest03 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int a = 0; a < 1000; a++) {
System.out.println("支线程——————" + a);
}
}
});
t.start();
for (int b = 0; b < 1000; b++) {
System.out.println("主线程————————" + b);
}
}
}
运行结果:
5.Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
6.线程的生命周期
线程生命周期存在五种状态:
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
如图:
7.线程中的常用方法
1.设置线程名字
2.获取线程名字
3.启动支线程
如下:
public class ThreadTest04 {
public static void main(String[] args) {
sss s=new sss();
//支线程永远次于主线程执行*****
s.start();
//设置线程名字
s.setName("s1");
//输出线程名字
System.out.println(s.getName());
}
}
class sss extends Thread{
@Override
public void run() {
for (int a=0;a<1000;a++){
System.out.println("支线程——————"+a);
}
}
}
运行结果:
4.获取当前线程对象
如下:
public class ThreadTest05 {
public static void main(String[] args) {
gxs gxs=new gxs();
Thread thread=new Thread(gxs);
thread.start();
//获取当前线程对象 currentThread就是当前线程,当前线程也就是main方法主线程
Thread currentThread=Thread.currentThread();
thread.setName("支线程");
//输出main
System.out.println(currentThread.getName());
for (int b=0;b<1000;b++){
System.out.println(currentThread.getName()+"————————"+b);
}
}
}
class gxs implements Runnable{
@Override
public void run() {
for (int a=0;a<1000;a++){
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"——————————"+a);
}
}
}
运行结果:
8.线程的sleep方法
Thread.sleep(long millis)
使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
如下:
/*
关于线程sleep方法:
1.静态方法
2.参数是毫秒
3.作用:让当前线程进入休眠状态,进入阻塞状态,放弃占有cpu时间片,让给其他线程使用
这段代码出现在a线程就会让a线程休眠
这段代码出现在b线程就会让b线程休眠
*/
public class ThreadTest06 {
public static void main(String[] args) {
try {
//让当前线程休眠五秒钟
Thread.sleep(1000*5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("睡醒了!");
}
}
运行结果:程序等待五秒钟,然后输出“睡醒了!”
sleep方法可以模拟计时器:
//相当于是个定时器
public class ThreadTest07 {
public static void main(String[] args) {
for (int i=0;i<=10;i++){
System.out.println(i);
try {
//每隔一秒钟输出一个数字
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
运行结果:每隔一秒输出一个数字
9. 终止线程睡眠
依靠java异常处理机制,中断当前线程睡眠,使用关键字interrupt。
public class ThreadTest08 {
public static void main(String[] args) {
Thread thread=new Thread(new teacher());
thread.start();
thread.setName("hahah");
//模拟睡眠,希望线程睡眠五秒钟醒来
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
//中断线程睡眠,这种中断睡眠的方式依靠了java异常处理机制
}thread.interrupt();
}
}
class teacher implements Runnable{
//这里不能throws,因为子类重写,不能抛出比父类更多的异常
//因为run方法的父类没有抛出任何异常.所以子类不能抛出异常
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-------"+"begin");
try {
Thread.sleep(10000*60*60*24*365);
} catch (InterruptedException e) {
//throw语句一执行,后面的语句执行不到了
throw new RuntimeException(e);
}
System.out.println("over");
}
}
运行结果:支线程本应该睡眠一年,但是在主线程里面模拟线程睡眠五秒钟,然后通过java异常处理机制,抛出异常,终止线程睡眠。
10.关于synchronized关键字
synchronized关键字的作用:
在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能),这点确实也是很重要的。
synchronized的三种应用方法:
1.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
2.修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁,一个对象一把锁。
3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
第一种方式:修饰实例方法
/*
多线程代码现在的处理器一般都能解决,唯一需要解决的是线程安全*****
线程同步这一块,涉及两个专业术语:
异步编程模型:异步就是并发
同步编程模型:同步就是排队
*/
public class ThreadsafeTest10 {
public static void main(String[] args) {
//创建一个对象
bank b=new bank("gxs",10000);
//创建两个线程
Thread t=new account(b);
Thread t1=new account(b);
//设置线程名字
t.setName("t1");
t1.setName("t2");
//启动两个线程
t.start();
t1.start();
}
}
class bank {
String id;
double balance;
public bank() {
}
public bank(String id, double balance) {
this.id = id;
this.balance = balance;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getBalance() {
return balance;
}
//定义一个模拟银行取款的方法
public void setBalance(double balance) {
this.balance = balance;
}
public synchronized void tikuan(double num) throws InterruptedException {
//t1和t2线程并发,两个线程对应两个栈,操作同一个对象
//synchronized () {线程同步代码块},处理线程同步机制语法,小括号里面填什么?填多个线程共享的对象,这里的共享对象是银行对象
// Thread.sleep(1000);
double before = this.balance;
double after = before - num;
//模拟发生网络延迟,余额更新慢延迟了
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//更新余额
this.setBalance(after);
//System.out.println(after);
}
}
class account extends Thread{
private bank b;
public account(bank b) {
this.b=b;
}
@Override
public void run() {
//模拟取款操作
double money=5000;
//调用取款方法
try {
b.tikuan(money);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread()+"对"+b.getId()+"取款成功"+"余额为"+b.getBalance());
}
}
运行结果:使用synchronized关键字可以避免两个用户同时对同一种银行卡进行取款操作时,发生同时取走5000元,余额却还显示5000元。
第二种方式:修饰静态方法
public class test02 {
public static void main(String[] args) throws InterruptedException {
gxs g=new gxs();
Thread t1=new mythread(g);
Thread t2=new mythread(g);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class mythread extends Thread{
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
g.dosome();
}
if (Thread.currentThread().getName().equals("t2")) {
g.doother();
}
}
public gxs g;
public mythread(gxs g) {
this.g=g;
}
}
class gxs{
//这是类锁
public synchronized static void dosome(){
System.out.println("begin");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("over");
}
//这是对象锁
public synchronized static void doother(){
System.out.println("过一会再输出吧");
}
}
运行结果:先输出begin,过五秒几乎同时输出“over”和“过一会再输出吧”。注意:必须要对两个静态方法都要加上synchronized关键字,否则doother方法是不会等待t1线程结束再执行的。
第三种方式:修饰代码块
/*
多线程代码现在的处理器一般都能解决,唯一需要解决的是线程安全*****
线程同步这一块,涉及两个专业术语:
异步编程模型:异步就是并发
同步编程模型:同步就是排队
*/
public class ThreadsafeTest10 {
public static void main(String[] args) {
//创建一个对象
bank b=new bank("gxs",10000);
//创建两个线程
Thread t=new account(b);
Thread t1=new account(b);
//设置线程名字
t.setName("t1");
t1.setName("t2");
//启动两个线程
t.start();
t1.start();
}
}
class bank {
String id;
double balance;
public bank() {
}
public bank(String id, double balance) {
this.id = id;
this.balance = balance;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public double getBalance() {
return balance;
}
//定义一个模拟银行取款的方法
public void setBalance(double balance) {
this.balance = balance;
}
public void tikuan(double num) throws InterruptedException {
//t1和t2线程并发,两个线程对应两个栈,操作同一个对象
//synchronized () {线程同步代码块},处理线程同步机制语法,小括号里面填什么?填多个线程共享的对象,这里的共享对象是银行对象
// Thread.sleep(1000);
synchronized (this) {
double before = this.balance;
double after = before - num;
//模拟发生网络延迟,余额更新慢延迟了
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//更新余额
this.setBalance(after);
//System.out.println(after);
}
}
}
class account extends Thread{
private bank b;
public account(bank b) {
this.b=b;
}
@Override
public void run() {
//模拟取款操作
double money=5000;
//调用取款方法
try {
b.tikuan(money);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread()+"对"+b.getId()+"取款成功"+"余额为"+b.getBalance());
}
}
运行结果:
11.守护线程
概述:
java提供两种类型线程:一个是用户线程,一个是守护线程。
用户线程:是高优先级线程。JVM 会在终止之前等待任何用户线程完成其任务。
守护线程: 是低优先级线程。其唯一作用是为用户线程提供服务。
守护线程用来做什么?
常见的做法,就是将守护线程用于后台支持任务,比如垃圾回收、释放未使用对象的内存、从缓存中删除不需要的条目,按照这个解释,那么大多数 JVM 线程都是守护线程。
如何创建守护线程?
守护线程也是一个线程,因此它的创建和启动其实和普通线程没什么区别,要将普通线程设置为守护线程,方法很简单,只需要调用 Thread.setDaemon() 方法即可。
代码如下:旨在用户线程结束时,守护线程也跟着结束。
public class BakDataThreadTest {
public static void main(String[] args) {
//创建守护线程对象,父类型引用指向子类型对象
Thread thread=new bakdata();
//启动线程之前,将这个线程设置为守护线程
thread.setDaemon(true);
thread.start();
thread.setName("守护线程");
Thread current=Thread.currentThread();
current.setName("主线程");
//主线程:也就是用户线程
for (int i=0;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"---------->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//这是个守护线程
class bakdata extends Thread{
@Override
public void run() {
int i=0;
//虽然是个死循环,但是用户线程一结束,守护线程自动结束
while(true){
System.out.println(Thread.currentThread().getName()+"------->"+(++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
运行结果:主线程循环十次输出到10,守护线程察觉到用户线程结束,便也会自动结束。
12.定时器作用和如何实现
作用:
间隔特定的时间,执行特定的程序。
每周要进行银行总账操作。
每天要进行数据备份操作。
在实际开发中,每隔多久执行一个程序是很常见的。在java中有很多种实现方式:
1.可以使用sleep方法,每隔一段时间线程醒来,执行特定的程序,这是最原始的定时器(但是比较low)。
2.在java中已经写好了一个定时器:java.util.Timer,可以直接拿来用,但是这种方式在目前开发中还用得少,因为现在很多高级框架都时支持定时任务的。
3.在实际的开发中,目前是用的比较多的是spring框架中提供的springTask框架,在这个框架中只要进行简单的配置,就可以实现定时器任务。
实现方式:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) {
//创建定时器对象
Timer timer=new Timer();
//获取当前时间
Date nowtime=new Date();
System.out.println(nowtime);
//给日期制定一个格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
//SimpleDateFormat里面有一format方法,将现在的时间转换成指定格式
//System.out.println(sdf.format(nowtime));
timer.schedule(new logtimertask(),nowtime,1000*10);
}
}
//public abstract class TimerTask
//extends Object
//implements Runnable
class logtimertask extends TimerTask {
@Override
public void run() {
//编写你需要执行的任务
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
String time=sdf.format(new Date());
System.out.println("电脑"+time+" 完成日志记录!");
}
}
运行结果: