【学习记录】线程学习(一)

本篇主要记录学习多线程的相关知识点

一、线程睡眠(sleep)

  • sleep指定当前线程阻塞的毫秒数;
  • sleep存在异常InterruptedException;
  • sleep时间达到后线程进入就绪状态;
  • sleep可以模拟网络延时,倒计时等;
  • 每个对象都有一个锁,sleep不会释放锁;

二、线程礼让(yield)

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功!看CPU心情
public class yieldTest {
	
	public static void main(String[] args) {
		MyYield myYield = new MyYield();
		new Thread(myYield,"a").start();
		new Thread(myYield,"b").start();
	}
}

class MyYield implements Runnable{

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+"线程开始执行!");
		Thread.yield();
		System.out.println(Thread.currentThread().getName()+"线程停止执行!");
		
	}
}

结果输出:



a线程开始执行!
b线程开始执行!
a线程停止执行!
b线程停止执行!


a线程开始执行!
a线程停止执行!
b线程开始执行!
b线程停止执行!

三、线程强制执行(join)

  • join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 相当于排队中的插队现象

public class joinTest implements Runnable{
	
	public static void main(String[] args) throws InterruptedException {
		//线程启动
		joinTest join = new joinTest();
		Thread thread = new Thread(join);
		thread.start();
		
		//主线程
		for (int i = 1; i <= 50; i++) {
			if (i==20) {
				thread.join();//线程插入
			}
			System.out.println("main"+i);
		}
	}

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println("线程VIP来了"+i);
		}
		
	}
}

四、线程优先级(priority)

  • 优先级范围:1~10
  • 设立方式:setPriority(int x)
  • 获取方式:getPriority(int x)
  • 优先级的设定建议在start()调度前
  • 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度

五、守护线程(daemon)

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 守护线程:后台记录操作日志、监控内存、垃圾回收等待

六、线程同步

1、解释:

由于同一进程的多个线程共享同一块存储空间,会产生访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized) ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,但也存在如下问题:

  • 一个线程持有锁会导致所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
2、同步方法:

由于我们一般通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,就是synchronized关键字,它包含两种方法 synchronized方法synchronized块

(1)synchronized方法

//同步方法:
public synchronized void method(int args){}

synchronized方法控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

(缺陷:若将一个大的方法申明为synchronized,将会影响效率)

(2)synchronized块

//同步块
synchronized(Obj){}		//Obj为同步监视器

  • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

同步监视器的执行过程:

  • 1、第一个线程访问,锁定同步监视器,执行其中代码
  • 2、第二个线程访问,发现同步监视器被锁定,无法访问
  • 3、第一个线程访问完毕,解锁同步监视器
  • 4、第二个线程访问,发现同步监视器没有锁,然后锁定并访问

七、死锁

多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方先释放资源,都停止执行的情形。某个同步块同时拥有“两个以上对象的锁”时,就可能会发生死锁问题。

产生死锁的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求和保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

八、锁(lock)

  • JDK5.0以后通过显示定义同步锁对象来实现同步,同步锁使用lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

//调用方式:
class A{
	private final ReentrantLock lock = new ReentrantLock();
	public void m(){
		lock.lock();
		try{
			//保证线程安全的代码
		}finally{
			//如果同步代码有异常,要将unlock()写进finally语句块
			lock.unlock();
		}
	}
}

synchronized和lock的对比:

  • Lock是显式锁(需要手动开启和关闭);synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少时间来调度线程,性能更好,并且具有更好的扩展性
  • 使用优先顺序:Lock > 同步代码块 > 同步方法

本文笔记主要来源:【狂神说Java】多线程详解

你可能感兴趣的:(java)