JAVA多线程的基本概念

多线程

问题

线程在什么时候进入阻塞状态

阻塞的情况分三种:
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类中定义了线程的几种状态

  1. 新建:当一个Thread类或其子类的对象在声明并且被创建的时候,新生线程对象就处于新建状态。这个时候还没有被执行;

  2. 就绪:当调用线程对象的start()方法之后,线程被安排进入等待cpu时间片,此时这个线程已经具备了运行条件,只是暂且还没有 分配到cpu资源。

  3. 运行:当已经处于就绪状态的线程分配到cpu的执行资源的时候,便进入运行状态run()方法定义了线程需要完成的工作和功能。

  4. 阻塞:在某种情况下,被认为挂起(例如sleep方法),或者进行输入输出操作的时候,这个时候该线程会让出cpu的执行资源 并且 临时中止自身的执行,我们称这种状态为阻塞状态。线程在等待时会进入阻塞状态

  5. 死亡:线程完成了自身的工作,自然的结束。或者被人为干预甚至遭遇异常被迫结束。这个时候我们称之为死亡状态。

    常见方法名

在这里插入图片描述

线程等待和线程谦和
线程等待

如果一个线程依赖一个或多个线程的数据,在这种情况下,这个线程就需要等待依赖线程执行完毕,才能继续执行。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种使用方式:
  • 修饰实例方法:作用于当前实例加锁
  • 修饰静态方法:作用于当前类对象加锁
  • 修饰代码块:指定加锁对象,对给定对象加锁
  • img

修饰代码块

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:关于同步方法
  1. 不要将run()定义为同步方法

  2. 非静态同步方法的同步监视器是this

静态同步方法的同步监视器是 类名.class 字节码信息对象

  1. 同步代码块的效率要高于同步方法

    原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部

  2. 非静态同步方法的锁是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状态线程,具体还要看业务来使用。

ThreadLocal
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值