应用程序

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

进程

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

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

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

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

 

多线程

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

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

2.线程之间共享内存。

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

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

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

 

java.lang.Thread   ( Thread 线程类)

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

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

1.Thread.yield()方法

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

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

1  示例

//使用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();

  ....

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

 

2,3示例

/*使用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(批量的情况)。



转载于:https://blog.51cto.com/adairh/1873869