Java---多线程

1.进程和线程的基本概念

进程是正在进行的程序,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程,多个线程可共享数据。

线程就是进程中的一个独立控制单元,一个单一的控制流,线程在控制着进程的执行,一个进程中至少有一个线程。

线程和进程的区别

内存角度:进程有独立的内存空间,进程中的数据存放空间(堆和栈)是独立的,进程中至少有一个线程;线程实际上没有其独立的内存空间,堆空间是共享的,栈空间是独立的。

资源角度:进程是资源分配的基本单位,线程是资源调度的基本单位,实际上不拥有资源,消耗的资源也比进程小,线程相互之间可以影响,又称为轻型进程或进程元。

上下文切换:指 CPU 从一个进程(或线程)切换到另一个进程(或线程)。上下文是指某一时间点 CPU 寄存器和程序计数器的内容。通常是计算密集型的,意味着此操作会消耗⼤量的 CPU 时间,故线程也不是越多越好。(指 CPU 从一个进程(或线程)切换到另一个进程(或线程)。上下文是指某一时间点 CPU 寄存器和程序计数器的内容。程序计数器是一个专用的寄存器,⽤于表明指令序列中 CPU 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,具体实现依赖于特定的系统。)

线程组:一个线程必定属于一个线程组,如果没有指定,默认是当前执行的线程。例如main函数中创建的线程所属的线程组。线程优先级不可超过线程组优先级

2.为什么要用多线程?

(1)为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;

(2)进程之间不能共享数据,线程可以;

(3)系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;

(4)Java语言内置了多线程功能支持,简化了java多线程编程。

3.线程的种类

(1)守护(Daemon)线程和用户线程

概念:守护线程守护用户线程,一旦用户线程结束了,守护线程还守护谁?没事做了jvm就退出了。守护线程是后台提供通用服务的线程,例如垃圾回收线程之类的。

可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。需在启动之前设置,否则会跑出一个IllegalThreadStateException异常,所以不能把正在运行的常规线程设置为守护线程。

Daemon线程中产生的新线程也是Daemon的。

守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断(因为用户线程执行完了,守护线程跟着没了)。

(2)父线程和子线程

概念:子线程是由父线程创建并启动的,在Java中两者没有本质区别。

JVM在启动时,首先创建main线程,去执行main方法。在main方法中创建其他的线程后,如果main线程执行完毕,其他线程也会继续执行。

需要注意的是,子线程会在默认情况下继承父线程的类别,如果父线程是守护线程,子线程也是守护线程。当然可以通过setDaemon方法改变属性。

默认情况,在新开启一个子线程的时候,他是前台线程,只有,将线程的IsBackground属性设为true,他才是后台线程;

当子线程是前台线程,则主线程结束并不影响其他线程的执行,只有所有前台线程都结束,程序结束;

当子线程是后台线程,则主线程的结束,会导致子线程的强迫结束;

后台线程一般做的都是需要花费大量时间的工作,如果不这样设计,主线程已经结束,而后台工作线程还在继续,第一有可能使程序陷入死循环,第二主线程已经结束,后台线程即时执行完成也已经没有什么实际的意义;

JVM的退出:会自动的检测注册的hook线程,并调用其run方法;释放资源。

4.线程的状态(线程的生命周期)

(1)初始(new):新创建了一个线程对象,但还没有调用start()方法。

实现Runnable接口:先创建一个类例如Arunnable实现Runnable接口(实现run()方法),再创建一个此类对象arunnable,并将此对象作为参数传递给Thread类用构造方法创建对象athread。

继承Thread类:先创建一个类Bthread继承Thread类(实现了Runnable),再创建一个对象bthread。

二者都是实现了Runnable接口,最后都通过对象将调用Thread类的start()方法启动

多使用接口的原因:接口可以多实现;Runnbal实现资源共享,而Thraed类中每个线程独立不共享。

packagemy_test;public classTestThread {public static voidmain(String[] args) {

Arunnable arunnable=newArunnable();

Thread athread=new Thread(arunnable);//接口类参数

athread.start();

Bthread bthread=newBthread();

bthread.start();

}

}class Arunnable implementsRunnable{

@Overridepublic voidrun() {

System.out.println("A启动了");

}

}class Bthread extendsThread{public voidrun() {

System.out.println("B启动了");

}

}/*输出

A启动了

B启动了*/

实现线程的方式

(2)运行(runnable):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。连续调用start()方法会抛出IllegalThreadStateException异常。

该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。(其他就绪状态:当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。锁池里的线程拿到对象锁后,进入就绪状态。)

(3)阻塞(blocked):表示线程阻塞于锁,等待锁的释放以进⼊同步区。

(4)等待(waiting):处于等待状态的线程变成runnable状态需要其他线程唤醒。

调用如下3个方法会使线程进入等待状态

Object.wait():使当前线程处于等待状态直到另一个线程唤醒它。调⽤wait方法前线程必须持有对象的锁。例如线程a调⽤wait()方法时,会释放当前的锁,直到有其他线程调⽤notify()/notifyAll()方法唤醒等待锁的线程。但是被唤醒后,如果有其他线程bcd也在等锁,不一定给a,看系统调度。

Thread.join():等待线程执行完毕,底层调用的是Object实例的wait方法。调⽤join()方法不会释放锁,会一直等待当前线程执行完毕(转换为TERMINATED状态)。假设当前执行的线程是a,有线程b调用了join方法,a交出cpu让b执行完先,此时a进入waiting状态,而b进入runable状态。如果join加了参数时间例如100,则表示线程a让线程b执行100毫秒先,然后就是二者并行。join(0)=join()。

LockSupport.park():除非获得调⽤许可,否则禁用当前线程进行线程调度。

(5)超时等待(timed_waiting):该状态不同于waiting,线程等待一个具体的时间,时间到后会被自动唤醒。

调⽤如下方法会使线程进⼊超时等待状态

Thread.sleep(long millis):使当前线程睡眠指定时间,不会释放锁,到时间后变回runable状态,

Object.wait(long timeout):线程休眠指定时间,会释放锁,等待期间可以通过notify()/notifyAll()唤醒;

Thread.join(long millis):使当前线程执行指定时间,并且使线程进⼊TIMED_WAITING状态。

LockSupport.parkNanos(long nanos):除⾮获得调⽤许可,否则禁⽤当前线程进行线程调度指定时间。如果在join的时间内线程还没结束,依旧是timed_waiting状态,时间一过才是terminated状态。

LockSupport.parkUntil(long deadline):同上,也是禁⽌线程进行调度指定时间;

(6)终止(terminated):表示该线程已经执行完毕。

5.线程的中断

interrupt()方法:作用是中断线程。

本线程中断自身是被允许的,且"中断标记"设置为true

其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。

若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。

如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。

如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。

interrupted()方法:判断的是当前线程是否处于中断状态。是类的静态方法,同时会清除线程的中断状态。

isInterrupted()方法:判断调用线程是否处于中断状态,不会清除状态。

6.线程中的常用方法

(1)getName();//获取线程名

(2)currentThread();//取得当前线程对象

(3)isAlive();//判断线程是否启动

(4)join();//线程的强行运行,需要先启动,t.join(),让线程t执行完再回来执行当前线程,会释放锁。

public classJoin {static class ThreadA implementsRunnable {

@Overridepublic voidrun() {try{

System.out.println("我是子线程,我先睡1秒");

Thread.sleep(1000);

System.out.println("我是子线程,我睡完了1秒");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}public static void main(String[] args) throwsInterruptedException {

Thread thread= new Thread(newThreadA());

thread.start();

thread.join();//当前线程指的是main线程,停止执行当前线程,先执行thread线程

System.out.println("如果不加join方法,我会先被打出来,加了就不一样了");

}

}

Join.class

(5)static void sleep(long millis)

使当前正在执行的线程暂停(睡眠)millis毫秒,睡眠的线程会变成阻塞状态,放弃争夺CPU资源,暂停结束后会变成就绪状态,等待运行。

调用方式:多线程直接调用sleep(millis)方法,其他情况都需要通过Thread.sleep(millis)方式调用,适用于网页爬虫等场景,假装是真人慢慢浏览,免得封号。需要捕获异常,不释放锁,易死锁。

(6)yield();//线程的礼让,让出CPU交给线程池中拥有相同优先级的线程,由运行状态变成就绪状态,并不是阻塞,不释放锁。

(7)setPriority(int newPriority);//设置线程的优先级

优先级的范围是1-10。MIN_PRIORITY,NORM_PRIORITY,MAX_PRIORITY分别表示1,5,10优先级。不设置默认是5。优先级高的线程1更有可能抢到CPU,但不是一定的。如果某个线程优先级大于线程所在线程组的最大优先级,那么该线程的优先级将会失效,取而代之的是线程组的最大优先级。

(9)suspend()resume()stop()是被弃用的。因为在调用方法之后,线程不会保证占用的资源被正常释放

(10)notify()

唤醒在此对象监视器上等待的单个线程。

(11)notifyAll()

唤醒在此对象监视器上等待的所有线程。

(12)wait()

wait一般和notify/notifyAll一起使用,这三个方法都是Object的方法,并且只能在synchrnoized中使用,因为wait和notify方法使用的前提是必须先获取一个锁。Wait的作用是使当前线程进入阻塞状态,放弃争夺CPU资源,释放锁,线程会进入该对象的等待池中,但不会主动去竞争该对象的锁;notify是随机唤醒一个等待当前对象的锁的线程,notifyAll是唤醒所有等待当前对象的锁的线程。wait方法必须放在同步块或同步方法中,

7.线程间的通信

(1)锁与同步

在Java中,锁的概念都是基于对象的,所以我们⼜经常称它为对象锁。一个锁同一时间只能被一个线程持有。线程A持有锁L,其他线程例如线程B要得到这个锁需要等A释放锁L。线程需要不断尝试获得锁,失败了就继续尝试,可能会耗费服务器资源。

关键字synchronized锁住的是对象,而不是代码。

public classTestThread {public static voidmain(String[] args) {

Object lock=newObject();

Athread a1=newAthread(lock);

Athread a2=newAthread(lock);

Athread a3=newAthread(lock);

Athread a4=newAthread(lock);//所有线程的对象锁都是同一个lock

a1.start();

a2.start();

a3.start();

a4.start();

}

}class Athread extendsThread{privateObject lock;publicAthread(Object lock) {this.lock=lock;

}

@Overridepublic voidrun() {

System.out.println("A启动了");synchronized(lock){for(int i=0;i<=10;i++) {//System.out.println("A的i="+i);

System.out.println("当前线程是:"+Thread.currentThread()+" i="+i);

}

}

}

}

synchronized锁对象

修饰方法的情况以后遇到再补

(2)等待/通知机制

基于Object 类的 wait() 方法和 notify() , notifyAll() 方法来实现的。notify()方法会随机叫醒一个正在等待的线程,而notifyAll()会叫醒所有正在等待的线程。例如有锁Object lock = new Object();是锁lock调用方法唤醒其他有lock锁的线程,lock在哪个线程类调用wait()就是让哪个线程等待。

(3)信号量

volatile来保证变量的同步,但是这种变量需要进行原子操作,i++这种不是原子操作,读取i,+1,赋值三个原子操作构成,这类操作要用synchronized上锁。volatile效果有点像static,static是在类里的,在实例对象中操作;volatile是搞主存的,保证多线程对于同一个变量的同步
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值