多线程wait、notify、sleep、join、yield、synchronized关键字 深入了解线程(Thread)

本文深入探讨线程同步机制,包括synchronized关键字与ReentrantLock的使用,解析线程的生命周期及wait、notify、sleep、join、yield等操作的作用。通过实例演示,帮助读者理解线程同步的重要性和实现方式。

线程

工作快一年了,对线程老是躲来躲去只要是线程出现的ANR就会避让,看来这种态度还是不行,如果在这样下去只会是一个普通,打工仔,花店时间写DOME吧

1 . Thread是个线程,而且有自己的生命周期
2 . 对于线程常用的操作有:wait(等待)、notify(唤醒)、notifyAll、sleep(睡眠)、join(阻塞)、yield(礼让)
3 . wait、notify、notifyAll都必须在synchronized中执行,否则会抛出异常
4 . synchronized关键字和ReentrantLock锁都是辅助线程同步使用的
5 . 初学者常犯的误区:一个对象只有一个锁(正确的)
 

线程同步synchronized(关键字)

比如抢火车票,在以前抢火车票是存在误差的,火车抢票是一年中沸沸扬扬的事情,这也就好比我们的多线程抢夺资源是一个道理,下面写一个例子:

public class ThreadT {
	int number=10;
	public void main(){
		for (int i = 0; i < 10; i++) {
			new Thread(){
				public void run() {
					addProdect();
				};
			}.start();
		}
	}
	public   void addProdect(){
		number--;
		System.out.println("商品数量"+(number));
	}
}

这里我们开启了10个线程去抢火车票,不过火车票只有10张,需要一张一张来,比如先从10开始抢到最后一张为止。

输出结果:

商品数量6
商品数量6
商品数量6
商品数量6
商品数量5
商品数量2
商品数量2
商品数量2
商品数量1
商品数量0

很明显我们这个输出结果是错误的,可以看出来数据很乱,想要9,8,7,6,5,4,3,2,1,0,比如4个6就是因为有4个线程挤在一起4个线程一起执行了 number-- 接着在执行System.out.println("商品数量 = "+number);导致了4个6输出结果。

那么如果让他们自己排队就需要synchronized关键字。

一,方法上加上synchronized关键字

public class ThreadT {
	int number=10;
	public void main(){
		for (int i = 0; i < 10; i++) {
			new Thread(){
				public void run() {
					addProdect();
				};
			}.start();
		}
	}
	//在这里添加synchronized关键字 进行线程同步
	public synchronized  void addProdect(){
		number--;
		System.out.println("商品数量"+(number));
	}
}

这里就是表示方法同步,让线程一个一个来抢夺资源,下面是执行结果

商品数量9
商品数量8
商品数量7
商品数量6
商品数量5
商品数量4
商品数量3
商品数量2
商品数量1
商品数量0

二,方法内部添加synchroized关键字

public class ThreadT {
	int number=10;
	public void main(){
		for (int i = 0; i < 10; i++) {
			new Thread(){
				public void run() {
					addProdect();
				};
			}.start();
		}
	}
	//在这里添加synchronized关键字 进行线程同步
	Object obj = new Object();
	public  void addProdect(){
		synchronized (obj) {
			number--;
			System.out.println("商品数量"+(number));
		}
		
	}
}

其实synchronized可以理解为一个锁,而锁就是为了锁东西,所以synchronized又分为类锁和对象锁 ,即可以锁类也可锁对象,他们两者的作用就是为了保证线程同步。就好比上面的synchronized(obj){},就是对象锁,将Object对象锁起来。

类锁与对象锁的概念

对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是在多线程访问时,两个锁实际是很大区别的,对象锁是用于对象实例方法,或者对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知到,类的对象实例可以有很多给,但是每个类只有一个class对象,所以  结论是 : 1,不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。而且类锁和对象锁互相不干扰

一,对象锁

对象锁创建下面两个方法

public class ThreadLock {
	//同步方法,对象锁
	public synchronized void methodObject(){
		
	}
	//同步快,对象锁
	public void syncThis(){
		synchronized (this) {
			
		}
	}
}

二,类锁

类锁创建下面两个方法

public class ThreadLock {
	//同步class对象,类锁
	public  void classClockObject(){
		synchronized (ThreadLock.class) {
			
		}
	}
	//同步静态方,法类锁
	public static synchronized void syncThis(){
	
	}
}

三,写个锁的例子

根据类锁和对象锁的概念,我们来通过例子验证一下其正确性,这里演示两个对象锁和一个类锁,我们创建一个类

public class ThreadLock {
	int number = 10;
	//同步方法,对象锁
	public synchronized void syncMethodLock(){
		for (int i = 0; i < 10; i++) {
			number--;
			 System.out.println(Thread.currentThread().getName() + "剩余的数:" + number);
		}
	}
	//同步块,对象锁
	public void syncObjectLock(){
		synchronized (this) {
			for (int i = 0; i < 10; i++) {
				number--;
				 System.out.println(Thread.currentThread().getName() + "剩余的数:" + number);
			}
		}
	}
	//同步class对象,类锁
	public void syncClassLock(){
		synchronized (ThreadLock.class) {
			for (int i = 0; i < 50; i++) {
				number--;
				 System.out.println(Thread.currentThread().getName() + "剩余的数:" + number);
			}
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
//		ThreadT t = new ThreadT();
//		t.main();
		final ThreadLock lock = new ThreadLock();
	    new Thread(){
	    	public void run() {
	    		lock.syncMethodLock();
	    	};
	    }.start();
	    new Thread(){
	    	public void run() {
	    		lock.syncObjectLock();
	    	};
	    }.start();
	}
	
}

  由于使用的是同一个对象的对象锁,所以执行出来的结果是同步的(即先运行线程一,等线程一运行完后运行线程二,ticket有序的减少)

输出结果

Thread-0剩余的数:9
Thread-0剩余的数:8
Thread-0剩余的数:7
Thread-0剩余的数:6
Thread-0剩余的数:5
Thread-0剩余的数:4
Thread-0剩余的数:3
Thread-0剩余的数:2
Thread-0剩余的数:1
Thread-0剩余的数:0
Thread-1剩余的数:-1
Thread-1剩余的数:-2
Thread-1剩余的数:-3
Thread-1剩余的数:-4
Thread-1剩余的数:-5
Thread-1剩余的数:-6
Thread-1剩余的数:-7
Thread-1剩余的数:-8
Thread-1剩余的数:-9
Thread-1剩余的数:-10

由于对象锁和类锁互不干扰,所以也是线程不安全的

如果不加 同步方法,对象锁  和 同步块,对象锁 会导致输出异常 

异常结果:

Thread-0剩余的数:9
Thread-0剩余的数:8
Thread-0剩余的数:7
Thread-0剩余的数:6
Thread-0剩余的数:4
Thread-1剩余的数:4
Thread-1剩余的数:2
Thread-1剩余的数:1
Thread-1剩余的数:0
Thread-1剩余的数:-1
Thread-1剩余的数:-2
Thread-1剩余的数:-3
Thread-1剩余的数:-4
Thread-1剩余的数:-5
Thread-1剩余的数:-6
Thread-0剩余的数:3
Thread-0剩余的数:-7
Thread-0剩余的数:-8
Thread-0剩余的数:-9
Thread-0剩余的数:-10

2、不同对象,使用两个线程调用同个对象锁

public class TestMain {
	public static void main(String[] args) {

		final ThreadLock lock1 = new ThreadLock();
		final ThreadLock lock2 = new ThreadLock();
		//线程一
	    new Thread(){
	    	public void run() {
	    		lock1.syncMethodLock();
	    	};
	    }.start();
	    //线程二
	    new Thread(){
	    	public void run() {
	    		lock2.syncMethodLock();
	    	};
	    }.start();
	}
	
}

输出数据

Thread-0剩余的数:9
Thread-0剩余的数:8
Thread-0剩余的数:7
Thread-0剩余的数:6
Thread-0剩余的数:5
Thread-0剩余的数:4
Thread-0剩余的数:3
Thread-0剩余的数:2
Thread-0剩余的数:1
Thread-1剩余的数:9
Thread-0剩余的数:0
Thread-1剩余的数:8
Thread-1剩余的数:7
Thread-1剩余的数:6
Thread-1剩余的数:5
Thread-1剩余的数:4
Thread-1剩余的数:3
Thread-1剩余的数:2
Thread-1剩余的数:1
Thread-1剩余的数:0

由于是不同对象,所以执行的对象锁都不是不同的,其结果是两个线程互相抢占资源的运行,即number偶尔会无序的减少

温习结论:1、不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。2、而且类锁和对象锁互相不干扰。

 

线程同步ReentrantLock锁

Java6.0增加了一种新的机制:ReentrantLock,下面看ReentrantLock的使用

import java.util.concurrent.locks.Lock;
public class ReentantLock {
	Lock lock;
	public void reentantLocks(){
	 lock = (Lock) new ReentantLock();
	 doStart();
	}
	public void doStart(){
		lock.lock();
		try{
			//同步代码块
		}finally{
			lock.unlock();
		}
		
	}
}

      使用ReentrantLock很好理解,就好比我们现实的锁头是一样道理的。使用ReentrantLock的一般组合是lock与unlock成对出现的,需要注意的是,千万不要忘记调用unlock来释放锁,否则可能会引发死锁等问题。如果忘记了在finally块中释放锁,可能会在程序中留下一个定时炸弹,随时都会炸了,而是用synchronized,JVM将确保锁会获得自动释放,这也是为什么Lock没有完全替代掉synchronized的原因
 

线程生命周期

线程也有属于自己的生命周期 下面画图来解释一下

线程的等待唤醒机制 wait()等待、notify()唤醒、notifyAll()

一开始我们也提到了wait、notify、notifyAll都必须在synchronized中执行,否则会抛出异常。所以下面以一个简单的例子来介绍线程的等待唤醒机制

//线程等待and唤醒
public class ThreadWaitNotify {
	private static Object objLock = new Object();
	public void main(){
		System.out.println("主线程运行");
		Thread threads = new Threads();
		threads.start();
		synchronized (objLock) {
			long start = System.currentTimeMillis();
			System.out.println("主线程等待......");
			try {
				objLock.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			 System.out.println("主线程 --> 等待的时间:" + (System.currentTimeMillis() - start));
		}
	}
	class Threads extends Thread{
		@Override
		public void run() {
			synchronized (objLock) {
				try {
					//子线程等待了2秒钟后唤醒objLock 锁
					Thread.sleep(2000);
					objLock.notifyAll();//唤醒线程
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					//e.printStackTrace();
				}
			}
			
		}
	}
}

输出结果

主线程运行
主线程等待......
主线程 --> 等待的时间:2001

    可以看到,我们使用的是同一个对象的锁,和同一个对象执行的wait()和notify()才会保证了我们的线程同步。当主线程执行到wait()方法时,代表主线程等待,让出使用权让子线程执行,这个时候主线程等待这一事件会被加进到【等待唤醒的队列】中。然后子线程则是两秒钟后执行notify()方法唤醒等待【唤醒队列中】的第一个线程,这里指的是主线程。而notifyAll()方法则是唤醒整个【唤醒队列中】的所有线程,这里就不多加演示了
 

写一道练习题目  :子子线程循环5次,接着主线程循环5次,接着又回到子线程循环5次,接着再回到主线程又循环19次,保证数据正确


public class Practice {
	Object objLock = new Object();
	public void main(){
		//子线程循环5次,接着主线程循环5次,接着又回到子线程循环5次,接着再回到主线程又循环19次,保证数据正确
		//子线程
		new Thread() {
		    @Override
		    public void run() {
		        for (int i = 0; i < 5; i++) {
		        	synchronized (objLock) {
			            for (int j = 0; j < 5; j++) {
			                System.out.println("子循环循环第" + (j + 1) + "次");
			            }
			            System.out.println("--> 子线程循环了" + (i + 1) + "次");
			            objLock.notify();
			            try {
							objLock.wait(); //等待去执行主线程执行数据
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
		              
		        	}
		        }     	
		    }
		}.start();
		//主线程
		for (int i = 0; i < 5; i++) {
			synchronized (objLock) {
				try {
					objLock.wait(); //先锁住,等待子线程唤醒
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			    for (int j = 0; j < 10; j++) {
			        System.out.println("主循环循环第" + (j + 1) + "次");
			    }
			    objLock.notify();
			    System.out.println("--> 主线程循环了" + (i + 1) + "次");
			 
			}
			
		}
		
	}
}

    不管是主线程先运行还是子线程运行,两个线程只能同时进入synchronized (lock)一个锁中。由于是子线程先运行:1、当主线程先进入synchronized (lock)锁时,它就必须是等待,而子线程开始运行输出,输出后就唤醒主线程。2、当子线程先运行的话,那就直接输出,然后等待主线程的运行输出

线程的sleep()、join()、yield()

一、sleep()

sleep()作用是让线程休息指定的时间,时间一到就继续运行,它的使用很简单

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

看图

二、join()

join()【阻塞】作用是让指定的线程先执行完再执行其他线程,而且会阻塞主线程,它的使用也很简单


public class join {
    public void main() throws InterruptedException{
    	Thread thread1 = new  MyThread("线程一");
    	Thread thread2 = new  MyThread("线程二");
    	thread1.start();
    	thread1.join();
    	System.out.println("主线程等待");
    	thread2.start();
    	thread2.join();
    }
    class MyThread extends Thread{
        public MyThread(String name){
        	super(name);
        }
    	@Override
    	public void run() {
    		super.run();
    		try {
    			System.out.println(getName() + " -> 在运行....");
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    }
}

输出结果 : 

线程一 -> 在运行....
主线程等待
线程二 -> 在运行....

看图:

 

三、yield()

yield()的作用是指定线程先礼让一下别的线程的先执行,就好比公交车只有一个座位,谁礼让了谁就坐上去。特别注意的是:yield()会礼让给相同优先级的或者是优先级更高的线程执行,不过yield()这个方法只是把线程的执行状态打回准备就绪状态,所以执行了该方法后,有可能马上又开始运行,有可能等待很长时间
 


public class join {
    public void main() throws InterruptedException{
    	Thread thread1 = new  MyThread("线程一");
    	Thread thread2 = new  MyThread("线程二");
    	thread1.start();
    	thread2.start();
    }
    class MyThread extends Thread{
        public MyThread(String name){
        	super(name);
        }
    	@Override
    	public synchronized void run() {
    		super.run();
    		for (int i = 0; i < 10; i++) {
                System.out.println(getName() + "在运行,i的值为:" + i + " 优先级为:" + getPriority());
                if (i == 2) {
                    System.out.println(getName() + "礼让");
                    Thread.yield();
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    	}
    }
}

运行结果

线程一在运行,i的值为:0 优先级为:5
线程一在运行,i的值为:1 优先级为:5
线程二在运行,i的值为:0 优先级为:5
线程二在运行,i的值为:1 优先级为:5
线程二在运行,i的值为:2 优先级为:5
线程一在运行,i的值为:2 优先级为:5
线程一礼让
线程二礼让
线程一在运行,i的值为:3 优先级为:5
线程一在运行,i的值为:4 优先级为:5
线程一在运行,i的值为:5 优先级为:5
线程一在运行,i的值为:6 优先级为:5
线程一在运行,i的值为:7 优先级为:5
线程一在运行,i的值为:8 优先级为:5
线程一在运行,i的值为:9 优先级为:5
线程二在运行,i的值为:3 优先级为:5
线程二在运行,i的值为:4 优先级为:5
线程二在运行,i的值为:5 优先级为:5
线程二在运行,i的值为:6 优先级为:5
线程二在运行,i的值为:7 优先级为:5
线程二在运行,i的值为:8 优先级为:5
线程二在运行,i的值为:9 优先级为:5

结束语:

这篇文章写了差不多一天了,简介其他文章写出的,加上demo,可能过几天没用又会忘记,但是写了文章加图一看应该可以大致回忆过来。最近出现一个wait异常at java.lang.Object.wait(Native method)才来了解线程这些特效,内容有点多当前我是对线程收悉一点了,不知道各位看官怎么样了。如有错误位置其提出,做修改谢谢。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达帮主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值