程序本来是静止的,静止在磁盘上的,当它运行之后,当它运行之后,它就处在一个进程当中,而一个进程里面可以包含着多个线程,多个线程可以同时的运行,所谓多线程就是说多个这样的线程可以同时的去运行。就比如说你去一个建筑工地干活,有一对砖头你想要把
他们搬走,如果你一个人去,那你就是一个进程,进程里面就你一个线程,你就开始一次次搬砖,一次可能搬几块砖,来回这样搬,直到你把这些砖都搬完了;也可能是你找了其他的两个人来搬这个转,你给他们点钱让他们帮你把这个转搬完,你就不用干了,然后这两个人就开始搬砖,由于他们两个人的体力不同搬砖的节奏可能也不同,可能出现两个人同向搬砖,也可能出现两个人迎面走来的情况等等,如果搬砖的人不止两个人的话,出现的情况可能就更复杂了。每个搬砖的工人都可以比作一个线程,搬砖这件事就是进程。
例如我打开了一个word文档,那么word这个程序就相当于是一个进程,我可以在word文档里面修改内容,也可以打印这些文档等等,这些操作都是这个进程里面的一个线程。这些线程共享同一块
内存空间和一组系统资源,有可能相互影响。
注:java线程模型和多线程的优势等了解下即可。
下面是两个简单的线程示例:
class="java" name="code">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里面是详细的讲解课件(流程性的笔记都在里面),自己到百度网盘上下载下来配合这里的笔记详细查看。
- 大小: 27.6 KB
- 大小: 23.9 KB