java 锁旗标_Java多线程

Java多线程

1. 多线程存在的意义

多线程最大的作用就是能够创建“灵活响应”的桌面程序,而编写多线程最大的困难就是不同线程之间共享资源的问题,要使这些资源不会同时被多个线程访问。由于CPU的调度具有一定的随机性,因此使用多线程千万别较真。

2. 进程与线程的区别

-进程:运行时(runtime)的应用程序,进程之间的内存是独立的,进程之间的通信需要使用socket

-线程:进程中并发执行的代码段,线程之间的内存时共享的,每一个运行着的线程对应一个stack,一个应用程序(进程)至少有一个线程(主线程)

3. 线程的创建方式

3.1 方式一:继承Thread类

1、覆盖重写run()方法,将需要同步执行的代码编写到run()方法中。

2、创建子类对象的同时线程被创建

3、调用Thread.start()方法开启线程

3.1.1 Thread.yield()方法

yield()是静态方法,与对象无关。让当前线程放弃cpu的抢占权,具有谦让之意,但动作是瞬时的,放弃之后又会立即去抢占cpu。

案例1:多个线程交替打印当前系统时间,观察输出情况

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 importjava.util.Date;4

5 /*

6 * 案例1:多个线程交替打印当前系统时间,观察输出情况7 **/

8 public class PrintSysDateTime extendsThread {9

10 @Override11 public voidrun() {12 String name =Thread.currentThread().getName();13 for (int i = 0; i < 10; i++) {14 System.out.println(name + ":"+newDate());15 //输出完成一次之后放弃当前cpu的抢占权

16 Thread.yield();17 }18 }19 }

PrintSysDateTime

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg.test;2

3 importcom.yss.gyg.PrintSysDateTime;4

5 importjava.util.Date;6

7 public classPrintSysDateTimeTest {8 public static voidmain(String[] args) {9 PrintSysDateTime t1 = newPrintSysDateTime();10 PrintSysDateTime t2 = newPrintSysDateTime();11 PrintSysDateTime t3 = newPrintSysDateTime();12 t1.start();13 t2.start();14 t3.start();15 }16 }

PrintSysDateTimeTest

3.1.2 Thread.join()方法

join()方法:当前线程需等待指定的线程结束后才能继续运行。

案例2:打麻将需要4个人,需4个人都到了之后才能开局

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 public class Player extendsThread{4 privateString name;5 private inttime;6

7 public Player(String name, inttime) {8 this.name =name;9 this.time =time;10 }11

12 @Override13 public voidrun() {14 System.out.println(name + ":出发了");15 try{16 Thread.sleep(time);17 } catch(InterruptedException e) {18 e.printStackTrace();19 }20 System.out.println(name + ":到了");21 }22 }

Player

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg.test;2

3 importcom.yss.gyg.Player;4

5 public classPlayerTest {6 public static voidmain(String[] args) {7 Player player1 = new Player("player1", 1000);8 Player player2 = new Player("player2", 1500);9 Player player3 = new Player("player3", 1500);10 Player player4 = new Player("player4", 1700);11 player1.start();12 player2.start();13 player3.start();14 player4.start();15 try{16 player1.join();17 player2.join();18 player3.join();19 player4.join();20 } catch(InterruptedException e) {21 e.printStackTrace();22 }23 System.out.println("开搞!!!");24 }25 }26 /*

27 player1:出发了28 player4:出发了29 player3:出发了30 player2:出发了31 player1:到了32 player2:到了33 player3:到了34 player4:到了35 开搞!!!36 **/

PlayerTest

3.1.3 t1.start()和直接调用run()方法的区别

直接调用run()方法:直接调用run方法就是普通方法的调用,run()方法的执行依然是压栈到mian()的栈中,每一个运行着的线程对应一个stack

t1.start():我们只是告诉cpu可以调用run()方法了,具体又cpu开启一个新的线程去调用run()方法,也就是另外开启一个stack

3.1.4  Thread.setDaemon():设置守护线程

守护线程是为其它的线程服务的,当一个进程中除了守护线程外的其它线程都执行完时,则该进程结束。

案例3:Waiter为酒吧里的每一个包厢服务,每一个包厢为一个线程,当所有的包厢都结束之后Waiter下班,整个进程结束。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 importjava.util.Date;4

5 public class Waiter extendsThread {6 @Override7 public voidrun() {8 //每隔2s钟报一次时间

9 for (; ; ) { //死循环

10 System.out.println("当前时间:" + newDate());11 try{12 Thread.sleep(2000);13 } catch(InterruptedException e) {14 e.printStackTrace();15 }16 }17 }18 }

Waiter

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 public class Box extendsThread{4 privateString name;5 private inttime;6

7 public Box(String name, inttime) {8 this.name =name;9 this.time =time;10 }11

12 @Override13 public voidrun() {14 System.out.println(name+":开始消费");15 try{16 Thread.sleep(time);17 } catch(InterruptedException e) {18 e.printStackTrace();19 }20 System.out.println(name+":消费结束");21

22 }23 }

Box

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg.test;2

3 importcom.yss.gyg.Box;4 importcom.yss.gyg.Waiter;5

6 public classWaiterTest {7 public static voidmain(String[] args) {8 Waiter waiter = newWaiter();9 Box box1 = new Box("box1", 5000);10 Box box2 = new Box("box2", 8000);11 Box box3 = new Box("box3", 10000);12 Box box4 = new Box("box4", 7800);13 waiter.setDaemon(true);14 waiter.start();15 box1.start();16 box2.start();17 box3.start();18 box4.start();19 }20 }21 /*

22 box1:开始消费23 box4:开始消费24 box3:开始消费25 box2:开始消费26 当前时间:Fri Dec 18 16:41:15 CST 202027 当前时间:Fri Dec 18 16:41:17 CST 202028 当前时间:Fri Dec 18 16:41:19 CST 202029 box1:消费结束30 当前时间:Fri Dec 18 16:41:21 CST 202031 box4:消费结束32 box2:消费结束33 当前时间:Fri Dec 18 16:41:23 CST 202034 box3:消费结束35 **/

WaiterTest

3.1.5 线程间资源共享(synchronize)

多线程之间的内存是共享的,但多个线程同时访问同一个资源的时候会出现线程安全问题,同步可以解决线程安全问题。

同步的前提:同步需要两个或者两个以上的线程,多个线程使用的是同一个锁。

同步的方式:

同步代码块:同一时刻只能有一个线程执行同步代码块中的代码,同步代码块以指定的那个对象为“锁旗标”。同步代码块执行期间,线程始终持有对象的监控权,其它的线程处于阻塞状态。

1 synchronize(对象){

3 需要同步的代码块

5   }

同步非静态方法:synchronize(this)=== 同步方法,以当前对象为“锁旗标”

同步静态方法:使用类的描述符(.class)作为“锁旗标”。

同步的弊端:当线程比较多的时候,由于每个线程都要去判断锁的状态,需要消耗比较多的资源,因此会降低程序的运行效率。

案例4:多线程卖票:多个售票员同时卖一个票池中的票,直到票被全部卖完后结束进程。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 public classTicketPoll {4 private int initNum;//初始化票池

5

6 public TicketPoll(intinitNum) {7 this.initNum =initNum;8 }9

10 //同步方法:以当前对象为锁旗标

11 public synchronized int getTicket() {//取票的方法同时只能有一个线程执行12 //有票返回票号

13 if (initNum > 0) {14 //initNum--;//原子性操作

15 return initNum--;16 }17 //没票返回-1

18 return -1;19 }20 }

TicketPoll

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2 /*售票员类*/

3 public class Seller extendsThread {4 privateString name;5 privateTicketPoll poll;6

7 publicSeller(String name, TicketPoll poll) {8 this.name =name;9 this.poll =poll;10 }11

12 @Override13 public voidrun() {14 int ticketNum = -1;15 while ((ticketNum = poll.getTicket()) > 0) {16 System.out.println(name + "卖出了 " + ticketNum + " 号票");17 try{18 //模拟售票时间

19 Thread.sleep(50);20 } catch(InterruptedException e) {21 e.printStackTrace();22 }23 }24

25 }26 }

Seller

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg.test;2

3 importcom.yss.gyg.Seller;4 importcom.yss.gyg.TicketPoll;5

6 public classSellerTest {7 public static voidmain(String[] args) {8 TicketPoll poll = new TicketPoll(100);9 Seller seller1 = new Seller("seller1", poll);10 Seller seller2 = new Seller("seller2", poll);11 Seller seller3 = new Seller("seller3", poll);12 System.out.println("售票开始...");13 seller1.start();14 seller2.start();15 seller3.start();16

17 }18 }

SellerTest

3.1.6 线程间通信(wait()、notify()、notifyall())

wait()方法:让当前线程进入等待队列,释放cpu的抢占权,并且还释放“锁旗标”的监控权,进入等待队列后等待Object.notify()来通知可以“抢”cpu了。

notify()方法:通知等待队列中的一个线程唤醒,若等待队列中有多个等待线程,将随机选择一个。

notifyall()方法:通知等待队列中的所有线程唤醒,可以解决“死锁”的象限。

案例5:多线程生产者、消费者问题:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 public class Producer extendsThread{4 privateString name;5 privateP_C_Poll poll;6

7 publicProducer(String name, P_C_Poll poll) {8 this.name =name;9 this.poll =poll;10 }11

12 @Override13 public voidrun() {14 int i = 1;15 while (true) {16 poll.add(i);17 i++;18 try{19 sleep(500);//模拟生产时间

20 } catch(InterruptedException e) {21 e.printStackTrace();22 }23 }24 }25 }

生产者

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 public class Consumer extendsThread{4 privateString name;5 privateP_C_Poll poll;6

7 publicConsumer(String name, P_C_Poll poll) {8 this.name =name;9 this.poll =poll;10 }11

12 @Override13 public voidrun() {14 while (true) {15 poll.remove();16 try{17 sleep(200);//模拟消费时间

18 } catch(InterruptedException e) {19 e.printStackTrace();20 }21 }22 }23 }

消费者

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg.test;2

3 importcom.yss.gyg.Consumer;4 importcom.yss.gyg.P_C_Poll;5 importcom.yss.gyg.Producer;6

7 public classP_C_Test {8 public static voidmain(String[] args) {9 P_C_Poll poll = newP_C_Poll();10 //10个生产者

11 for (int i = 0; i < 10; i++) {12 new Producer(i + "", poll).start();13 }14

15 //5个消费者

16 for (int i = 0; i < 5; i++) {17 new Consumer(i + "", poll).start();18 }19

20 }21 }

测试类

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packagecom.yss.gyg;2

3 importjava.util.ArrayList;4 importjava.util.List;5

6 public classP_C_Poll {7 private int max = 100;8 private List list = new ArrayList<>();9

10 public synchronized void add(intnum) {11 int i = 0;12 //容器已满

13 while ((i = list.size()) >=max) {14 notify();//通知消费

15 try{16 wait();//进入等待

17 } catch(InterruptedException e) {18 e.printStackTrace();19 }20 }21 //容器没满

22 list.add(num);23 System.out.println("生产了:" +num);24 System.out.println("容器中数量:" +list.size());25 notify();//通知消费

26 }27

28 public synchronized intremove() {29 int i = 0;30 //容器为空

31 while (list.isEmpty() && (i = list.size()) == 0) {32 notify();//通知生产

33 try{34 wait();//进入等待队列

35 } catch(InterruptedException e) {36 e.printStackTrace();37 }38 }39 //容器不为空

40 int num = list.remove(0);41 System.out.println("消费了:" +num);42 System.out.println("容器中数量:" +list.size());43 notify();//通知生产

44 returnnum;45 }46 }

容器类

wait()、notify()、notifyall()方法为什么定义在Object类中?

wait()、notify()、notifyall()方法都是在同步中使用,而同步必须要有一个“锁”,“锁”可以是任意对象,因此定义在Object中。

sleep()和wait()方法的区别:

sleep()方法释放cpu的抢占权,不释放“锁”的监控权

wait()方法释放cpu的抢占权,同时释放“锁”的监控权

3.2 方式二:

-------------------------------end--------------------------------------------------------

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值