线程的调度机制及线程同步问题

调度机制

  • 具体的调度实现分为操作系统和JVM

    • 操作系统的调度机制有多种,一般常见的有2种:时间片Unix和抢占式windows
  • 所有的Java虚拟机都有一个线程调度器,用来确定那个时刻运行那个线程。主要有两种调度模型:分时调度模型和抢占式调度模型

    • 基于时间片轮转法的抢占式调度。线程调度器会给高优先级有更多的运行机会,如果优先级相同则随机选中,执行时长到达时间片后从新进行调度
  • 线程的调度不是跨平台的,它不仅取决于Java虚拟机,还依赖于操作系统。在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行的机会

线程的优先级

Java采用的是抢占式的调度机制,提供一个线程调度器来监控程序中启动后进入就绪态的所有线程。线程调度器会按照线程的优先级决定应调度哪个线程来执行

线程的优先级用数字表示,范围从1到10,默认值5。但是需要注意的是优先级的具体实现需要依赖于操作系统,所以Java中优先级需要映射到操作系统上,所以可能会出现操作系统中支持的优先级数量少,Java中多个优先级映射到同一个操作系统优先级上。所以在具体使用中建议优先级的差值应该比较大,建议使用预定义的常量
t2.setPriority(Thread.MAX_PRIORITY);----10
t1.setPriority(Thread.NORM_PRIORITY);-----5默认值
t3.setPriority(Thread.MIN_PRIORITY);-----1

getPriority():int 获取当前线程对象的优先级

setPriority(int):void 设置当前线程对象的优先级,注意修改优先级必须在start()方法调用之前

总结

1、在没有指定线程的优先级的时候,线程都带有普通的优先级。

2、线程的优先级可以分为1到10;10代表最高的优先级,1代表最低的优先级,普通优先级是5.

3、优先级最高的线程在运行时给予优先,但不能保证线程启动后立刻就进入运行状态。

4、与线程池中等待的线程相比,正在运行的线程拥有更高的优先级。

5、由调度程序来决定执行哪一个线程。

6、用setPriority(int)来设定用线程的优先级。

7、在线程的start方法调用之前,应该指定线程的优先级

线程的同步问题

多线程编程中容易出现错误情况,这是由系统的线程调度具有一定随机性造成的,这些情况必须杜绝。
- 线程的执行顺序不可重现,但是执行结果一定可重现
- 解决方案就是同步处理

同步的处理方法1

当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。

具体编程中使用synchronized关键字可以使用互斥锁。

synchronized将并行改为串行,当然会影响程序的执行效率,执行速度会受到影响。其次synchronized操作线程的堵塞,也就是由操作系统控制CPU的内核进行上下文的切换,这个切换本身也是耗时的。所以使用synchronized关键字会降低程序的运行效率。

编程实现

需求:定义4个线程对同一个num进行加减计算,2个线程各执行50次加,2个线程各执行50次减

1、定义一个类用于封装需要操作的数据和对应的操作方法

//封装数据和对应的操作
public class NumOps {
    
    
	private int num;//具体操作数据
	
	//提供的方法,业务处理
	public void add() {
    
    
		num=num+1;
		System.out.println(Thread.currentThread().getName()+":add...."+this.num);
	}
	public void sub() {
    
    
		num=num-1;
		System.out.println(Thread.currentThread().getName()+":sub...."+this.num);
	}

}

2、定义对应的线程实现
定义线程有4种方法: extends Thread、implements Runnable、Callable和Future、线程池

  • Thread基本不使用,因为单根继承
  • Runnable一般用于没有返回值的情况下,使用较多,如果需要记录线程的执行结果必须自行编程实现
  • Callable一般用于有返回值的情况下,需要配置FutureTask进行使用。call方法允许抛出异常
  • 5种线程池一般建议使用ThreadPoolExecutor使用,不建议使用系统预定义,用于在短时间内有大量的短时间执行的线程需求时

实际具体开发中很少直接使用线程,一般通过框架使用线程

public class Test1 {
    
    
	private static NumOps ops=new NumOps();
	
	static class AddThread extends Thread{
    
    
		@Override
		public void run() {
    
    
			for(int i=0;i<50;i++) {
    
    
				ops.add();
			}
		}
	}
	static class SubRunnable implements Runnable{
    
    
		@Override
		public void run() {
    
    
			for(int i=0;i<50;i++) {
    
    
				ops.sub();
			}
		}
	}
}

3、启动2个加线程和2个减线程

public class Test1 {
    
    
	private static NumOps ops=new NumOps();
	public static void main(String[] args) throws Exception {
    
    
		Thread[] arr=new Thread[4];
		for(int i=0;i<4;i++) {
    
    
			if(i%2==0) {
    
    
				AddThread at=new AddThread();
				arr[i]=at;
				at.start();
			}else {
    
    
				Thread st=new Thread(new SubRunnable());
				arr[i]=st;
				st.start();
			}
		}
		for(Thread tmp:arr) {
    
    
			tmp.join();
		}
		System.out.println("Main:"+ops.getNum());
	}
}

结论:计算结果正确,但是输出中会出现问题

解决方案:在NumOps类的加减方法上添加关键字synchronized

public class NumOps {
    
    
	private int num;//具体操作数据
	//提供的方法,业务处理
	public synchronized void add() {
    
    
		num=num+1;
		System.out.println(Thread.currentThread().getName()+":add...."+this.num);
	}
	public synchronized void sub() {
    
    
		num=num-1;
		System.out.println(Thread.currentThread().getName()+":sub...."+this.num);
	}
	public int getNum() {
    
    
		return num;
	}	
}

理解synchronized的含义

1、synchronized就是给当前的NumOps类型的对象添加了一个互斥锁机制:锁只能有一个

2、当有一个线程正在某个同步方法中执行,则其它线程不能进入该对象的任何同步方法中,可以进入非同步方法

常见的4种Java线程锁:原子量AtomicInteger、信号量Semaphone、同步处理synchronized和重入锁ReentrantLock

jdk6之前是重量级锁,JDK6开始优化为锁的状态总共有四种,无锁状态(没有synchronized)、偏向锁、轻量级锁和重量级锁。锁状态的改变是根据竞争激烈程度进行的,在几乎无竞争的条件下,会使用偏向锁,在轻度竞争的条件下,会由偏向锁升级为轻量级锁, 在重度竞争的情况下,会升级到重量级锁。 随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级

使用synchronized加锁的字节码会出现monitorenter和monitorexit两个指令,可以理解为代码块执行前的加锁,和退出同步时的解锁

猜你喜欢

转载自blog.csdn.net/qq_43480434/article/details/114104183