Java基础知识多线程
1、多线程的介绍
- 什么是程序?
程序(Program)是一个静态的概念,一般对应于操作系统中的一个可执行文件。
-
什么是进程?
执行中的程序就是进程,是一个动态的概念。其实进程就是在一个内存中独立运行的程序空间。
-
什么是线程?
线程(Thread)是操作系统能够进行运算调度的最小单位。他被包含在进程之中,是进程中的实际运作单位,线程在进程中是共享内存空间的,即是共享进程中的资源。
- 什么是并发?
并发是指在一段时间内同时做多个事情,当有多个线程在运行时,如果只有一个CPU,在这种情况下操作系统会采用并发技术实现并发运行,具体做法是采用时间片轮询算法,在一个时间段内的线程代码运行,其他线程处于就绪状态,这就是并发。
- 线程的执行特点
方法的执行特点
线程的执行特点
- 什么是主线程以及子线程?
主线程:当Java程序启动时,一个线程就会立刻运行,该线程通常叫做主线程(main thread),即main方法对应的线程,它是程序开始时就执行的。
主线程特点:产生其它子线程的线程;它不一定是最后完成执行的线程,子线程可能在它结束之后还在运行。
子线程:在主线程中创建并启动,一般称为子线程。
注意:只要线程一启动,不论主次都是并行运行的!!!
2、线程的创建
-
继承Thread类实现多线程
- 在Java中与多线程操作想关的都是java.lang.Thread类。
- 可以通过创建Thread类的实例来创建线程。
- 每一个线程都是通过特定的Thread对象所对应的run()方法来完成线程的操作的,run方法称为线程体。
- 调用Thread类的start()方法来移动一个线程。
public class TestThread extends Thread{
//run为线程体
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//由于继承Thread类 因此可以直接调用获取线程名得方法
System.out.println(this.getName()+ " "+ i);
}
}
}
class test{
public static void main(String[] args) {
//创建线程对象
TestThread testThread = new TestThread();
//设置线程名称 必须在线程启动前
testThread.setName("Thhh");
//启动线程
testThread.start();
}
}
- 通过实现Runnable接口实现多线程
- 新建一个类,实现Runnable接口,实现run方法
- 启动线程,使用Thread类包装,上述定义的类,调用start()启动线程
public class TestThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+ i);
}
}
}
class test2 {
public static void main(String[] args) {
Thread t2 = new Thread(new TestThread2());
t2.setName("Runnable");
t2.start();
}
}
切记!!一个线程对象只能启动一次,否者就会报错!若想启动新的线程,必须在new 一个对象!
在实际开发中这种实现方式最常用。
从Thread类中的源码角度看,Thread类也实现了Runnable接口。
而在Runnable中接口只有一个抽象方法
两种方式比较看,实现Runnable接口的方式要通用一些。
该方式克服了继承Thread类的缺点,即在实现Runnable接口的同时还可以继承某个类。
- 线程的执行流程
- 线程状态和声明周期
一个线程在其生命周期内,需要经历一下5个状态。
新生状态(NEW):用new关键字建立一个线程对象后,这样的线程就是新生状态。处于新生状态的线程有这自己的内存空间,只有通过调用start()方法后才进入下一个状态就绪状态Runnable。
就绪状态(Runnable):处于该阶段(状态)的线程已经具备了运行条件,但是还没有分配到CPU(即是无权执行),就一直处于“线程就绪队列”中等待,等待操作系统为其分配CPU执行权限。就绪状态(Runnable) 不是执行状态(Running),而是当操作系统在“线程就绪队列”中选定一个等待的线程对象后,他就会进入执行状态。一旦获取到cpu,线程进入了运行状态(Running),就会调用自己的Run()方法。
线程进入就绪状态的原因:
运行状态(Running):执行自己线程体里面的代码,知道调用其他方法而终止或等待某资源而阻塞或者完成任务而死亡。如果时间片到了,该线程还没有执行结束,那么该线程就会被换下来,进入就绪状态。也有可能由于某些阻塞的事件导致该线程直接进入阻塞状态。
导致阻塞的原因:
死亡状态(Terminated):
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作; 另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。
当一个线程进入死亡状态以后,就不能再回到其它状态了。
3、线程的使用
- 典型的终止线程的方式
终止线程我们一般不使用JDK提供的stop()/destroy()方法(它们本身也被JDK弃用了)
通常的做法是提供一个boolean变量用于控制线程体是否继续执行。
public class TestThread3 implements Runnable{
private boolean flag = true;
public TestThread3() {
}
public TestThread3(boolean flag) {
this.flag = flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始");
int i= 0;
while (flag){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" "+i++);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"结束");
}
}
class test3 {
public static void main(String[] args) {
System.out.println("主线程开始");
TestThread3 t3 = new TestThread3();
Thread thread = new Thread(t3);
thread.start();
try {
System.in.read();
//停止子线程
t3.setFlag(false);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("主线程结束!");
}
}
- 线程休眠
sleep()方法:可以让当前正在运行的线程进入阻塞状态,直到休眠的时间结束。sleep方法的参数为休眠的毫秒数。
public class TestThread4 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始了");
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " "+ i);
}
System.out.println(Thread.currentThread().getName()+"结束了");
}
}
class test4 {
public static void main(String[] args) {
System.out.println("主线程开始");
TestThread4 testThread4 = new TestThread4();
Thread thread = new Thread(testThread4,"myTreadSleep");
thread.start();
System.out.println("主线程结束");
}
}
- 线程让步(礼让)
yield()让当前正在运行的线程回到就绪状态,让其相同优先级的线程。使用yield()的目的是让具有相同优先级的线程之间能够适当的乱换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
- yield()是一个静态方法;
- 调用yield()告诉当前线程把运行机会交给具有相同优先级的线程。
- yield() 不能保证,当前线程迅速从运行状态转换到就绪状态。
- yield只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。
- 案例
public class TestyieldThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 30; i++) {
if (Thread.currentThread().getName().equals("线程一")) {
if (i == 10 ) {
System.out.println("让步");
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName()+"\t"+ (i+1));
}
}
}
class test5{
public static void main(String[] args) {
TestyieldThread ty1 = new TestyieldThread();
TestyieldThread ty2 = new TestyieldThread();
new Thread(ty1,"线程一").start();
new Thread(ty2,"线程二").start();
}
}
- 线程联合
当前要求联合的线程必须等待被联合的线程先执行完才能继续执行,线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。
- 案例
package Create;
import sun.awt.windows.ThemeReader;
class A implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+ i);
}
}
}
class B implements Runnable {
private Thread a;
public B(Thread a) {
this.a = a;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i == 4){
try {
System.out.println("B中现在i = "+ i +",线程 "+"B已经联合了线程A,B必须等待A执行完成");
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public class TestJoinThread {
public static void main(String[] args) {
A a = new A();
Thread thread = new Thread(a,"A");
B b1 = new B(thread);
Thread thread1 = new Thread(b1,"B");
thread.start();
thread1.start();
for (int i = 0; i < 20; i++) {
if (i == 5){
try {
System.out.println("main中现在i = "+ i +",线程 "+ Thread.currentThread().getName() + "已经联合了线程B,main必须等待B执行完成");
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+ i);
}
}
}
在main中i等于5之前,在B中i小于4之前,没有进行线程联合,3个线程可以并行执行。main中i = 5时联合了B,所以之后main必须等待B完成,在B中i = 4的时候联合了A,B必须等待A的完成。
- 线程联合案例:实现爸爸让儿子买烟
package Create;
public class TestJoinDemo {
public static void main(String[] args) {
new Thread(new FartherThread()).start();
}
}
/**
* 儿子线程
*/
class SonThread implements Runnable{
@Override
public void run() {
System.out.println("儿子拿到了钱,出去买烟了!");
System.out.println("儿子买烟需要10分钟!");
for (int i = 1; i <= 10 ; i++) {
System.out.println("第"+ i +" 分钟");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("儿子买烟回来了");
}
}
/**
* 爸爸抽烟线程
*/
class FartherThread implements Runnable{
@Override
public void run() {
System.out.println("爸爸想抽烟,烟没了");
System.out.println("让儿子去买烟");
Thread son = new Thread(new SonThread(), "儿子买烟中……");
son.start();
System.out.println("需要等10分钟");
try {
son.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("儿子丢了,烟没买掉 ");
System.exit(1);
}
System.out.println("烟已到手,开抽,把零钱给了儿子!");
}
}
-
获取线程名字
-
方式一
this.getName()获取线程名称,该方法适用于继承Thread实现多线程方式。
class GetName1 extends Thread{ @Override public void run() { System.out.println(this.getName()); } }
-
方式二
hread.currentThread().getName()获取线程名称,该方法适用于实现Runnable接口实现多线程方式。
class GetName2 implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
-
-
修改线程名
-
方式一:通过构造方法设置线程名称。
package Create; public class SetName1 extends Thread { public SetName1(String name) { super(name); } @Override public void run() { System.out.println(this.getName()); } } class SetName{ public static void main(String[] args) { new SetName1("setName").start(); } }
-
方式二:通过setName()方法设置线程名称。
class SetName2 implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } } class SetName{ public static void main(String[] args) { // new SetName1("setName").start(); SetName2 setName2 = new SetName2(); Thread thread = new Thread(setName2); thread.setName("SetNamw2"); thread.start(); } }
-
-
判断线程是否存活
isAlive()方法: 判断当前的线程是否处于活动状态。
活动状态是指线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是存活的。
活动状态只包括:就绪,运行,阻塞状态
package Create;
public class Alive implements Runnable{
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName()+" "+ i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TestAliveThread {
public static void main(String[] args) {
Thread thread = new Thread(new Alive());
thread.setName("Alive");
thread.start();
System.out.println(thread.getName()+" "+thread.isAlive());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName()+" "+thread.isAlive());
}
}
4、线程的优先级
- 线程优先级的使用
int getPriority();
void setPriority(int newProority);
优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
package Create; public class Priority implements Runnable{ private int num = 0; private boolean flag = true; @Override public void run() { while (flag){ System.out.println(Thread.currentThread().getName()+" "+ (++num)); } } public void stop(){ this.flag = false; } } class PriorityThread { public static void main(String[] args) { Priority p1 = new Priority(); Priority p2 = new Priority(); Thread t1 = new Thread(p1,"线程1"); Thread t2 = new Thread(p2,"线程2"); System.out.println(t1.getPriority()); //Thread.MAX_PRIORITY = 10 t1.setPriority(Thread.MAX_PRIORITY); //Thread.MAX_PRIORITY = 1 t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } p1.stop(); p2.stop(); } }
5、守护线程
- 什么是守护线程?
在Java中有两类线程
User Thread(用户线程):就是程序里面的自定义线程
Daemon Thraed(守护线程):比如垃圾回收机制的线程,就是典型的守护线程。
守护线程是一个服务线程,使用于服务其他的线程。
- 守护线程的特点:
- 守护线程会随着用户线程的死亡而消亡。用户线程只有两种情况会死掉,run中异常终止,正常把run执行完毕,线程死亡。
package Create;
class UsersThread implements Runnable{
@Override
public void run() {
Daemon daemon = new Daemon();
Thread t = new Thread(daemon, "Daemon");
t.setDaemon(true);
t.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Daemon implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class DaemonThread {
public static void main(String[] args) {
Thread t = new Thread(new UsersThread(),"UsersThread");
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
6、线程同步
- 什么是线程同步?
线程冲突现象:
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。
- 线程同步的概念
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候,就需要用到线程同步,否则拿到的对象数据可能不正确。线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程在进行操作,这就是线程同步。
-
线程冲突案例:银行取钱问题,如果不进行线程同步操作,导致金额可能会不正确。
- 取钱的步骤基本可以分为几个步骤
- 用户输入账户密码,系统判断用户的账户密码是否正确。
- 用户输入取款金额
- 系统判断账户余额是否大于或等于取款金额
- 如果余额大于或等于取款金额,则成功,否则失败。
package Create; class Accunt { private String accountNo; private double balance; public Accunt() { } public Accunt(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } } /** * 取钱线程 */ class DrawThread implements Runnable{ private Accunt accunt; private double needMony; public DrawThread(Accunt accunt,double needMony) { this.accunt = accunt; this.needMony = needMony; } public double getNeedMony() { return needMony; } public void setNeedMony(double needMony) { this.needMony = needMony; } @Override public void run() { //判断金额是否大于等于当前用户的余额 if (this.accunt.getBalance() >= this.needMony){ System.out.println(Thread.currentThread().getName()+" 取钱成功!"+this.needMony+"元"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //更新用户余额 this.accunt.setBalance(this.accunt.getBalance() - this.needMony); System.out.println("该账户余额为:"+this.accunt.getBalance()); }else { System.out.println("余额不足,取款失败"); } } } public class TestDrawMoneyThread { public static void main(String[] args) { Accunt accunt = new Accunt("1234", 1000); new Thread(new DrawThread(accunt,800),"王晓丽").start(); new Thread(new DrawThread(accunt,600),"张菊").start(); } }
- 取钱的步骤基本可以分为几个步骤
很显然这是不对的!,原因是在张菊取钱走600时,还没来得设置余额为400,当前余额还是1000,当王晓丽来了之后一看,诶还有一千!可以取800,那就取走!此时张菊已经更新余额为400了。而王晓丽也在取800,然后更新账户就出现了-400.这明显不对,产生的原因就是线程冲突!
- 实现线程同步
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。这套机制就是synchronized关键字。
synchronized语法结构:
synchronized(锁对象){ 同步代码 }
- 线程冲突案例解决冲突:锁定当前账户
package Create;
class Accunt {
private String accountNo;
private double balance;
public Accunt() {
}
public Accunt(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
/**
* 取钱线程
*/
class DrawThread implements Runnable {
private Accunt accunt;
private double needMony;
public DrawThread(Accunt accunt, double needMony) {
this.accunt = accunt;
this.needMony = needMony;
}
public double getNeedMony() {
return needMony;
}
public void setNeedMony(double needMony) {
this.needMony = needMony;
}
@Override
public void run() {
synchronized (this.accunt) {
System.out.println(Thread.currentThread().getName()+" 正在取钱……");
//判断金额是否大于等于当前用户的余额
if (this.accunt.getBalance() >= this.needMony) {
System.out.println(Thread.currentThread().getName() + " 取钱成功!" + this.needMony + "元");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新用户余额
this.accunt.setBalance(this.accunt.getBalance() - this.needMony);
System.out.println("该账户余额为:" + this.accunt.getBalance());
} else {
System.out.println(Thread.currentThread().getName()+" 余额不足,取款失败");
}
}
}
}
public class TestDrawMoneyThread {
public static void main(String[] args) {
Accunt accunt = new Accunt("1234", 1000);
new Thread(new DrawThread(accunt, 800), "王晓丽").start();
new Thread(new DrawThread(accunt, 600), "张菊").start();
}
}
-
线程同步的使用
-
使用this作为对象锁(在方法上就是锁当前类对象的实例)
-
语法结构
synchronized (对象锁){//简单理解为一种标识,互斥的线程的标识 //代码块 } //或 public synchronized void accessVal(int newVal){ //同步代码 }
- 案例
package Create; /** * 定义程序员类 */ class Programmer{ private String name; public Programmer(String name){ this.name = name; } //打开电脑 synchronized public void openCompurter(){ try { System.out.println(this.name+"插上电源"); Thread.sleep(500); System.out.println(this.name+"按电源键"); Thread.sleep(500); System.out.println(this.name+"系统启动中"); Thread.sleep(500); System.out.println(this.name+"系统启动成功"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } //编码 synchronized public void encoding(){ try { System.out.println(this.name+"点击桌面IDE"); Thread.sleep(500); System.out.println(this.name+"IDE启动中"); Thread.sleep(500); System.out.println(this.name+"IDE启动成功"); Thread.sleep(500); System.out.println(this.name+"开始写代码"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 打开电脑工作线程 */ class Working1 extends Thread{ private Programmer programmer; public Working1(Programmer programmer) { this.programmer = programmer; } @Override public void run() { this.programmer.openCompurter(); } } /** * 编码工作线程 */ class Working2 extends Thread{ private Programmer programmer; public Working2(Programmer programmer) { this.programmer = programmer; } @Override public void run() { this.programmer.encoding(); } } public class TestSyncThread { public static void main(String[] args) { Programmer p = new Programmer("张三"); new Working1(p).start(); new Working2(p).start(); } }
-
-
使用字符串作为线程对象锁
synchronized(“字符串”){
//同步代码
}
在程序员类中加
/**
* 去卫生间
*/
public void wc(){
synchronized ("suibian") {
try {
System.out.println(this.name + " 打开卫生间门");
Thread.sleep(500);
System.out.println(this.name + " 开始排泄");
Thread.sleep(500);
System.out.println(this.name + " 冲水");
Thread.sleep(500);
System.out.println(this.name + " 离开卫生间");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestSyncThread {
public static void main(String[] args) {
Programmer p = new Programmer("张三");
Programmer p1 = new Programmer("李四");
Programmer p2 = new Programmer("王五");
new WC(p).start();
new WC(p1).start();
new WC(p2).start();
}
-
使用Class作为线程对象锁
-
语法模板
synchronized(XX.class){ //同步代码 } //或者 synchronized public static void accessVal()
-
该对象锁的是以当前类为模板的所有实例对象
-
自定义对象做为对象锁,也就是这个类为为自己定义的类。
-
什么是线程死锁?
“死锁”指的是:多个线程之间彼此拥有所需的资源,并且相互等待其他线程所占有的资源才能进行。导致两个或多个线程都在等待对方四方资源,都停止执行的情形。
- 死锁案例
/** * 口红类 */ class Lipstick{ } /** * 镜子类 */ class Mirror{ } /** * 化妆线程类 */ class Makeup extends Thread{ private int flag; //flag=0:拿着口红。flag!=0:拿着镜子 private String girlName; static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); public Makeup(int flag,String girlName){ this.flag = flag; this.girlName = girlName; } @Override public void run() { this.doMakeup(); } /** * 开始化妆 */ public void doMakeup(){ if(flag == 0){ synchronized (lipstick){ System.out.println(this.girlName+" 拿着口红"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (mirror){ System.out.println(this.girlName+" 拿着镜子"); } } }else{ synchronized (mirror){ System.out.println(this.girlName+" 拿着镜子"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lipstick){ System.out.println(this.girlName+" 拿着口红"); } } } } } public class DeadLockThread { public static void main(String[] args) { new Makeup(0,"大丫").start(); new Makeup(1,"小丫").start(); } }
上述死锁解决,同步块不能嵌套,改为并行。
死锁问题的解决
死锁是由于 “同步块需要同时持有多个对象锁造成”的,要解决这个问题,思路很简单,就是:同一个代码块,不要同时持有两个对象锁。
7、线程并发协作_生产者消费者模式
多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。
package Create;
class ManTou{
private int id;
public ManTou(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
/**
* 定义缓冲区类
*/
class SyncStack{
private ManTou[] mt = new ManTou[10];
private int index;
//放馒头
public synchronized void push(ManTou manTou){
while (this.index == this.mt.length){
/**
* 语法:wait(),该方法必须要在synchronized块中调用。
* wait执行后,线程会将持有的对象锁释放,并进入阻塞状态,
* 其他需要该对象锁的线程就可以继续运行了。
*/
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//唤醒取馒头的线程
/**
* 语法:该方法必须要在synchronized块中调用。
* 该方法会唤醒处于等待状态队列中的一个线程。
*/
this.notify();
this.mt[index++] = manTou;
}
/**
* 取馒头
*/
public synchronized ManTou pop(){
//没有馒头我就通知生产者生产
while (this.index == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
this.index--;
return mt[index];
}
}
class ShengChan extends Thread{
private SyncStack syss;
public ShengChan(SyncStack syss) {
this.syss = syss;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("生产馒头"+ i);
this.syss.push(new ManTou(i));
}
}
}
/**
* 定义消费者线程类
*/
class XiaoFei extends Thread{
private SyncStack ss;
public XiaoFei(SyncStack ss){
this.ss = ss;
}
@Override
public void run() {
for(int i=0;i<10;i++){
ManTou manTou = this.ss.pop();
System.out.println("消费馒头:"+i);
}
}
}
public class TestProduceThread {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
new ShengChan(ss).start();
new XiaoFei(ss).start();
}
}
8、线程并发协作总结
线程并发协作(也叫线程通信)
生产者消费者模式:
- 生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
- 对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
- 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。
- 在生产者消费者问题中,仅有synchronized是不够的。synchronized可阻止并发更新同一个共享资源,实现了同步但是synchronized不能用来实现不同线程之间的消息传递(通信)。
- 那线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:
方法名 | 作 用 |
---|---|
final void wait() | 表示线程一直等待,直到得到其它线程通知 |
void wait(long timeout) | 线程等待指定毫秒参数的时间 |
final void wait(long timeout,int nanos) | 线程等待指定毫秒、微秒的时间 |
final void notify() | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行 |
以上方法均是java.lang.Object类的方法;都只能在同步方法或者同步代码块中使用,否则会抛出异常。
id run() {
for(int i=0;i<10;i++){
ManTou manTou = this.ss.pop();
System.out.println(“消费馒头:”+i);
}
}
}
public class TestProduceThread {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
new ShengChan(ss).start();
new XiaoFei(ss).start();
}
}
总结
这是一篇多线程基础,包含一些定义,以及一些偏向应用的多线程!接下来将会从面向面试问题的角度对多线程的讲解。到此点个赞吧!!