java 自学日志【九】---多线程

来源:互联网 发布:淘宝怎么截图发给别人 编辑:程序博客网 时间:2024/06/09 23:01
--------android培训,java培训,期待与您交流-------
多线程

进程:正在执行中的程序,由于cpu分时机制的作用,使每个进程都能循环获得自己的cpu时间片,但由于轮换速度非常快,使得所有程序看起来像是同时运行一样,线程,是进程内部单一的一个顺序控制流,因此一个进程可能容纳了多个同时执行的线程,进程也是一种封装。

线程:每个进程至少有一个线程,控制着进程的执行,jvm启动时就有一个进程java.exe,其中至少有一个线程负责java程序的执行,且这个线程的运行代码存在于主函数中,这个线程成为主线程。

多线程存在的意义:① 提高运行效率;② 建立反应灵敏的用户界面。

多线程的特性:随机性,多个线程交替执行,执行结果随机。

线程创建方式:① 继承Thread类 ② 实现 Runnable接口。

线程创建方式一:继承Thread类方式

创建步骤:① 定义类,继承Thread类;
   ② 复写Thread类中的run方法; 目的是将自定义的代码存储在run方法中,让线程执行;
   ③ 调用线程的 start方法,start方法作用有两个:一是启动线程,二是调用run方法。
为什么要复写run方法?
因为Thread类用于描述线程,定义了一个用于存储线程运行代码的功能,而这个功能就是run方法,所以子类要复写父类的run方法,将自定义的代码写入。
举例代码:
class Demo extends Thread//继承Thread类{public void run()//复写run方法{for(int x=0; x<60; x++)System.out.println("demo run----"+x);}}class ThreadDemo {public static void main(String[] args) {Demo d = new Demo();//创建好一个线程。d.start();//开启线程并执行该线程的run方法。//d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。}}
线程的运行状态:

线程有自己的默认名称:Thread-编号(从0开始编号)
获取线程名称方法:.getName()方法;
Thread.currentThread():获取当前线程对象,是静态的,直接调用;
设置线程名称方法:① .setName()方法 ② 通过构造函数 super(String name);

创建线程方式二:实现Runnable接口方法

创建步骤:① 定义类,实现Runnable接口;
   ②实现Runnable接口中的run方法,将线程要执行的代码存放在run方法中;
③ 通过Thread类 建立对象;
   ④ 将Runnable接口的子类作为实际参数传递给Thread类的构造函数;因为自定义对象的run方法所属对象是Runnable接口子类对象,所以要让线程去指定对象的run方法,必须明确run方法所属对象;
  ⑤调用Thread类中的start方法,启动线程并调用Runnable接口子类的run方法;
举例代码:
class Demo implements Runnable //实现Runnable接口{public void run()//复写run方法{for(int x=0; x<60; x++)System.out.println("demo run----"+x);}}class RunnableDemo {public static void main(String[] args) {Demo d = new Demo();//建立接口子类对象;Thread t = new Thread(d);//创建Thread类对象,并将接口子类对象传递给t;t.start();//调用start方法}}

两种创建线程方式有什么不同?
①实现接口方式避免了单继承的局限性,在定义线程时,建议使用实现接口方式;
②运行代码存放位置不同,继承方式线程代码存放在Thread子类的run方法中;而 实现接口方式的线程代码存放在接口子类的run方法中。

多线程的安全问题:
产生原因:当多条语句在操作同一线程共享数据时,一个线程的多条语句只执行了一部分,还没有执行完,当另一个线程参与进来执行,就会导致共享数据错误;
解决方法:对 多条操作共享数据语句,只能让一个线程执行完,而在执行过程中,其他线程不可以参与运行,即实现同步;

实现同步两种方式:①同步代码块;②同步函数

同步用关键字synchronized表示,只要同步,就有锁(监视器);

同步代码块:synchronized(任意对象) {需要被同步的代码};
原理:对象就如同锁,只有锁的线程可以在同步中执行,没有锁的线程,即使获取了cpu执行权,也进不去,不能执行;

同步的前提:① 必须有2个或2个以上的线程同时运行;
       ②多个线程必须使用同一个锁,保证同步中只有一个线程运行;

同步的利弊:①解决了多线程的安全问题;②多个线程需要判断锁,比较消耗资源;

如何找到需要同步的代码?
①明确哪些代码是多线程的运行代码;
②明确共享数据;
③明确多线程运行代码中 哪些语句是操作共享数据的,操作共享数据的代码即是要同步的代码;

举例代码:
class Ticket  implements Runnable  {      private  int tick = 1000;      Object obj = new Object();      public void run()      {          while(true)          {              synchronized(obj)//同步代码块 ,obj代表任意对象;            {                  if(tick>0)                  {                      try {Thread.sleep(10);} catch (Exception e){}                      System.out.println(Thread.currentThread().getName()+"....sale..."+tick--);                  }              }          }      }  }    class TicketDemo  {      public static void main(String[] args)       {          Ticket t = new Ticket();          Thread t0 = new Thread(t);          Thread t1 = new Thread(t);          Thread t2 = new Thread(t);          Thread t3 = new Thread(t);            t0.start();          t1.start();          t2.start();          t3.start();      }  }

同步函数:同步代码块可以抽取为同步函数,以简化书写;

同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。
所以同步函数使用的锁是this。
通过该程序进行验证:使用两个线程来买票。一个线程在同步代码块中。一个线程在同步函数中。
都在执行买票动作。
代码如下:
class Ticket implements Runnable{private  int tick = 100;Object obj = new Object();boolean flag = true;public  void run(){if(flag){while(true){synchronized(this){if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);}}}}elsewhile(true)show();}public synchronized void show()//this{if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);}}}class  ThisLockDemo{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try{Thread.sleep(10);}catch(Exception e){}t.flag = false;t2.start();}}

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不在是this。因为静态方法中也不可以定义this。

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class  该对象的类型是Class

静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class;
代码:
class Ticket implements Runnable{private static  int tick = 100;boolean flag = true;public  void run(){if(flag){while(true){synchronized(Ticket.class)//使用的所是 类名.class{if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);}}}}elsewhile(true)show();}public static synchronized void show()//被静态修饰后;{if(tick>0){try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);}}}class  StaticMethodDemo{public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try{Thread.sleep(10);}catch(Exception e){}t.flag = false;t2.start();}}

之前说过单例设计模式的懒汉式容易特点是延迟加载,当多线程访问时会出现安全问题,可以用同步来解决;
并采用双重判断解决低效问题;
代码如下:
class Single{private Single(){}private static Single s = null;/*这样比较低效public synchronized static Single getInstance(){if (s==null){s=new Single();}return s;}*/public static  Single getInstance(){if(s==null){synchronized(Single.class){if(s==null)s = new Single();}}return s;}}

同步的死锁问题:
死锁:2个或2个以上的进程在执行过程中,因为争夺资源而造成的一种互相等待的现象,若无外力作用,他们都无法推进下去,此时系统处于死锁状态,这些永远在互相等待的进程成为死锁进程。
产生原因:同步中嵌套同步,而锁不同;
举例代码:
class Test implements Runnable  {      private  boolean flag;Test(flag){this.flag = flag;}    public void run()      {          if(flag)          {  while(true)            {synchronized(Mylock.lock1){  System.out.println("if lock1");synchronized(Mylock.lock2){System.out.println("if lock2");}                }  }          }else{while(true)            {synchronized(Mylock.lock2){  System.out.println("else lock2");synchronized(Mylock.lock1){System.out.println("else lock1");}                }  }  }    }  } class MyLock{static Object lock1= new Object();static Object lock2= new Object();}class DeadLockDemo  {      public static void main(String[] args)       {          Thread t0 = new Thread(new Test(true));Thread t1 = new Thread(new Test(false));t0.start();t1.start();    }  }

线程间通信:
之前的代码多个线程对数据操作和处理都是一样的,如果多个线程对数据的操作不一样,那么就是线程间通信;如:2个线程做读取操作,2个线程做写入操作;

线程间通信的安全问题:对于一个共享资源,对其进行不同的操作的线程,需要一个共同的锁来同步,而一般共享资源对象来当这个锁;
举例代码:
class Res1  {      String name;      String sex;      boolean flag = true;  }  class Input1 implements Runnable  {      private Res1 r;      Input1(Res1 r)      {          this.r = r;      }      public void run()      {          int x = 0;          while(true)          {              synchronized(r) //共享对象 Res1 ,所以用它来当锁;            {                  if(r.flag)                      try{r.wait();}catch(Exception e){}                  if(x==0)                  {                      r.name = "mike";                      r.sex = "man";                  }                  else                  {                      r.name = "丽丽";                      r.sex = "女";                  }                  r.flag = true;                            r.notify();                   }              x =(x+1)%2;          }      }  }  class Output1 implements Runnable  {      private Res1 r;      Output1(Res1 r)      {          this.r = r;      }      public void run()      {          while(true)          {              synchronized(r)              {                  if(!r.flag)                      try{r.wait();}catch(Exception e){}                                System.out.println(r.name+"..."+r.sex);                  r.flag = false;  r.notify();                             }          }      }  }  class  InputOutputDemo  {      public static void main(String[] args)       {          Res1 s = new Res1();            Input1 in = new Input1(s);          Output1 out = new Output1(s);            Thread t1 = new Thread(in);          Thread t2 = new Thread(out);                    t1.start();          t2.start();      }  }  

等待唤醒机制:wait(); notify(); notifyAll(); 使得吸入和读取交替执行;
阻塞的线程都放在线程池中,notify唤醒的都是线程池中的线程,通常是线程池中的第一个线程,notifyAll唤醒的是线程池中所有的线程,三个方法都在同步中使用,因为要对持有锁的线程操作,只有同步才有锁,等待和唤醒必须是同一个锁,而锁可以是任意对象,而能被任意对象调用的方法定义在object类中。

生产和消费者示例:
class ProducerConsumerDemo {public static void main(String[] args) {Resource1 res = new Resource1();Producer1 pro = new Producer1(res);Consumer1 con = new Consumer1(res);Thread t0 = new Thread(pro);Thread t1 = new Thread(pro);Thread t2 = new Thread(con);Thread t3 = new Thread(con);t0.start();t1.start();t2.start();t3.start();}}class Resource1{private String name;private int count = 1;private boolean flag = false;public synchronized void set(String name){while(flag)//if(flag)try{wait();}catch(Exception e){}this.name = name + "--"+count++;System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);flag = true;this.notifyAll();}public synchronized void out(){while(!flag)//if(!flag)try{wait();}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);flag =false;this.notifyAll();}}class Producer1 implements Runnable{private Resource1 res;Producer1 (Resource1 res){this.res = res;}public void run(){while(true){res.set("商品");}}}class Consumer1 implements Runnable{private Resource1 res;Consumer1(Resource1 res){this.res = res;}public void run(){while(true){res.out();}}}

当有多生产和多消费时注意:
①把判断标记if改成while,因为如果同一组某一线程运行完毕后,标记值已经改变,如果此时同组另一线程获取执行资格,由于标记改变会导致连续生产或连续消费,使用while就是为了让线程在冻结唤醒获取执行权后,再判断一次标记flag;
②把notify换成notifyAll,以为notify只唤醒一个线程,如果唤醒的是同组线程,会导致所有组的线程都处于冻结状态,notifyall也会唤醒同组线程,现在想只唤醒对方线程,就要用到jdk1.5出现的新特性;将同步synchronized替换成Lock操作,将wait,notify,notifyAll替换成condition对象的相应操作,其中condition对象可以通过Lock获取,这是一种显示的锁机制和锁对象上的等待唤醒机制,把等待唤醒机制封装成了condition对象,让一个锁对应多个condition对象。
改动代码:
import java.util.concurrent.locks.*;class ProducerConsumerDemo2 {public static void main(String[] args) {Resource r = new Resource();Producer pro = new Producer(r);Consumer con = new Consumer(r);Thread t1 = new Thread(pro);Thread t2 = new Thread(pro);Thread t3 = new Thread(con);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}}class Resource{private String name;private int count = 1;private boolean flag = false;private Lock lock = new ReentrantLock();private Condition condition_pro = lock.newCondition();private Condition condition_con = lock.newCondition();public  void set(String name)throws InterruptedException{lock.lock();try{while(flag)condition_pro.await();this.name = name+"--"+count++;System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);flag = true;condition_con.signal();}finally{lock.unlock();//释放锁的动作一定要执行。}}public  void out()throws InterruptedException{lock.lock();try{while(!flag)condition_con.await();System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);flag = false;condition_pro.signal();}finally{lock.unlock();}}}class Producer implements Runnable{private Resource res;Producer(Resource res){this.res = res;}public void run(){while(true){try{res.set("+商品+");}catch (InterruptedException e){}}}}class Consumer implements Runnable{private Resource res;Consumer(Resource res){this.res = res;}public void run(){while(true){try{res.out();}catch (InterruptedException e){}}}}

停止线程:stop()方法已过时,如何停止线程呢?就是让run方法结束。
开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可实现;
特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束;
当没有指定的方法让冻结的线程恢复到运行状态时,只是需要对冻结的状态进行清除,强制让线程恢复到运行状态,这样就可以让线程结束,Thread类中提供了interrupt()方法来中断线程;

守护线程:也叫后台线程
特点:开启后和前台线程一起抢夺cpu资源,当所有前台线程都结束后,后台线程会自动结束,Thread类中,使用setDeamon()方法把一个线程标记为守护线程,需要在线程启动前设置;如果线程A依赖于线程B,可以在B中调用A的setDeamon(true)方法,把A设置为B的守护线程;

join方法:当A线程执行到B线程的join方法时,A就会等待,等待B线程都执行完,A才执行,用来临时加入线程执行;
yield:暂停当前正在执行的线程对象,并执行其他线程。 相当于放弃下执行权,不长期占用。
setPriority(int newPriority)更改线程的优先级。 (1到10级)

















原创粉丝点击