4 线程控制API

4.1 Lock:同synchronize,用于实现同步,保证多线程有序执行

ReentrantLock():创建可重入锁对象

lock():获取锁对象,锁对象属于当前线程,如果被其他线程占用,则进入阻塞状态

unlock():释放锁对象,如果在线程执行结束前未释放,那么锁对象将被TERMINATED线程一直占用,其他线程将永远无法获得锁,从而导致死锁。务必在finally块中执行,防止程序异常终止。

示例:保证静态共享的num,分别被5个加线程和减线程调用后,结果为0

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockThread extends Thread{
	private static Lock lock = new ReentrantLock();//establish static lock for all
	private static int num = 0;
	private String name;
	public LockThread(String name) {
		this.name = name;
	}
	@Override
	public void run(){
		lock.lock();//belong to current thread not the thread object
		lock.lock();//lock count +1, just test for fun
		try{
			if(name.equals("add")){
				for(int i=0;i<10;i++){
					num++;
				}
			}else if(name.equals("minus")){
				for(int i=0;i<10;i++){
					num--;
				}
			}
			System.out.println(name+":"+num);//final result must be 0
		}finally{
			this.myUnlock();//must in finally,you cant't be sure you program will be abnormal
			this.myUnlock();//two lock ,two unlock
		}
	}
	public void myUnlock(){
		lock.unlock();//if thread terminated,you cant't do this operation
	}
	public static void main(String[] args) {
		for(int i=0;i<10;i++){
			if(i%2==0){
				new LockThread("add").start();//add 5 times
			}else{
				new LockThread("minus").start();//minus 5 times
			}
		}
	}
}

执行结果:

add:10
add:20
add:30
minus:20
minus:10
minus:0
minus:-10
add:0
minus:-10
add:0

ReentrantReadWriteLock

readLock可以与readLock并发执行,不发生阻塞,和writeLock相互排斥

示例:同时模拟synchronized和ReentrantReadWriteLock实现同步,并对比执行效率

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class ReadWriteLockThread extends Thread{
	private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	private static ReadLock readLock = lock.readLock();
	private static WriteLock writeLock = lock.writeLock();
	private static int num;
	private static int num2;
	private String name;
	private static long endtime;
	private static long endtime2;
	public static int getNum() {
		readLock.lock();//加读锁
		try{
			return num;
		}finally{
			readLock.unlock();
		}
	}
	public static void addNum() {
		writeLock.lock();//加写锁
		try{
			num++;
		}finally{
			writeLock.unlock();
		}
	}
	public synchronized int getNum2() {
		return num2;
	}
	public synchronized static void addNum2() {
		num2++;
	}
	@Override
	public void run(){
		if(name.equals("readwrite")){
			for(int i=0;i<1000;i++){
				addNum();
				getNum();
			}
			endtime = System.currentTimeMillis();
		}else{
			for(int i=0;i<1000;i++){
				addNum2();
				getNum2();
			}
			endtime2 = System.currentTimeMillis();
		}
	}
	public ReadWriteLockThread(String name) {
		this.name = name;
	}
	
	public static void main(String[] args) {
		long starttime = System.currentTimeMillis();
		for(int i=0;i<10;i++){
			new ReadWriteLockThread("readwrite").start();
		}
		sleep(2000);
		System.out.println("num="+num);//检验是否达到同步效果
		System.out.println(endtime-starttime);//ReadWriteLock方式执行时间
		starttime = System.currentTimeMillis();
		for(int i=0;i<10;i++){
			new ReadWriteLockThread("sync").start();
		}
		sleep(2000);
		System.out.println("num2="+num2);//检验是否达到同步效果
		System.out.println(endtime2-starttime);//synchronized方式执行时间
	}
	public static void sleep(long time){
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

执行结果:

num=10000
75
num2=10000
9

注:当前示例下,ReadWriteLock效率没有synchronize高。

4.2 Condition:同wait和notify机制,用于实现按条件执行多线程

newCondition():返回一个与锁相关的条件对象

await():将线程放到条件的等待集中,此时释放lock占用,进入等待状态

signal():随机选择一个线程,解除等待状态(可能进入Runnable也可能Blocked),容易死锁

signalAll():解除条件等待集中所有线程的等待状态(可能进入Runnable也可能Blocked,因为还需要看是否能够获得lock)

注:以上三个方法,必须是lock锁的持有者才能调用

示例:保证静态共享的num,分别被5个加线程和减线程调用后,结果为0,并且调用过程中num不能小于0

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionThread extends Thread{
	private static Lock lock = new ReentrantLock();
	private static Condition condition = lock.newCondition();
	private static int num = 0;//保证num不小于0
	private String name;
	public ConditionThread(String name) {
		this.name = name;
	}
	@Override
	public void run(){
		lock.lock();
		try {
			if(name.equals("add")){
				for(int i=0;i<10;i++){
					num++;
				}
				condition.signalAll();//当num增加后,唤醒等待线程,唤醒的线程依然得等待lock
			}else if(name.equals("minus")){
				for(int i=0;i<10;i++){
					while(num==0){//当num为零,且操作为minus时,线程等待并放弃锁
						condition.await();
					}
					num--;
				}
			}
			System.out.println(name+":"+num);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public static void main(String[] args) {
		for(int i=0;i<10;i++){
			if(i%2!=0){
				new ConditionThread("add").start();
			}else{
				new ConditionThread("minus").start();//先start,但await放弃lock
			}
		}
	}
}

执行结果:

add:10
add:20
minus:10
add:20
minus:10
minus:0
add:10
minus:0
add:10
minus:0

4.3 synchronized、wait、notifyAll

synchronized:同步关键字,可修饰方法或者代码块。占用者:对象锁或类锁

注:static 和非static 分别属于类锁和对象锁,互不干扰,当类锁被占用,此时由对象去调用static方法也将被阻塞,同时注意,static方法是无法影响对象本身私有属性。

wait:释放对象锁,进入等待状态

notify:随机选择一个线程,解除等待状态(可能进入Runnable也可能Blocked),容易死锁

notifyAll:解除条件等待集中所有线程的等待状态(可能进入Runnable也可能Blocked,因为还需要看是否能够获得对象锁)

注:以上三个方法(属于Object),必须是对象锁的持有者才能调用

示例:完成主线程和自定义线程之间的来回跳转,以及跳转过程的线程状态变化

public class WaitingThread extends Thread{
	private static byte[] lock = new byte[0];//创建最简易的lock
	public WaitingThread(String name) {
		super(name);
	}
	@Override
	public void run(){
		try {
			synchronized (lock) {
				System.out.println(this.getState());//wait前,RUNNABLE状态
				System.out.println("WaitingThread give out the lock");
				lock.wait();
				System.out.println("WaitingThread get the lock");
				System.out.println(this.getState());//获得lock,重回RUNNABLE状态
				lock.notifyAll();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		WaitingThread w = new WaitingThread("WaitingThread");
		w.start();
		try {
			Thread.sleep(1000);//给WaitingThread执行wait的时间
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		synchronized (lock) {
			System.out.println("main thread get the lock!");
			System.out.println(w.getState());//唤醒前,WAITING状态
			lock.notifyAll();//唤醒WaitingThread
			System.out.println(w.getState());//唤醒后,BLOCKED状态
			try {
				System.out.println("main give out the lock");
				lock.wait();//让出锁给WaitingThread
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("main thread get the lock again!");
		}
	}
}

执行结果:

RUNNABLE
WaitingThread give out the lock
main thread get the lock!
WAITING
BLOCKED
main give out the lock
WaitingThread get the lock
RUNNABLE
main thread get the lock again!

4.4 volatile和atomic

volatile这个值的作用就是告诉JVM:对于这个成员变量不能保存它的副本,要直接与共享成员变量交互,也就保证在取值那一刻值是最新的。但是它不确保原子性(如果操作包含volatile修饰域本身)!

PS:多年之后发现这个理解有问题,volatile修饰变量依旧会在工作内存中操作,即依旧保持副本,只是通过控制多个读操作或写操作的原子性+有序性,来实现共享变量的可见性。

java.util.concurrent.atomic包中有许多用于原子操作的基础类型

示例:一并展示volatile和atomic在多线程下的执行情况

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicThread extends Thread{
	private static volatile int num =0;
	private static AtomicInteger atomicNum =new AtomicInteger(0);
	@Override
	public void run(){
		add();
		atomicAdd();
	}
	public void add(){
		for(int i =0;i<10000;i++){
			num=num+1;//执行过程中num值被其他线程修改
		}
		System.out.println("add: "+num);
	}
	public void atomicAdd(){
		for(int i =0;i<10000;i++){
			atomicNum.incrementAndGet();//等价i++,但AtomicInteger具备原子性
		}
		System.out.println("atomicAdd: "+atomicNum.get());
	}
	public static void main(String[] args) {
		for(int i=0;i<20;i++){
			new AtomicThread().start();
		}
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("num最终结果:"+num);
		System.out.println("atomicNum最终结果:"+atomicNum.get());
	}
}

执行结果:

num最终结果:155704
atomicNum最终结果:200000

4.5 join

 join() 方法主要是让调用该方法的thread完成run方法里面的任务后,再执行原线程join()方法后面的代码。

示例:mainthread线程最后一轮时,jointhread中途插入执行

public class JoinThread extends Thread{
	private Thread joinThread;
	public JoinThread(Thread t) {
		joinThread = t;
	}
	@Override
	public void run(){
		for(int i=0;i<3;i++){
			System.out.println(i);
			if(i==2){
				try {
					joinThread.join();//joinThread执行完成后再回来执行后面代码
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("--over--");
		}
	}
	public static void main(String[] args) {
		Thread joinThread = new Thread(){
			@Override
			public void run(){
				try {
					Thread.sleep(1000);//让mainThread先执行
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("join!!!!");
				System.out.println("over!!!!");
			}
		};
		JoinThread mainThread = new JoinThread(joinThread);
		joinThread.start();
		mainThread.start();
	}
}

执行结果:

0
--over--
1
--over--
2
join!!!!
over!!!!
--over--

4.6 interrupt(),interrupted()和isInterrupted()

interrupt():向线程发出中断请求,线程不会立即中断,只是将中断状态变为true。如果遇到sleep阻塞,那么抛出异常,并且中断状态重新变为false。

static interrupted():测试线程是否被中断,副作用:当前中断状态变为false。

isInterrupted():测试线程是否被中断,中断状态不改变

示例:展示中断方法的效果

public class InterruptThread extends Thread{
	
	@Override
	public void run(){
		while(!this.isInterrupted()){
			for(int i = 1;i<=5;i++){
				System.out.println(i);
				if(i==2){
					System.out.println("执行interrupt()");
					this.interrupt();//此时线程不会停下
				}
			}
			System.out.println("当前中断状态:"+this.isInterrupted());//中断状态不变
			System.out.println("当前中断状态:"+Thread.interrupted());//中断状态将改变成为false
			if(!this.isInterrupted()){//如果线程中断状态不为true
				this.interrupt();//再次中断程序
			}
			/*try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {//中断状态将改变成为false
				e.printStackTrace();
				this.interrupt();
			}*/
		}
	}
	public static void main(String[] args) {
		new InterruptThread().start();
	}
}

执行结果:

1
2
执行interrupt()
3
4
5
当前中断状态:true
当前中断状态:true

4.7 stop,suspend和resume

这三个方法被弃用了。为啥呢?

stop:终止所有未结束的方法,将导致对象被破坏,比如钱从一个账户到另一个,中途stop,那么钱去哪了?O(∩_∩)O~

suspend:用于挂起线程,需要其他线程调用resume来回复,容易导致死锁。比如,a线程挂起,并持有b线程需要的锁,而b线程要执行resume将a回复,可惜锁又被a占用着,那么程序死锁。

示例:自定义挂起,回复线程

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ResumeThread extends Thread{
	private Lock suspendLock = new ReentrantLock();
	private Condition suspendCondition = suspendLock.newCondition();
	private boolean suspend = false;//挂起标识
	private void mySuspend(){
		suspend = true;
	}
	private void myResume(){
		suspend = false;
		suspendLock.lock();//锁定
		try{
			suspendCondition.signalAll();//唤醒线程
		}finally{
			suspendLock.unlock();
		}
	}
	@Override
	public void run(){
		while(true){
			System.out.println("------ResumeThread------");
			if(suspend){
				suspendLock.lock();
				try{
					suspendCondition.await();//释放锁,进入等待状态
				}catch (Exception e) {
					e.printStackTrace();
				}finally{
					suspendLock.unlock();
				}
			}
			sleep(1);
		}
	}
	public static void main(String[] args) {
		ResumeThread resumeT = new ResumeThread();
		resumeT.start();
		sleep(10);
		System.out.println("-------suspend------");
		resumeT.mySuspend();//挂起
		System.out.println("-------resume------");
		resumeT.myResume();//恢复
		sleep(10);
		System.out.println("-------suspend------");
		resumeT.mySuspend();//再次挂起
		System.exit(0);//结束程序
	}
	public static void sleep(long time){
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

执行结果:

------ResumeThread------
------ResumeThread------
------ResumeThread------
...
-------suspend------
-------resume------
------ResumeThread------
------ResumeThread------
------ResumeThread------
...
-------suspend------
------ResumeThread------

4.8 yield

Thread.yield( )方法,译为线程让步,就是说使用它的线程会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意并不是单纯的让给其他线程。它能让当前线程由“运行状态”进入到“就绪状态”,此时当前线程和其他线程一同竞争CPU的使用权。

 

 


爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐