应用程序

-------------

 

 

进程

-------------

1.运行时(runtime)应用程序。

2.进程之间的内存不是共享(独占)

3.进程间通信使用的socket(套接字).

 

多线程

-------------

1.同一个进程内并发执行的代码段。

2.线程之间共享内存。

3.创建灵活响应的桌面程序。(带操作界面的程序)

4.每个运行着的线程对应一个stack(栈)。

5.应用程序至少有一个线程(主线程)

 

java.lang.Thread   ( Thread 线程类)

----------------

学习多线程时,要保持一个不要较真的心态,因为在程序调用程序的线程时,什么时候调用是不好说的

1.Thread.yield()方法

  让当前线程让出cpu抢占权,具有谦让之意,瞬时的动作,让出后,立即重新开始抢占。

------------------------------

示例

//使用yield()方法的示例

class ThreadDemo2{

public static void main(String [] args){

//创建2个线程对象,直接传参

MyThread t1 = new MyThread("Thread-111");

MyThread t2 = new MyThread("Thread-222");

//线程对象中run方法是由CPU调用的,我们需要调的方法是start方法

//这里的t1.start和t2.start()是通知CPU,你可以去调线程了。不要纠结先输出哪个

t1.start();

t2.start();

}

}

//线程1

class MyThread extends Thread{

private String name ;

public MyThread (String name){

this.name = name ;

}

public void run(){

for (; ; ){

System.out.println(name);

//yield  放弃CPU抢占权,但让的只是很短暂的时间

Thread.yield();

}        

}

}

-------------------------------

2.Thread.sleep()

  让当前线程休眠指定的毫秒数.

  释放cpu抢占权,和锁旗标的监控权没有关系。

 

3.Thread.join()

  当前线程等待指定的线程结束后才能继续运行。(让另外一个线程加入到我当前线程里来,只要调用join代码,后面代码必须等待join执行完后,才开始执行)

  Thread t = ...

  t.join();

  ....

---------------------------------------------------

 

23示例

/*使用sleep()方法和join()方法的示例,一个Boss找四个人打麻将,Boss作为麻将馆老板其作为主函数main,玩家为Player类

*/

class ThreadDemo3{

public static void main(String [] args){

//建立四个玩家对象,直接传参,代入玩家姓名和时间

Player p1 = new Player("甲", 1000);

Player p2 = new Player("乙", 2000);

Player p3 = new Player("丙", 2500);

Player p4 = new Player("丁", 3000);

//调用start()方法,开启线程

p2.start();

p4.start();

p1.start();

p3.start();

//调入join()方法,等待所有玩家都完成。join()方法也会报异常,需使用try方法包上

try{

p4.join();

p2.join();

p1.join();        

p3.join();

}

catch(Exception e){

}

//因为在所有人就绪之前,是不能开局的,所以输出“开局”这段内容,是在所有人都就绪后才可以。所以前面给四个对象都调用了join方法

System.out.println("人到齐,开局!");

}

}

//玩家类

class Player extends Thread{

private String name ;

//这个tmie变量表示从出发到麻将馆的时候为time时间

private int time ;

public Player (String name, int time){

this.name = name ;

this.time = time ;

}

//run()方法通过start()方法调用

public void run(){

System.out.println(name + "出发了!");

try{

//让当前线程休眠多长时间,时间单位为毫秒,但个方法是有异常的,需要抛出异常,这个异常不是运行时异常,只能用try 加 catch操作。用try将sleep方法包起来。

Thread.sleep(time);

}

//catch内有没有不用管

catch ( Exception e ) {

}

 

System.out.println(name + "到了! 耗时" + time);

}

}

-----------------------------------------------------

4.daemon

守护线程,作用服务员。

为其他线程提供服务的线程。

若进程中剩余的线程都是守护线程的话,则进程就终止了。

Thread.setDaemon(true); //调用daemo线程

-------------------------------------------------------------

4 示例

/*

演示守护线进程daemon

*/

class ThreadDemo4{

public static void main(String [] args){

//创三个对象,两个包房,一个服务员

BoxFang no1 = new BoxFang("01号",3000);

BoxFang no2 = new BoxFang("02号",7000);

Waiter w = new Waiter();

//设置服务员线程为守护线程

w.setDaemon(true);

no1.start();

no2.start();

w.start();

}

}

//定义一个包房类,继承Thread线程类

class BoxFang extends Thread{

//包房编号

private String no ;

//包房消费时间

private int time ;

public BoxFang (String no, int time){

this.no = no ;

this.time = time ;

}

public void run(){

System.out.println( no + "号包房开始消费。");

try{

//包房消费的时间

Thread.sleep(time);

System.out.println(no + "号包房的消费时间" + time);

}

//catch内有没有不用管

catch ( Exception e ) {

}

System.out.println(no + "号包房的消费时间:" + time  + ",结束消费。");

}

}

 

//定义一个服务员类 Waiter  演示d

class Waiter extends Thread{

public void run(){

//死循环,保证进程一直存在,每隔一秒打印一次系统时间

while(true){

//打印当前系统时间

System.out.println(new java.util.Date());

try{

//打印当前时间后,休眠1秒(1000毫秒)

Thread.sleep(1000);

}

catch(Exception e ){

}

}

}

}

------------------------------------------------------------

5.--操作 (就是两个连续的减号)

原子性操作。

 

6.线程间通信,共享资源的问题。

加锁,防止并发访问。由并行改为串行。

(加锁指的就是一个参照物的概念或叫锁旗标)。

//同步代码块

synchronized(){

...

}

同步代码块执行期间,线程始终持有对象的监控权,其他线程处于阻塞状态。

注:多线程是为了并发执行提升效率,但由于有些工作是必须串行执行的,例如示例中的卖票是不可以卖重复的,所以,只将有特殊需求的代码放入同步代码块中

-------------------------------------

5.6  示例:

/*

2个售票员共同卖100张票,演示线程间通信,共享资源的问题

每个售票员是一个线程,这个演示没有卖重票的原因是因为tickets-- 这个原子性操作。(这里不论有几个线程,所有对象都需要做tickets--的减减操作,因为减减操作是原子性的,只有当前线程执行完后,另一个线程才可以执行,存在这样的情况。

但往往在实际开发中并发执行资源访问情况是很复杂的,并不是一个减减操作就可以实现的,这样就需要有一个过程,但一旦涉及到过程,就不是原子性的了,这样就容易出现卖重票的可能。)

*/

class ThreadDemo5{

public static void main(String [] args){

//创建两个售票员对象

Saler s1 = new Saler("1号");

Saler s2 = new Saler("2号");

//Saler s3 = new Saler("3号");

s1.start();

s2.start();

//s3.start();

}

}

 

//定义售票员类 继承 Thread线程类

class Saler extends Thread {

//因为要个售票员共同卖100张票,就存在一个共享的问题,静态变量跟对象无关,跟类有关,就相当于一个全局变量,但在JAVA中不存在全局变量,在这里就设置一个类上的静态变量。

static int tickets = 100 ;

//锁旗标,在同步代码块中使用,这个对象是作为锁出现的,所有售票员都要看这个对象来抢售票的权限,要保证所有对象看同一个成员变量的锁定状态,所以这个锁必须是静态变量

static Object lock = new Object();

//售票员的编号

private String num ;

//创建构造函数

public Saler( String num ){

this.num = num ;

}

//卖票操用

public void run(){

/*

当tickets票,小于等于0时,表示票售空

while( tickets > 0){

int temp = tickets ;

//使用减减操作情况下

//System.out.println(num + "售票员出售了" + (tickets--) + "号票。");

System.out.println(num + "售票员出售了" + temp + "号票。");

tickets = tickets - 1 ;

*/

while( true){

int t = getTickets();

//票号小于1 表示票售空,停止

if ( t == -1){

return ;

}else{

System.out.println(num + "售票员出售了" + t + "号票。");

 

}

}

}

//建立一个取票的方法,改造run()方法中的while循环中的取消方式

public int getTickets(){

/*同步代码块,需要对同一个对象加锁,锁是无所谓的,在前面加一个Object对象就叫lock

加同步代码块后,大括号中的内容,同一时刻只能有一个线程访问。*/

synchronized(lock){

//保持当前的票号

int temp = tickets ;

try{

Thread.sleep(500);

}

catch(Exception e){

}

tickets = tickets - 1 ;  //票数减1

return temp < 1 ? -1 : temp ;  //返回票号

}

}

}

---------------------------------------

 

7.同步方法是以当前所在对象做锁旗标。

synchronized(this) === 同步方法。

---------------------------------------------------

7 示例  体验票池与程序代码块加锁  在方法定义首行使用synchronized关键加锁的使用

/*

这里售票员出售的票都来自TicketPool票池中

*/

class ThreadDemo7{

public static void main(String [] args){

TicketPool tp = new TicketPool();

Saler s1 = new Saler("S1",tp);

Saler s2 = new Saler("S2",tp);

Saler s3 = new Saler("S3",tp);

Saler s4 = new Saler("S4",tp);

s1.start();

s2.start();

s3.start();

s4.start();

}

}

 

//定义售票员类 继承 Thread线程类

class Saler extends Thread {

//售票员名字

private String name ;

//定义票池变量;

private TicketPool pool ;

//构造函数

public Saler(String name , TicketPool pool){

this.name = name ;

this.pool = pool ;

}

 

public void run(){

//死循环,保证一直卖票

while(true){

int no = pool.getTicket();

if ( no == 0 ){

return ;

}else{

System.out.println(name + "售票员出售了" + no + "号票。");

//谦让,这段代码可以放在同步代码块中,但不是必须串行的最好不放在同步代码块中,那样会影响性能。

Thread.yield();

}

}

}

}

 

//票池类

class TicketPool{

//总票数

private int tickets = 9 ;

 

//取票方法,使用synchronized()保证串行取票

/*另一种同步方式,就是将synchronized关键字放在方法定义中,不采用代码块的形式,这样synchronized将类所创建的对象作为锁旗标*/

public  synchronized int getTicket(){

//这里的this作为锁,这个this指的是TicketPool票池,synchronized所包含的就是同步代码块,以票池类自身作为锁旗标。

 

//synchronized(this){

int temp = tickets ;

tickets = tickets - 1 ;

return temp > 0 ? temp : 0 ;

//}

}

}

--------------------------------------------------

 

8.同步静态方法,使用类作为同步标记。

public static synchronized xxxx(...){

...

}

java -Xmx10m ThreadDemo9

--------------------------------

8 示例同7  

由于采用静态方法,那所使用的票的数量也要转为静态的,因为静态方法只能访问静态变量,由于是静态了,所以对象使用时也不需要声明创建对象了,直接通过类名就可以访问了。

/*

这里售票员出售的票都来自TicketPool票池中

*/

class ThreadDemo8{

public static void main(String [] args){

Saler s1 = new Saler("S1");

Saler s2 = new Saler("S2");

Saler s3 = new Saler("S3");

s1.start();

s2.start();

s3.start();

}

}

//定义售票员类 继承 Thread线程类

class Saler extends Thread {

//售票员名字

private String name ;

//构造函数

public Saler(String name ){

this.name = name ;

}

public void run(){

//死循环,保证一直卖票

while(true){

int no = TicketPool.getTicket();

if ( no == 0 ){

return ;

}else{

System.out.println(name + "售票员出售了" + no + "号票。");

Thread.yield();

}

}

}

}

//票池类

class TicketPool{

//总票数  方法为静态,成员变量也必须为静态

private static int tickets = 9 ;

//取票方法,使用synchronized()保证串行取票

/*

静态方法,以类作锁旗标

*/

public  synchronized static int getTicket(){

int temp = tickets ;

tickets = tickets - 1 ;

return temp > 0 ? temp : 0 ;

}

}

 

--------------------------------

9.wait

只有在对象拥有锁旗标的监控权下才可以调用这个方法。(这个方法只能在同步代码块里面调用。)

让当前线程进入锁旗标的等待队列。释放cpu抢占权,还释放锁旗标的监控权。等到有对象调用object.notify()方法时,通知等待对列中的其中一个线程,你们可以唤醒了,被唤醒的线程是随机的,让被唤醒的线程有抢占CPU的和锁旗标的监控权。

 

 

10.解决死锁的问题。

  notify() 只能知一个线程可以抢占cpu和锁旗标监控权

   为解决死锁问题,改用 notifyAll()

    notifyAll()通知所有线程可以抢占cpu和锁旗标监控权。

   wait(1000);可以指定一个固定时间,时间一到立即唤醒自己不在等 了。

 

------------------------------------

9  10  示例:

/*

生产者与消费者,生产者产出放集合里放,消费者从对象里往外取

当生产过快,导致内存溢出,解决方式就是给集合设置一个最大值,不让其无限制增加,当生产在给集合增加元素时,如果发现集合已达最大值,停止增加进行等待消费者将元素消费掉,消费者取元素时如果元素个数为0,也进行等待生产者向集合增加元素。这个集合最好是作为中间的一个对象,就像前面示例中的票池一样,

*/

class ThreadDemo10{

public static void main(String [] args){

//选创建一个集合,暂是先不用管,记住它就是一个可以变化的容器。

Pool pool = new Pool();

//生产者和消费者用的是同一个集合

Productor p1 = new Productor("生产者1" , pool);

//Productor p2 = new Productor("生产者2" , pool);

Consumer c1 = new Consumer("消费者1",pool);

Consumer c2 = new Consumer("消费者2",pool);

p1.start();

    //p2.start();

c1.start();

c2.start();

 

}

}

 

//生产者类,继承线程类  说明这个类是一个线程

class Productor extends Thread{

//生产者的名字

private String name;

static int i = 0 ;

//还需要一个存放对象的地方,创建一个集合

private Pool pool ;

//创建带参构造方法,传入名字和集合对象

public Productor(String name , Pool pool){

this.name = name ;

this.pool = pool ;

}

//作为一个线程,它应该有run方法,生产者在不断向里面产生数字

public void run(){

//int i = 0 ;

while(true){

//每次实例化一个对象i+1,向集合中添加。

pool.add(i ++); 

try{

Thread.sleep(1000);

}catch(Exception e){

e.printStackTrace();

}

/*运行时会发现,生产的速度比消费的速度快,很快就把栈区占满了,就会造成溢出,下面要采用通知者模式(我生产一个就通知,快取走,再知道我,我再生产,这就是典形的生产消费关系),这时需要给集合设一个最大值*/

System.out.println(name + "填加 " + i + "  个");

}

}

}

 

//消费者

class Consumer extends Thread{

//消费者的名字

private String name;

//使用Pool类创建一个集合变量

private Pool pool ;

//创建带参构造方法,传入名字和集合对象

public Consumer(String name , Pool pool){

this.name = name ;

this.pool = pool ;

}

//消费者从集合中不断的取出对象

public void run(){

while(true){

int i = pool.remove();

try{

//Thread.sleep(50);

}

catch(Exception e ){

e.printStackTrace();

}

System.out.println(name + "~~~取出了~~~~~~:" + i );

}

}

}

 

//增加一个对象池类,就像票池

class Pool{

private java.util.List<Integer> list = new java.util.ArrayList<Integer>();

//容器的最大值(这个对象池最多可以放100个)

private int MAX = 1 ;

//增加两个方法,一个是增加元素的方法,

public void add( int n ){

//同步代码块,锁旗标为自己,说明同一时刻只能有一个线程调用这个方法。

synchronized (this){

//让当前线程进入锁旗标的等待队列。释放cpu抢占权,还释放锁旗标的监控权。

try{

//判断集合的元素数量是否大于最大值,进行等待

while (list.size() > MAX){

this.wait();

}

list.add( n ) ;

System.out.println("list.size大小" + list.size());

this.notify();

}catch(Exception e){

//出现异常打印栈跟踪信息

e.printStackTrace();

}

}

}

//一个是删除元素的方法

public int remove(){

synchronized (this){

try{

while(list.size() == 0 ){

this.wait();

}

int i = list.remove(0);

this.notify();

return i ;

}

catch(Exception e){

e.printStackTrace();

}

return -1 ;

}

}

}

-------------------------------------

 

多线程演示:

————————————————

示例1

class ThreadDemo1{

public static void main(String [] args){

//创建线程对象

MyThread t1 = new MyThread();

YourThread t2 = new YourThread();

//线程对象中run方法是由CPU调用的,我们需要调的方法是start方法

//这里的t1.start和t2.start()是通知CPU,你可以去调线程了。不要纠结先输出哪个

t1.start();

t2.start();

}

}

//线程1

class MyThread extends Thread{

public void run(){

for (; ; ){

System.out.println("第1个线程");

}        

}

}

//线程2

class YourThread extends Thread{

public void run(){

for (; ; ){

System.out.println("第2个线程");

}

}

}

------------------------------------------

示例2

 

--------------------------------------------

集合:简要了解 之后再讲

List列表,可变长的

 

————————————————

作业

--------------

1.一共100个馒头,40个工人,每个工人最多能吃3个馒头。使用多线程输出所有工人吃馒头的情况。

 

2.5辆汽车过隧道,隧道一次只能通过一辆汽车,每辆汽车通过时间不固定,

  机动车通过时间3秒,

  三轮车通过时间5秒,

  畜力车通过时间10秒,

  5辆车分别是2辆机动车,2辆畜力车,1辆三轮车。

  通过多线程模拟通过隧道的情况。

  提示:Car ThreeCar CowCar

 

3.用多线程模拟蜜蜂和熊的关系。

   蜜蜂是生产者,熊是消费者。蜜蜂生产蜂蜜是累加的过程,熊吃蜂蜜是批量(满20吃掉)的过程。

   生产者和消费者之间使用通知方式告知对方。注意不能出现死锁的现象。

100只蜜蜂,每次生产的蜂蜜是1.

熊吃蜂蜜是20(批量的情况)。