首先要从java.lang.object的类的三个方法学习
void notify():唤醒此对象监视器上等待的单个线程
void notifyAll(): 唤醒此监视器上等待的所有线程。
void wait():导致当前的线程阻塞等,线程要立马放弃同步代码块被同步对象的锁(目前感觉不合理,可能错了),直到其他线程调用此对象的notify() 或者 notifyAll(). 并且wait的调用对象也得是被锁住的那个对象。(这个是后来发现的规则)
另外 wait还有两个很重要的重载方法:
void wait(long timeout) :导致当前的线程等待,直到其他线程调用此线程此对象的notify()方法或者notifyAll()方法,或者超过指定的时间量。
void wait(long timeout, int nanos): 导致当前的线程等待直到其他线程调用此对象的notify() 或者 notifyAll() 方法,或者超过某个实际时间量,或者其他线程中断当前线程。
线程不能在平常的情况下调用对象上等待或者通知的方法,除非这个线程持有该对象的锁。所以 wait , notify, notifyAll 只能在同步环境中用!
wait, notify, notifyall 都是object 的实例方法。正如每个对象的锁一样,每个对象也有一个线程列表,他们等待来自该信号通知。线程通过执行对象上的wait方法获得这个等待列表,从那时起,他就不再执行任何其他指令,直到调用对象的notify方法为止。如果多个线程在同一对象上等待,则只选择一个线程继续执行。如果没有线程等待,则不采取任何特殊操作。调用它的对象一定要一致。
注意哈,wait 和 notify 搭配使用的话,一定要搞清锁住的对象。
你可以模拟出以下场景,有几个线程给你要数据,但是你这个数据很重要,坚决不允许并发篡改的问题出现,那么你:
改数据可以!!一个一个来!! 于是乎在重点的地方乖乖上了把锁。
然后线程们一个接着一个访问!
但是忽然你发现,数据改着改着,到达了某种临界状态,不能再改啦!除非将数据调整到临界点以下。
那么你只能这样做!
线程1 过来要数据,,你大声的告诉他,数据没了,你等着!我做下处理。处理完你可以从中断处继续执行。
但是你是因为自己的数据不符合他的要求而给他说这些情况的,不代表你当前的数据不符合所有线程要求的条件,,万一在逻辑外等待的某线程,人家要求的数据是你现在正好可以提供的呢??所以你还得想个办法,让别的线程能进来,能给数据就给,不能给数据,就告诉后面来的线程让他们等着!
你忽然想到,线程1还拿着锁呢,这要是歇了还不把锁给你,别的线程永远进不来了。不靠谱。于是你又对他说了句,兄嘚,把你的锁给我。好让后面排队的人进来。
于是线程1把锁归还了,,后面在等待的线程2正好轮到了,要执行代码朝你要数据。但是你发现你目前的数据不足以提供给他。于是你还得
将以上的套路重复一遍。 兄嘚,没数据呀,你先等会,好了知会你一声,把锁给我,下一位请进!
于是线程2也歇着了。线程3进来了,但是它要的数据,恰好是你仅仅能够提供得了的。于是你把仅存的一点数据给了它,没有让他等,让他正常的走完你的整个同步方法。
例子:
package Thread;
public class ThreadWaitDemo {
public static void main(String[] args) {
Object s = ""; //为了测试执行wait对象的时候,导致当前线程等待之外,,放弃的那把锁到底是谁的锁,
MyThread thread = new MyThread(s);
synchronized (thread) { //锁住了thread对象的锁
try {
System.out.println("开始等待计算");
thread.start();
// s.wait();
thread.wait();
System.out.println("等待结束, total = " + thread.total);
} catch (Exception e) {
}
}
}
}
class MyThread extends Thread {
Object string = null;
MyThread(Object s){
string = s;
}
public int total = 0;
public void run() {
synchronized (this) {
for (int i = 0; i < 101; i++) {
total += i;
System.out.println(i+"");
}
notify();
// string.notify();
}
}
}
哎,,,写代码的时候,,感觉到坑了!!果然实践很重要,会带出很多思维。什么坑呢??我写的时候,搞不清main 方法里的synchronized 括号里要的那把锁,,和 这一块的同步代码里 thread.wait(),,,为什么是thread调用wait??反正就是一个令当前线程放弃锁的操作,为什么偏偏调用thread的?和 synchronized括号里面的参数难道有什么规矩必须保持一致?还有好多其他的问题,主要集中于 notify 和 wait 之间调用,是不是有一个潜规则,不写就报错。。事实上我就这点试了一下,,的确报错了。 比如,我把main方法里面的thread.wait()方法注掉改为了 s.wait(), 同时把 MyThread 的run方法里面的notify改成了同一个对象的notify(),要凑凑一对,想着反正wait就是暂停当前线程并还线程所在锁的,,即使我用s调出wait(),那也得放弃锁。。运行了一下,可以确定 s.wait() 方法执行了之后,主线程的确放弃了synchronized所持有的那把 thread 的锁,并且执行了 MyThread的run方法,但是但是,,string.notify()执行之后,,就仿佛没了消息!!最后一句“等待结束,,”死活打印不出来。所以推断出 wait 和 notify 肯定有一套规则!但是我这里看不到源码我的天。。咋整。
好吧,native的。。。
使用wait方法和notify方法用于线程间通信的正确姿势 这篇文章,可以说是条条命中!
嗷嗷我明白了,经过查阅,,wait, notify 这类方法, 所在的同步代码块,锁了谁,就应该用谁调!!!当在对象上调用wait()方法时,执行该代码的线程立即放弃这个线程持有的锁,然而调用notify时,并不意味着这时线程会放弃其锁。如果线程仍然在完成同步代码,则线程在移除之前都不会放弃锁。因此,notify并不意味着这时的锁变得可用。
例子:
/**
* 要求:用wait 和 notify 确保两个线程一定先递加加后递减! 拒绝使用jion解决
* @author forev
*
*/
public class ThreadWaitDemo {
public static boolean isAddOver = false; //用于标志递加线程是否执行完毕。
public static void main(String[] args) {
NumberGame game = new NumberGame();
game.setAddTimes(100);
game.setSubtractTimes(100);
SubtractThread sThread = new SubtractThread(game);
AddThread aThread = new AddThread(game);
sThread.start();
aThread.start();
}
}
//递减线程
class SubtractThread extends Thread {
private NumberGame game= null;
public SubtractThread(NumberGame g) {
this.game = g;
}
public void run() {
synchronized (game) {
try {
//这里需要有个大大的细节要注意!!!一定要在while循环里面判断,不要用if!不要用if!不要用if!重要的事情说。。
while (!ThreadWaitDemo.isAddOver){
game.wait();
}
game.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//递加线程
class AddThread extends Thread {
private NumberGame game= null;
public AddThread(NumberGame g) {
this.game = g;
}
public void run() {
synchronized (game) {
game.add();
game.notify();
ThreadWaitDemo.isAddOver = true;
}
}
}
class NumberGame {
private int total = 0;
private int addTimes = 0;
private int subtractTimes = 0;
public void setAddTimes(int addTime) {
this.addTimes = addTime;
}
public void setSubtractTimes(int st) {
this.subtractTimes = st;
}
public int getTotal(){
return total;
}
// 递加 线程安全
public void add(){
synchronized (this) {
for (int i = 0; i <= addTimes; i++) {
total += i;
System.out.println("add......" + total);
}
}
}
//递减 线程安全
public void subtract() {
synchronized (this) {
for (int i = subtractTimes; i >= 0; i--) {
total -= i;
System.out.println("subtract......" + total);
}
}
}
}
多数情况下,最好通知某个对象的所有线程,如果这样做,可以在对象上使用 notifyAll()让所有在此对象上等待的线程冲出等待区,回到可运行状态。
Java线程调度是java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。这里要明确一点,不管程序员如何编写调度,只能最大限度的影响执行的次序,而不能做到精准控制。
线程休眠是使线程让出cpu的最简单方法之一,线程休眠的时候,会将CPU资源交给其他的线程,以便能轮换执行,当休眠一定时间之后,线程就会苏醒,进入准备状态等待。例子前面好像有。
线程合并的含义是将几个线程合并为一个单线程执行,应用场景是当一个线程必须等另一个线程执行完毕才能执行时可以使用join方法。前面有例子。
守护线程基本上与普通线程没有啥区别调用线程的setDaemon(true),则可以将其设置为守护线程,守护线程的使用情况比较少,但并非无用,举例来说,JVM的垃圾回收,内存管理等线程都是守护线程。还有就是做数据库应用的时候,使用的是数据库连接池,连接池本身也包含很多后台线程,监控连接的个数,超时时间,状态等。
setDaemon方法详细说明:
public final void setDaemon(boolean on)将线程标记为守护线程,当正在运行的线程都是守护线程的时候,JVM就会退出了。
public class DaemonThreadDemo {
public static void main(String[] args) {
DaemonThread thread = new DaemonThread();
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread() + " i=" + i);
try {
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
class DaemonThread extends Thread {
public void run() {
for (int i = 0; i < 1000; i ++) {
System.out.println(Thread.currentThread() + " i=" + i);
try {
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
运行结果:
注意看,,当主线程执行完之后, 守护线程没执行几句就结束了。因为剩下的只有守护线程了,JVM会退出的。
对于多线程的程序来说,不管任何语言,生产者和消费者模型都是最经典的。就像学习每一门语言一样,hello world, 都是最经典的例子。
实际上,准确的来说应该是 生产者-消费者-仓储, 离开了仓储,生产者消费者模型就显得没有说服力了。
例子:
// 模拟生产者消费者模式
public class ProduceConsumeDemo {
public static void main(String[] args) {
ZhiZaoChang zhi = new ZhiZaoChang(80);
ProduceThread produceThread = new ProduceThread(zhi);
produceThread.setDaemon(true); //设置为守护线程,当消费者买完了东西,就不生产了
ConsumeThread consume1 = new ConsumeThread(zhi, 50);
ConsumeThread consume2 = new ConsumeThread(zhi, 80);
ConsumeThread consume3 = new ConsumeThread(zhi, 60);
ConsumeThread consume4 = new ConsumeThread(zhi, 10);
ConsumeThread consume5 = new ConsumeThread(zhi, 20);
ConsumeThread consume6 = new ConsumeThread(zhi, 90);
ConsumeThread consume7 = new ConsumeThread(zhi, 50);
ConsumeThread consume8 = new ConsumeThread(zhi, 90);
ConsumeThread consume9 = new ConsumeThread(zhi, 40);
ConsumeThread consume10 = new ConsumeThread(zhi, 35);
ConsumeThread consume11 = new ConsumeThread(zhi, 99);
ConsumeThread consume12 = new ConsumeThread(zhi, 79);
ConsumeThread consume13 = new ConsumeThread(zhi, 45);
produceThread.start();
consume1.start();
consume2.start();
consume3.start();
consume4.start();
consume5.start();
consume6.start();
consume7.start();
consume8.start();
consume9.start();
consume10.start();
consume11.start();
consume12.start();
consume13.start();
}
}
class ZhiZaoChang{
public static final int MAX = 100;
private int mProductNumber = 0;
public ZhiZaoChang(int nowNumber) {
this.mProductNumber = nowNumber;
}
public void produce() {
while (true) { //加一个死循环,以便于可以随时等待时间片的分配
synchronized (this) {
while (mProductNumber < MAX) {
mProductNumber = MAX; //补满货
System.out.println(Thread.currentThread() + " 生产完了,补满货了");
notifyAll();
}
}
try {
Thread.currentThread().sleep(1); //强制睡眠,以便于切换到其他的线程
} catch (Exception e) {
// TODO: handle exception
}
}
}
public void consumer (int needNumber) {
synchronized (this) {
try {
while (mProductNumber < needNumber) {
System.out.println(Thread.currentThread() + " 货不够了, 需要消费" + needNumber
+ "......余货还有:" + mProductNumber);
//缺货了,赶紧腾出来锁,让其他的线程用,如果此时恰好有一个其他的消费者获得了这把锁,但是他也发现缺货了,
//那么它也会走同样的逻辑,把锁甩出,让其他的线程用,如果有一个线程也是消费者线程,但是它购买的量比较少,余货
//正好够,他就会再消费一波。然后再甩出锁,来回几次剩下的只有都甩出过锁只等notify的消费者线程,和
//那个生产者线程了,反正总会轮到它!它进行补货,补完就发个通知告诉大家,有货了,继续排队买哈。
wait();
}
mProductNumber -= needNumber;
System.out.println(Thread.currentThread() + " 消费完了, 消费了" + needNumber
+ "......余货还有:" + mProductNumber);
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
class ConsumeThread extends Thread{
private ZhiZaoChang zhiZaoChang = null;
private int needNumber = 0;
public ConsumeThread(ZhiZaoChang zzc, int number) {
this.zhiZaoChang = zzc;
this.needNumber = number;
}
public void run() {
zhiZaoChang.consumer(needNumber);
}
}
class ProduceThread extends Thread {
private ZhiZaoChang zhiZaoChang = null;
public ProduceThread(ZhiZaoChang zhi) {
this.zhiZaoChang = zhi;
}
public void run() {
zhiZaoChang.produce();
}
}
需要注意的是:notifyAll 起到。的作用就是一个通知作用,不释放锁!得等同步块执行完后才会释放锁,或者粗暴点儿,直接wait,但是上面代码生产者可别用wait哈!所以notifyAll也就是告诉所有线程,你们wait那么久终于有结果了,都醒醒来等锁吧!!
Java中Volatile关键字详解
这个关键字我认为是有些复杂的。那么耐心的一点一点的看,突破,理解
首先,要知道的是java内存模型中的有序性,原子性,和可见性!
指令重排序:在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行
当对非volatile修饰的变量进行读写的时候,每个线程会从内存拷贝变量到CPU缓存中,如果你的计算机具有多个CPU的话。每个线程可能会在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache中。
在访问volatile变量的时候不会执行加锁操作,因此也就不会执行线程阻塞,因此volatile变量是一种比sychronized变量更加轻量级的同步机制。
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
当一个变量被volatile关键字所修饰的时候,具有以下特点:
Volatile性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
注意 volatile关键字解决的仅仅是,,
多线程的问题逃不过 有序性,可见性,和原子性这几个问题。思考的时候请多从这三点思考。