`

"Java 多线程"简单总结

    博客分类:
  • java
阅读更多
程序本来是静止的,静止在磁盘上的,当它运行之后,当它运行之后,它就处在一个进程当中,而一个进程里面可以包含着多个线程,多个线程可以同时的运行,所谓多线程就是说多个这样的线程可以同时的去运行。就比如说你去一个建筑工地干活,有一对砖头你想要把他们搬走,如果你一个人去,那你就是一个进程,进程里面就你一个线程,你就开始一次次搬砖,一次可能搬几块砖,来回这样搬,直到你把这些砖都搬完了;也可能是你找了其他的两个人来搬这个转,你给他们点钱让他们帮你把这个转搬完,你就不用干了,然后这两个人就开始搬砖,由于他们两个人的体力不同搬砖的节奏可能也不同,可能出现两个人同向搬砖,也可能出现两个人迎面走来的情况等等,如果搬砖的人不止两个人的话,出现的情况可能就更复杂了。每个搬砖的工人都可以比作一个线程,搬砖这件事就是进程。


例如我打开了一个word文档,那么word这个程序就相当于是一个进程,我可以在word文档里面修改内容,也可以打印这些文档等等,这些操作都是这个进程里面的一个线程。这些线程共享同一块内存空间和一组系统资源,有可能相互影响。



注:java线程模型和多线程的优势等了解下即可


下面是两个简单的线程示例:
package com.shengshiyuan.thread;

public class ThreadTest {

	/**
	 * 每次执行的结果都不相同
	 * 线程一旦启动之后它就不受你控制了,两个线程启动之后,准备好资源,分别调用各自的run方法。
	 * 两个线程启动之后,现在的电脑CPU基本上都是双核的,每一个核心都可以运行一个线程,所以两个线程可能同时在执行,一个线程占用着一个核。每个核最多同时被一个线程占用,所以双核的电脑最多同时可以有两个线程同时在CPU上执行(也可能是一个线程,一个核被占用了,另一个核空着)。
	 * 所以输出结果是不确定的(因为两个线程是否都在运行以及如果是一个的话具体运行的是哪个这些都不确定)。
	 * 如果是单核的,那么同一时刻只能有一个线程在执行,只能有一个线程占用着当前CPU的那一个核,这个时候多线程执行的结果也是不确定的(某一时刻具体哪个线程占用着CPU就不知道了)。
	 * 方法: main <br>
	 * 描述: TODO <br>
	 * 作者: 
	 * 时间: Nov 19, 2013 4:57:10 PM
	 * @param args
	 */
	public static void main(String[] args) {
		Thread1 t1 = new Thread1();
		Thread2 t2 = new Thread2();
		t1.start();
		t2.start();
	}
}

class Thread1 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("hello world :" + i);
		}
	}
}

class Thread2 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("welcome :" + i);
		}
	}
}

package com.shengshiyuan.thread;

public class ThreadTest2 {

	public static void main(String[] args) {

		Thread t1 = new Thread(new MyThread());
		t1.start();

		Thread t2 = new Thread(new MyThread2());
		t2.start();
	}
}

class MyThread implements Runnable {
	public void run() {

		for (int i = 0; i < 100; i++) {
			System.out.println("hello :" + i);
		}
	}
}

class MyThread2 implements Runnable {
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("welcome :" + i);
		}
	}
}


下面是线程的一些详细的笔记:
1. Java 中如果我们自己没有产生线程,那么系统就会给我们产生一个线程(主线程,main方法就在主线程上运行),我们的程序都是由线程来执行的。
2. 进程:执行中的程序(程序是静态的概念,进程是动态的概念)【程序如果不运行的话它就老老实实的呆在硬盘上,啥也不干,一旦它运行起来之后就会产生一个进程。】。
3. 线程的实现有两种方式,第一种方式是继承Thread类,然后重写run方法;第二种是实现Runnable接口,然后实现其run方法。
4. 将我们希望线程执行的代码放到run方法中,然后通过start方法来启动线程,start方法首先为线程的执行准备好系统资源,然后再去调用run方法。当某个类继承了Thread类之后,该类就叫做一个线程类。
5. 一个进程至少要包含一个线程。
6. 对于单核CPU来说,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行)。
7.对于双核或双核以上的CPU来说,可以真正做到微观并行。
8.【线程两种实现方式的区别和联系(通过查看Runable源代码总结出来)】 
1) Thread类也实现了Runnable接口,因此实现了Runnable接口中的run方法;
2) 当生成一个线程对象时,如果没有为其设定名字,那么线程对象的名字将使用如下形式:Thread-number,该number将是自动增加的,并被所有的Thread对象所共享(因为它是static的成员变量)
3) 当使用第一种方式来生成线程对象时,我们需要重写run方法,因为Thread类的run方法此时什么事情也不做【(通过查看Runable源代码总结出来)】。
4) 当使用第二种方式来生成线程对象时,我们需要实现Runnable接口的run方法,然后使用new Thread(new MyThread())(假如MyThread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法就会调用MyThread类的run方法,这样我们自己编写的run方法就执行了【(通过查看Runable源代码总结出来)】。
9. 关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。
10. 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。
11. 停止线程的方式:不能使用Thread类的stop方法来终止线程的执行。一般要设定一个变量,在run 方法中是一个循环,循环每次检查该变量,如果满足条件则继续执行,否则跳出循环,线程结束。
12. 不能依靠线程的优先级来决定线程的执行顺序。
13. synchronized关键字:当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
14. Java 中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁【将该对象上锁,指的是上锁期间其它线程不能访问该对象任何一个synchronized方法,但是需要注意的是其它线程在上锁期间可以访问该对象里面的非synchronized方法。】,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者是抛出了异常),那么将该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法【这样就实现了单线程访问】。
15. 如果一个对象有多个synchronized 方法,某一时刻某个线程已经进入到了某个
synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的【不能访问任何synchronized方法,但是可以访问非synchronized方法。】。


多线程的几种状态:
1、创建状态;
2、可运行状态;
3、运行状态;
4、消亡状态。

从开始的new出来一个线程,线程就处在了创建状态,然后调用start()方法,线程就会转到可运行状态,意思就是线程已经做好一切准备具备了运行的条件,随时可以运行,可能现在还没有抢占到CPU资源,所以还没有运行。接着过了一会如果线程抢占到了CPU资源,这时候线程就变成了运行状态,线程就正在执行了。当正在运行的线程可能遇到了其他一些情况,比如说它正在运行呢需要处理一下IO,然后它就放弃了CPU资源,去处理IO了,这时候线程就变成阻塞Blocked(不可运行状态)了,一直到IO处理完毕了,线程就又具备了可运行的能力,然后它就又回到了可运行状态,就这样来回往复,在可运行状态和运行状态和Blocked之间转换。当线程执行完毕之后就转到了消亡状态Dead(这里只是简单的描述了线程运行过程中的一些状态转换,下面这个图详细展示了线程运行过程中各种状态之间的转换)。



线程的优先级了解下即可,不作为重点


下面是线程里面成员变脸和局部变量的区别的一个例子,上面的笔记里面第9条已经做了总结,这里是给出一个例子做更直观的说明:
package com.shengshiyuan.thread;

/**
 * 1、关于成员变量与局部变量:如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。 
 * 2、如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。 
 * 类: ThreadTest3 <br>
 * 描述: TODO <br>
 * 作者: 
 * 时间: Nov 20, 2013 4:36:02 PM
 */
public class ThreadTest3 {
	/**
	 * 这里的t1和t2是根据同一个Runnable子类对象r衍生出来的两个线程对象,当这两个线程对象调用start()方法启动的时候,它们调用的是同一个Runnable子类对象r里面的run()方法,它们两个操纵的是同一个Runnable子类对象。
	 * 方法: main <br>
	 * 描述: TODO <br>
	 * 作者: 
	 * 时间: Nov 20, 2013 4:20:54 PM
	 * @param args
	 */
	public static void main(String[] args) {
		Runnable r = new HelloThread();

		Thread t1 = new Thread(r);
		Thread t2 = new Thread(r);

		t1.start();
		t2.start();
	}
}

/**
 * 这个例子的重点是要看"int i"定义成成员变量和局部变量的打印结果。
 * 如果把"int i"放在成员变量的位置上时,当这个类的同一个对象的两个引用对它进行操作时候,这两个引用操作的是同一个成员变量i,一个引用对i的改变会反映到另一个引用上面(同一个对象的多个引用共享一套成员变量)。
 * 如果把"int i"放在局部变量的位置上时(放在run方法里面),当这个类的同一个对象的两个引用对它进行操作时候,这两个引用各自有各自的一套局部变量,各自有各自的一套方法,互相之间没有影响,一个引用对i的改变不会反映到另一个引用上(同一个对象的多个引用之间局部变量互相没有影响,各自有各自的一套方法,一套局部变量)。
 * 类: HelloThread <br>
 * 描述: TODO <br>
 * 作者: 
 * 时间: Nov 20, 2013 4:19:18 PM
 */
class HelloThread implements Runnable {
	// int i;

	public void run() {
		int i = 0;
		while (true) {
			System.out.println("number :" + i++);
			try {
				// 线程睡眠0-1秒之间不确定的一个时间
				Thread.sleep((long) (Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			if (50 == i) {
				break;
			}
		}
	}
}



多线程的同步问题是一个难点,重点掌握。


下面是自动取款机和银行柜台同时取钱的一个例子,这里就是涉及到多线程的一个例子,就是为了举例说明一下实际开发中多线程同步的一个实际例子:
package com.shengshiyuan.thread;

/**
 * 银行取钱,主要为了说明多线程同步问题确实存在,而且很重要。
 * 正常情况应该是第一次取了800,第二次余额不够了就不能取了,但是结果会出现余额-600的情况,余额出现负数就不正常了。这里就涉及到多线同步的问题了。
 * 但是如果给getMoney方法加上synchronized关键字修饰,上述问题就解决了。
 * 类: FetchMoney <br>
 * 描述: TODO <br>
 * 时间: Nov 20, 2013 4:53:27 PM
 */
public class FetchMoney {
	public static void main(String[] args) {
		Bank bank = new Bank();

		Thread t1 = new MoneyThread(bank);// 柜台取钱
		Thread t2 = new MoneyThread(bank);// 取款机取钱

		t1.start();
		t2.start();
	}
}

// 银行账户
class Bank {
	// 账户余额
	private int money = 1000;

	// 取钱,正常情况下返回的是取的钱的数目
	public int getMoney(int number) {
		System.out.println("余额:" + money);
		// 取的钱不能是负数
		if (number < 0) {
			return -1;
		} else if (number > money) {// 取的钱大于余额
			return -2;
		} else if (money < 0) {// 余额不能小于0
			return -3;
		} else {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("余额:" + money);
			money -= number;
			System.out.println("余额:" + money);
			return number;
		}
	}
}

// 线程,比如从柜台取和从取款机取就是两个线程
class MoneyThread extends Thread {
	private Bank bank;

	public MoneyThread(Bank bank) {
		this.bank = bank;
	}

	@Override
	public void run() {
		// 进行取款,并打印出我成功取款的数额
		System.out.println(bank.getMoney(800));
	}
}


下面是关于synchronized块以及synchronized、static修饰方法等的一些例子,上面笔记里面已经讲解了一些synchronized关键字相关的知识,下面举完这些例子之后将对这一部分知识做详细笔记:
【这几个例子目的是使结果打印有序,不要乱了】
package com.shengshiyuan.thread;

/**
 * synchronized的一个使用示例,目的是使结果打印有序,打印完hello再打印world
 * 类: ThreadTest4 <br>
 * 描述: TODO <br>
 * 作者: 
 * 时间: Nov 26, 2013 3:36:46 PM
 */
public class ThreadTest4 {
	public static void main(String[] args) throws Exception {
		Example example = new Example();

		Thread t1 = new TheThread(example);
		Thread t2 = new TheThread2(example);

		t1.start();
		t2.start();
	}
}

class Example {
	public synchronized void execute() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}

	public synchronized void execute2() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}
}

class TheThread extends Thread {
	private Example example;

	public TheThread(Example example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute();
	}
}

class TheThread2 extends Thread {
	private Example example;

	public TheThread2(Example example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute2();
	}
}

package com.shengshiyuan.thread5;

/**
 * synchronized static使用示例
 * 类: ThreadTest5 <br>
 * 描述: TODO <br>
 * 作者: 
 * 时间: Nov 26, 2013 3:38:48 PM
 */
public class ThreadTest5 {
	public static void main(String[] args) throws Exception {
		Example example = new Example();

		Thread t1 = new TheThread(example);
		// example = new Example();
		Thread t2 = new TheThread2(example);

		t1.start();
		t2.start();
	}
}

// 里面的两个方法都加上static修饰
class Example {
	public synchronized static void execute() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}

	public synchronized static void execute2() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}
}

class TheThread extends Thread {
	private Example example;

	public TheThread(Example example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute();
	}
}

class TheThread2 extends Thread {
	private Example example;

	public TheThread2(Example example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute2();
	}
}

package com.shengshiyuan.thread6;

/**
 * synchronized static和synchronized修饰方法时的区别
 * 类: ThreadTest6 <br>
 * 描述: TODO <br>
 * 作者: 
 * 时间: Nov 26, 2013 3:39:22 PM
 */
public class ThreadTest6 {
	public static void main(String[] args) throws Exception {
		Example example = new Example();

		Thread t1 = new TheThread(example);
		// example = new Example();
		Thread t2 = new TheThread2(example);

		t1.start();
		t2.start();
	}
}

// 里面的两个方法其中一个加上static修饰
class Example {
	public synchronized static void execute() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("hello: " + i);
		}
	}

	public synchronized void execute2() {
		for (int i = 0; i < 50; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("world: " + i);
		}
	}
}

class TheThread extends Thread {
	private Example example;

	public TheThread(Example example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute();
	}
}

class TheThread2 extends Thread {
	private Example example;

	public TheThread2(Example example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute2();
	}
}

package com.shengshiyuan.thread;

/**
 * synchronized代码块使用示例
 * 类: ThreadTest7 <br>
 * 描述: TODO <br>
 * 作者: 
 * 时间: Nov 26, 2013 3:41:01 PM
 */
public class ThreadTest7 {
	/**
	 * 要想使下面方法有序打印(hello打印完了之后打印world)。一种方法是可以给Example2里面的两个方法加上synchronized关键字修饰。
	 * 第二种方式是使用synchronized代码块(synchronized关键字的第二种用法,类似于静态代码块),synchronized后面小括号里面的参数表示的是锁的是哪个对象,当括号里面都填写this的时候synchronized块和synchronized修饰方法起到的效果是一样的。
	 * 方法: main <br>
	 * 描述: TODO <br>
	 * 作者: 
	 * 时间: Nov 21, 2013 4:31:02 PM
	 * @param args
	 */
	public static void main(String[] args) {
		Example2 e = new Example2();

		TheThread3 t1 = new TheThread3(e);
		TheThread4 t2 = new TheThread4(e);

		t1.start();
		t2.start();
	}
}

class Example2 {
	private Object object = new Object();

	public void execute() {

		// synchronized块的使用方式。这里表示的是线程进来之后会将synchronized后面括号里面的那个object对象给锁上
		// 当一个线程进来访问该方法时,就会将object对象锁上,同时其他的线程想要去访问下面的execute2方法,发现它也使用的是synchronized,并且里面指定的锁的对象也是object,并且object现在已经锁上了。所以访问不了execute2方法。直到前面的线程执行完execute方法并将object对象解锁了之后才可以访问execute2
		synchronized (object) {
			for (int i = 0; i < 50; i++) {
				try {
					Thread.sleep((long) (Math.random() * 100));
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("hello: " + i);
			}
		}

	}

	public void execute2() {

		synchronized (object) {
			for (int i = 0; i < 50; i++) {
				try {
					Thread.sleep((long) (Math.random() * 100));
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("world: " + i);
			}
		}

	}
}

class TheThread3 extends Thread {
	private Example2 example;

	public TheThread3(Example2 example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute();
	}
}

class TheThread4 extends Thread {
	private Example2 example;

	public TheThread4(Example2 example) {
		this.example = example;
	}

	@Override
	public void run() {
		this.example.execute2();
	}
}

下面是关于synchronized块以及synchronized、static修饰方法等的详细笔记:
16. 如果某个synchronized 方法是static 的,那么当线程访问该方法时,它锁的并不是
synchronized方法所在的对象,而是synchronized方法所在的对象所对应的Class对象,因为Java 中无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的【就像是给class对象上锁了。
访问同一个类的两个不同对象的两个不同static,synchronized方法,结果也是一样的,把class对象锁了,结果肯定一样。详细了解查看实例代码ThreadTest5.java】,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始执行【需要注意的是:synchronized单个修饰和"synchronized、static"同时修饰是两个完全独立的模块,是两个互相之间没有任何影响的模式。比如说我一个类里面有一个方法A被synchronized修饰的,另一个方法B是被synchronized、static同时修饰的。线程1访问类的实例a1里面的A方法,同时线程2也可以访问类的实例a2里面的B方法。之间没有影响。详细了解查看实例代码ThreadTest6.java】。
17. synchronized块,写法【使用示例见上面实例代码ThreadTest7.java】:
synchronized(object)
{

}
表示线程在执行的时候会对object对象上锁【当括号里面都填写this的时候synchronized块和synchronized修饰方法起到的效果是一样的】。
18. synchronized 方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该
synchronized方法【(把整个方法都锁了)】;synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内【(可以只将方法里面的几行代码锁了,方法里面的其他方法还可以是多个线程共享的)】、synchronized块之外的代码是可以被多个线程同时访问到的。


19. 死锁(deadlock)【这里讲解了哲学家就餐问题。哈哈,他们之间就模拟了死锁这种情况。】。
20. wait与notify方法都是定义在Object类中,而且是final 的,因此会被所有的Java
类所继承并且无法重写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或块当中。当线程执行了wait方法时,它会释放掉对象的锁【当调用wait()方法之后如果没有调用notify()方法,线程是永远起不来的,永远在那儿等待。而如果调用了sleep()方法,经过指定的毫秒数之后,线程会自动起来,然后继续执行。】。
21. 另一个会导致线程暂停的方法就是Thread类的sleep方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的


线程间的通信,指的是比如线程A执行完了,去通知线程B说:“我执行完了,你快执行吧…”。


死锁(deadlock):比如有两个线程,1线程和2线程,这两个线程都需要A和B两个资源才能完成自己的事情,假如1线程先获得了A,2线程先获得了B,这时候他们手里面各持有一个资源,但是还不够,还需要对方那个资源才能完成自己的事情,让对方把资源给它之后才能继续往下进行,但是悲剧的是这个时候这两个线程谁都不会让步,都不肯交出自己手里的一部分资源,都在等待,但是又都绝对得不到,这种情况就是死锁。


Object类里面的wait()方法说明:使得当前线程去等待,直到另一个线程调用了当前这个对象的notify()或notifyAll()方法。换句话说,这个方法的行为非常类似于执行了wait(0)。要想调用wait()方法,当前这个线程必须要拥有这个对象的监视器(锁)【所以wait()这个方法应该放在synchronized块或synchronized方法中】。当线程调用了wait()方法之后,它就会释放对这个锁的拥有权,然后等待着,直到有另外一个线程通知在这个对象的等待这个对象的锁的那些线程,让他们去唤醒,通知的方式要么是通过调用notify()方法或是调用notifyAll()方法,这个线程接下来就会等待,直到它能重新获得锁的拥有权并且继续去执行。
比较容易理解的解释:当一个线程正在执行synchronized方法并在这个方法里面调用wait()方法的时候,当前线程就不再继续往下执行了,它就开始等待,并且它这时候就会释放掉当前这个对象的锁,不再持有对象的锁了,然后其他线程就有可能进入到这个synchronized方法或synchronized块里面,那么当另外一个线程调用了notify()或notifyAll()方法的时候就会把刚才等待那个正在等待的线程唤醒,刚才那个线程被唤醒之后也不是说立刻就能往下执行,直到它能重新获得锁的拥有权之后它才会继续执行,从刚才停止那个地方开始继续往下执行。

Object类里面的notify()方法说明:会唤醒等待对象锁的这样一个线程(比如说调用对象wait()方法的线程就是处于等待对象锁的状态),如果有任意线程都在等待这个对象,那么他们当中的一个将会被唤醒,这种选择是任意的(不一定唤醒哪个)。要想等待对象的锁可以通过调用wait()方法重载版本里面的一个。。被唤醒的线程是不能被执行的直到当前线程放弃了对象的锁之后。。notify()方法也是在synchronized方法或synchronized块中才能被调用


一个线程会成为一个对象锁的拥有者,可以通过以下三种方式:
1、通过执行那个对象的一个synchronized实例方法。
2、通过执行synchronized(this)块的方式。
3、对于class对象,执行一个synchronized static方法。



下面想要写这么一个程序,有一个成员变量初始值为0,有一个线程为它一次加一,有一个线程一次为它减一,这两个线程同时启动来操作这个成员变量,要求打印结果是0和1交替出现,不容许混乱,这就要求两个线程之间一定要协作运行,执行加法的线程执行一次之后执行减法的线程就执行一次,就这样交替执行结果才不会出错。
下面这四个代码就使用wait()和notify()实现了上述需求,是wait()和notify()的一个使用示例:
package com.shengshiyuan.thread;

public class Sample {
	private int number = 0;

	public synchronized void increase() {
		if (0 != number) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		number++;
		System.out.println(number);
		notify();
	}

	public synchronized void decrease() {
		if (0 == number) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		number--;
		System.out.println(number);
		notify();
	}
}

package com.shengshiyuan.thread;

public class IncreaseThread extends Thread {
	private Sample sample;
	
	public IncreaseThread(Sample sample) {
		this.sample = sample;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep((long)(Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sample.increase();
		}
	}
}

package com.shengshiyuan.thread;

public class DecreaseThread extends Thread {
	private Sample sample;

	public DecreaseThread(Sample sample) {
		this.sample = sample;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sample.decrease();
		}
	}

}

package com.shengshiyuan.thread;

public class MainTest {
	public static void main(String[] args) {
		Sample sample = new Sample();
		
		Thread t1 = new IncreaseThread(sample);
		Thread t2 = new DecreaseThread(sample);
		
		t1.start();
		t2.start();
	}
}




另外后面在上面代码的基础上又总结出了另一个比较常见的难以理解的代码示例【这个是在Sample2里面两个方法里面的两个判断条件都是if的情况下说明的,都是if的情况下就会出错,后来改成while就不会有错了】,下面先贴出代码,然后在后面说明其中反映的问题和解决方式(也是4个代码):
package com.shengshiyuan.thread2;

public class Sample2 {
	private int number = 0;

	public synchronized void increase() {
		// 这里千万不能用if了,否则打印结果就乱了
		// 错误代码(注意自己研究对比,一定要知道出错原因)
		// if (0 != number) {
		// try {
		// wait();
		// } catch (InterruptedException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		// }

		// 正确代码
		while (0 != number) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		number++;
		System.out.println(number);
		notify();
	}

	public synchronized void decrease() {

		// 这里千万不能用if了,否则打印结果就乱了
		// 错误代码(注意自己研究对比,一定要知道出错原因)
		// if (0 == number) {
		// try {
		// wait();
		// } catch (InterruptedException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		// }

		// 正确代码
		while (0 == number) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		number--;
		System.out.println(number);
		notify();
	}
}


package com.shengshiyuan.thread2;

public class IncreaseThread2 extends Thread {
	private Sample2 sample;
	
	public IncreaseThread2(Sample2 sample) {
		this.sample = sample;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep((long)(Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sample.increase();
		}
	}
}

package com.shengshiyuan.thread2;

public class DecreaseThread2 extends Thread {
	private Sample2 sample;

	public DecreaseThread2(Sample2 sample) {
		this.sample = sample;
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			try {
				Thread.sleep((long) (Math.random() * 100));
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			sample.decrease();
		}
	}

}

package com.shengshiyuan.thread2;

public class MainTest2 {
	public static void main(String[] args) {
		Sample2 sample = new Sample2();
		
		Thread t1 = new IncreaseThread2(sample);
		Thread t2 = new DecreaseThread2(sample);
		
		Thread t3 = new IncreaseThread2(sample);
		Thread t4 = new DecreaseThread2(sample);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

这几个代码反映的问题以及容易出现问题的地方以及解决方式是:比如我的一个加的线程运行的时候发现不符合要求就执行wait()开始等待了,然后我的另一个加的线程又获得了对象锁开始执行,然后它发现自己也不符合就执行wait()也开始等待,然后一个减的线程获得了对象锁开始执行,一直执行完了它调用notify()唤醒其他等待中的线程中的一个,然后恰恰第一次执行的现在正在等待中的加的线程恰好被唤醒并又获得了对象锁,然后就绕过了开始的”if(0 != number)”的条件判断,直接执行下面的number++操作了,其实它根本不满足执行加的操作,就这样一步错,步步错,导致打印出了混乱的结果。



pdf文档里面的线程组模块不作为重点,了解下即可。





这里附件的multithread.pdf由于太大上传不上来,已经上传到百度网盘了,multithread.pdf里面是详细的讲解课件(流程性的笔记都在里面),自己到百度网盘上下载下来配合这里的笔记详细查看。下载地址:
http://pan.baidu.com/s/1ciL7s
  • 大小: 27.6 KB
  • 大小: 23.9 KB
分享到:
评论

相关推荐

    Java多线程知识点总结

    该文档总结了Java多线程相关的知识点,分享给大家,简单易懂!

    java多线程全面总结

    java多线程全面总结,简单的介绍多线程技术中的各种应用问题,是你对多线程有更多的认识!

    java多线程核心技术

    资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 Java多线程无处不在,如...

    java 多线程设计模式 进程详解

    《JAVA多线程设计模式》PDF 下载 《Java线程 高清晰中文第二版》中文第二版(PDF) 前言 第一章 线程简介 Java术语 线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口...

    Java多线程简单Demo

    个人的小总结,适合初学者,包含了java多线程的基本概念,再配上简单的demo,相信初学者学习完对多线程能有较清晰的认识

    Java多线程设计模式(含实例源码)

    Java多线程设计模式,通过对多线程环境的分析,抽象出典型的多线程模型,结合Java编程语言的特性,总结出经典的多线程设计模式,通过本资料的学习,足以让您掌握如何使用JAVA编程技术来解决多线程问题,结合本书实例...

    个人总结的深入java多线程开发

    看完《think in java》多线程章节,自己写的多线程文档,还结合了其他的相关网络资料。 线程 一. 线程池 1)为什么要使用线程池 2 2)一个具有线程池的工作队列 3 3)使用线程池的风险: 4 4)有效使用线程池的原则 5...

    总结Java中线程的状态及多线程的实现方式

    Java中可以通过Thread类和Runnable接口来创建多个线程,线程拥有五种状态,下面我们就来简单总结Java中线程的状态及多线程的实现方式:

    java concurrent source code

    资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 封底 Java多线程无处不在,...

    java基础学习总结笔记

    Java基础常识、如何安装Java工具、Java语言的基础组成、Java面向对象、Java多线程、Java常用类、集合(重点)、IO流、GUI图形界面、网络编程、正则表达式、反射、注解、类加载器、动态代理等等,另外还包括银行管理...

    Java 多线程学习详细总结

    本文主要介绍 Java 多线程的知识资料,这里整理了详细的多线程内容,及简单实现代码,有需要的朋友可以参考下

    《Java程序设计实训》报告 多人聊天室

    2、使用Java 的多线程机制,深入理解Java多线程技术的应用。  3、使用GUI,对JFrame的深入学习。 4、使用网络编程,掌握基于TCP协议的Socket编程,了解Socket编程的协议约定,掌握简单应用协议的开发。  5、使用C...

    Java多人聊天室(有登录注册)

    根据所学的java皮毛编写的一个简单聊天小程序,使用到了集合,IO,Socket,多线程,GUI等方面的知识,实现了服务器和客户端、登录注册、多人聊天、单独聊天等功能。算是对自己前期学习的小总结。

    一篇文章认识4种Java多线程的创建方式

    那么JAVA多线程实现方式:(1)继承Thread类实现多线程:(2)实现Runnable接口方式实现多线程:(3)实现callable方式:(比实现Runnable方式强大)(4)使用ExecutorService、Future(线程池):实现有返回结果的多线程...

    JavaSE回顾总结(狂神说java).xmind

    javaSE回顾总结,学习笔记,查漏补缺...Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点 。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等.

    JAVA程序员学习之路总结.zip

    它由Sun Microsystems(现在是Oracle Corporation)的James Gosling等人在1995年推出,被设计为一种简单、健壮、可移植、多线程、动态的语言。Java的主要特点和优势包括以下几个方面: 跨平台性(Write Once, Run ...

    Java面试题-面向对象、多线程.pdf

    1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: ...总共90多道题目,包含面向对象、算法、多线程等面试题及详解 大厂面试题集,纯人工手写,分享不易,有问题敬请谅解 。。。。。。。。

    java 编程入门思考

    1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...

    Java 排序算法知识点总结.zip

    它由Sun Microsystems(现在是Oracle Corporation)的James Gosling等人在1995年推出,被设计为一种简单、健壮、可移植、多线程、动态的语言。Java的主要特点和优势包括以下几个方面: 跨平台性(Write Once, Run ...

    Java知识点总结,面试必备.zip

    它由Sun Microsystems(现在是Oracle Corporation)的James Gosling等人在1995年推出,被设计为一种简单、健壮、可移植、多线程、动态的语言。Java的主要特点和优势包括以下几个方面: 跨平台性(Write Once, Run ...

Global site tag (gtag.js) - Google Analytics