多线程
问题
线程在什么时候进入阻塞状态
阻塞的情况分三种:
1等待阻塞(o.wait->等待对列):
运行的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue) 中。
2 同步阻塞(lock->锁池)
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。
3其他阻塞(sleep/join)
运行的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行状态.
如何使两个线程获得尽可能相等的执行资源
什么情况下线程处于等待状态
多线程的存在可使程序在哪几个方面变得更好
线程生命周期
过程 在Thread.State类中定义了线程的几种状态
-
新建:当一个Thread类或其子类的对象在声明并且被创建的时候,新生线程对象就处于新建状态。这个时候还没有被执行;
-
就绪:当调用线程对象的start()方法之后,线程被安排进入等待cpu时间片,此时这个线程已经具备了运行条件,只是暂且还没有 分配到cpu资源。
-
运行:当已经处于就绪状态的线程分配到cpu的执行资源的时候,便进入运行状态run()方法定义了线程需要完成的工作和功能。
-
阻塞:在某种情况下,被认为挂起(例如sleep方法),或者进行输入输出操作的时候,这个时候该线程会让出cpu的执行资源 并且 临时中止自身的执行,我们称这种状态为阻塞状态。线程在等待时会进入阻塞状态
-
死亡:线程完成了自身的工作,自然的结束。或者被人为干预甚至遭遇异常被迫结束。这个时候我们称之为死亡状态。
常见方法名
线程等待和线程谦和
线程等待
如果一个线程依赖一个或多个线程的数据,在这种情况下,这个线程就需要等待依赖线程执行完毕,才能继续执行。jdk就给我们提供了join()这个方法。
public final void join() throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException
例子
public class join {
public volatile static int i = 0;
public static class addThread extends Thread{
@Override
public void run(){
for(int i = 0;i<999999999;i++);
}
}
public static void main(String[] args) throws InterruptedException{
addThread at = new addThread();
at.start();
at.join();//若不使用join,主函数会数据一个很小的数字,甚至是0
//使用后表示愿意等待addThread执行完毕
System.out.println(i);
}
}
线程谦和
当其他线程执行完毕,或者超过等待时间将会执行notity方法。
public static native void yield();
yield是一个静态的方法,如果一个线程不是那么的重要,或者优先级特别的低,并且希望他不要占用太多的cpu,就可以在适当的地方调用thread.yield。
他表示会给其他重要的线程更多的执行机会,但是cpu可以忽略这个方法
线程优先级
设置线程优先级
package com.MThread;
public class Mythread2 implements Runnable {
private char letter;
public Mythread2(char letter) {
this.letter = letter;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(letter+"+"+i);
}
}
}
public class MainF {
public static void main(String[] args) throws InterruptedException {
MyThread myThread=new MyThread();
Thread t1=new Thread(new Mythread2('_'));
Thread t2=new Thread(new Mythread2('0'));
t1.start();
t1.setPriority(Thread.MAX_PRIORITY);//优先级最高 10
t2.start();
t2.setPriority(Thread.MIN_PRIORITY);//优先级最低 1
}
}
线程1提前完成
用户线程和守护线程
如果一个进程中,有用户线程在执行,进程就继续执行
如果一个进程中没有任何用户线程在执行,进程将会结束,即使有再多守护线程,进程也会结束
设置守护线程的方法 线程名.setDaemon(true);
线程安全
线程安全是在多线程编程中,有可能会出现同时访问同一个 共享、可变资源 的情况,始终都不会导致数据破坏以及其他不该出现的结果。这种资源可以是一个变量、一个对象、一个文件等。
例题
线程锁
锁对象规范要求
规范上:建议使用共享资源作为锁对象
对于实例方法建议用this作为锁对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象
synchronized
synchronized中文意思是同步,也称之为”同步锁“。
synchronized的作用是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。
synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
synchronized的3种使用方式:
- 修饰实例方法:作用于当前实例加锁
- 修饰静态方法:作用于当前类对象加锁
- 修饰代码块:指定加锁对象,对给定对象加锁
修饰代码块
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
public class Demo00 {
public static void main(String args[]){
//调用方式一:test01
//SyncThread s1 = new SyncThread();
//SyncThread s2 = new SyncThread();
//Thread t1 = new Thread(s1);
//Thread t2 = new Thread(s2);
//调用方式二:test02
SyncThread s = new SyncThread();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
}
}
在调用方式二中,两个线程调用的是一个对象,因为synchronized(this),所以互斥,一个线程在执行时,另一个线程会处于阻塞状态,只有一个执行完,另一个才会继续执行
在调用方式一中,两个线程同时执行,因为synchronized(this)锁定的是对象,每个对象只有一个锁(lock)与之相关联
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
使用总结
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;
如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
总结1:关于同步代码块:
1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块
2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块
关于同步方法总结
总结2:关于同步方法
-
不要将run()定义为同步方法
-
非静态同步方法的同步监视器是this
静态同步方法的同步监视器是 类名.class 字节码信息对象
-
同步代码块的效率要高于同步方法
原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
-
非静态同步方法的锁是this,一旦锁住一个方法,就锁住了所有的非静态同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块
public class A {
public synchronized void m1() {//锁是当前的实例对象
try {
for (int i = 1; i <= 10; i++) {
StringBuilder sb = new StringBuilder(Thread.currentThread().getName());
sb.append("-->").append("A m1()");
System.out.println(sb);
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void m3() {//锁是当前的实例对象
try {
for (int i = 1; i <= 10; i++) {
StringBuilder sb = new StringBuilder(Thread.currentThread().getName());
sb.append("-->").append("A m1()");
System.out.println(sb);
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class test7 {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
new Thread("t1"){
@Override
public void run() {
a1.m1();
}
}.start();
new Thread("t2"){
@Override
public void run() {
a1.m3();
}
}.start();
new Thread("t3"){
@Override
public void run() {
a1.m3();
}
}.start();
}
}
死锁
发生原因,多个线程互相锁定了其他线程需要的锁旗标,导致线程互相牵制,无法继续执行
生产者消费者模式
两个线程一个打印数字,一个打印字母
要求打印出1A2B3C。。。。26Z
//主函数
class main{
public static void main(String[] args) {
Factory factory=new Factory();
Consumer c=new Consemer(factory);
Producer p=new Producer(factory);
Thread t1=new Thread(c);
Thread t2=new Thread(p);
t1.start();
t2.start();
}
}
wait()和notify方法
wait()当前线程开始等待,同时释放锁,
notify()让等待中的线程解除等待
notify会唤醒此Object控制权下的一个处于 wait 状态的线程。若有多个线程处于此 object 控制权下的 wait 状态,只有一个会被唤醒。
notifyAll唤醒所有处于此 object 控制权下的 wait 状态的线程。
如果一个Object对象被多个线程使用时,可以使用notifyAll唤醒全部wait状态线程,具体还要看业务来使用。
ThreadLocal
旗标,导致线程互相牵制,无法继续执行
生产者消费者模式
[外链图片转存中…(img-q9nydlpp-1659685629299)]
wait()和notify方法
wait()当前线程开始等待,同时释放锁,
notify()让等待中的线程解除等待
notify会唤醒此Object控制权下的一个处于 wait 状态的线程。若有多个线程处于此 object 控制权下的 wait 状态,只有一个会被唤醒。
notifyAll唤醒所有处于此 object 控制权下的 wait 状态的线程。
如果一个Object对象被多个线程使用时,可以使用notifyAll唤醒全部wait状态线程,具体还要看业务来使用。