JAVA多线程新手指引

一、简述进程与线程的关系  
 
  说到多线程就不得不说进程,那么什么是进程:简单的来说就是正在进行中的程序。
在windows中,我们可以直观的看到的正在运行中的程序,即进程,如图:
 
那么什么又是线程呢?           
  线程其实就是进程的最小工作单位。是进程中一个负责程序执行的控制单元(执行路径), 一个进程中可以多执行路径,称之为多线程。
  一个进程成至少要有一个线程。
 在JAVA中开启多线程的目的是什么?
   就是为了同时运行多部分代码,提高工作效率。
   而每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
   其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的,即所有应用程序在运行的过程中都在争夺cpu的执行权,谁拿到这个执行权那么谁就执行相应的代码,执行自己要执行的任务。
 
在JVM启动时,就启动了多个线程,即便是最简单的代码,我们也能够直观的分析出两个线程
1,执行main函数的线程。
                该线程的任务代码都定义在main函数中
2,负责垃圾回收的线程。
 
二、创建线程
 
创建线程方式一:继承Thread类
步骤:
1,定义一个类继承Thread类
2,覆盖Thread类中的run方法。
 
      创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。
      而运行的指定代码就是这个执行路径的任务。
      jvm创建的主线程的任务都定义在了主函数中。
 而自定义的线程它的任务哪里呢?
        Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述。
        这个任务就通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程任务的函数。
 
        run方法中定义的就是线程要运行的任务代码。
 
        开启线程是为了制定代码,所以只有继承Thread类,并复写run方法。
        将运行的代码定义在run方法中即可。
 简单代码示例:
public class ExtendsThreadDemo {

    public static void main(String[] args) {
        Student s1 = new Student();
        Student s2 = new Student();
        s1.start();
        s2.start();
        
    }

}

class Student extends Thread{
    public void run(){
        while(true){
        System.out.println(Thread.currentThread().getName()+".....student study!!!");
    }
    }
}
3,直接创建Thread类的子类对象创建线程。
4,调用对象的start方法开启线程并调用线程的任务run方法执行。
 
补充:可以通过Thread的getName获取线程的名称 : Thread-编号(从0开始)主线程的名字就是main
 
 调用run和调用start有什么区别?
      调用run与调用一个方法是相同的,而调用start是调用一个线程来执行run方法。
 
 
创建线程的第二种方式:实现Runnable接口
 
1,定义类实现Runnable接口。
2,覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3,通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
    原因是线程的任务都封装在Runnable接口子类对象的run方法中。所以要再线程对象创建时就必须明确要运行的任务。
4,调用线程对象的start方法开启线程。
简单代码示例:
public class ImplementsRunnableDemo {

    public static void main(String[] args) {
        Student s1 = new Student();
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        t1.start();
        t2.start();
        
    }

}

class Student implements Runnable{
    public void run(){
        while(true){
        System.out.println(Thread.currentThread().getName()+".....student study!!!");
    }
    }
}
实现Runnable接口的好处:
    1,将线程的任务从线程的子类中分离出来,进行了单独的封装。
        按照面向对象的思想将任务封装成对象。
    2,避免了java单继承的局限性。
所以,创建线程的第二种方式较为常用。
 
线程安全问题产生的原因:
  1,多个线程在操作共享的数据。
  2,操作共享数据的线程代码有多条。
 
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
 
解决思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
 
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
    synchronized(对象){
        需要被同步的代码;
}
 
同步的好处:
    解决了线程的安全问题。
同步的弊端:
    相对降低了效率,因为同步外的线程都会判断同步锁。判断后还不能执行,相当于进行的是无效判断。
 
同步的前提:
        同步中必须有多个线程并使用同一个锁。
 
同步函数
格式:    public synchronized void 函数名( ){   }
注:同步函数的锁是this
 
同步函数和同步代码块的区别:
    同步函数的锁是固定的this
    同步代码块的锁是任意的对象
在开发中,建议使用同步代码快,同步函数可以作为同步代码快的简写形式。
 
静态的同步函数
格式:    public static synchronized void 函数名( ){   }
静态的同步函数使用的锁是该函数所属的字节码文件对象,可以用getClass方法获取,也可以用 当前类名.class 形式表示
 
三、多线程下的单例设计模式
 
饿汉式
    饿汉式的写法比较安全和高效,也是我们在开发过程中常用的写法。
代码示例:
class Single
{
    private static final Single s = new Single();//类加载的时候就创建了s,不会有争执和分歧
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }
}
懒汉式
原写法:
class Single
{
    private static Single s = null;
    private Single(){}
    public static Single getInstance()
       {
            if (s == null)
                s = new Single();

               return s;
        }
}

多线程写法:

class Single
{
    private static Single s = null;
    private Single(){}
    public static Single getInstance()
       {
        if(s == null){    //解决效率问题
            syncchronized(Single.class) {  //解决安全问题 
           if (s == null)
                s = new Single();
               return s;
        }
       }
}
}

四、死锁

 常见情景之一就是同步之间的嵌套。
示例代码如下:
class DeadLock
{
 public static void main(String[] args)
 {
  Demo d1 = new Demo(true); 
  Demo d2 = new Demo(false);
  Thread t1 = new Thread(d1);
  Thread t2 = new Thread(d2);
  t1.start();
  t2.start();
  System.out.println(Thread.currentThread().getName()+"........"+"over");
 }
}


class Demo implements Runnable
{
 private  boolean flag;
 Demo(boolean flag){
  this.flag = flag;
 }

 public void run()
 {
  if(flag)
  {
   while(true)
    {
   synchronized(MyLock.lock2){
    System.out.println("aaaaaa111111锁"+Thread.currentThread().getName());
    synchronized(MyLock.lock1){
    System.out.println("aaaaaa222222锁"+Thread.currentThread().getName());
    }
   }
   }
  }
  else{
   while(true)
    {
  synchronized(MyLock.lock1){
    System.out.println("bbbbbbb111111锁"+Thread.currentThread().getName());
    synchronized(MyLock.lock2){
    System.out.println("bbbbbbbb222222锁"+Thread.currentThread().getName());
    }
   }
   }
  }
 }
}

class MyLock
{
 public static final Object lock1 = new Object();
 public static final Object lock2 = new Object();
}

五、线程间的通讯:

        个人理解:多个线程在处理同一资源,但是任务却不同。
 
等待和唤醒机制
    涉及的方法:
        1,wait();   
                :让线程处于冻结状态,线程会释放cpu的执行权和执行资格,被wait的线程会被存储到线程池中。
        2.notify();
                :用于唤醒线程池中的一个线程(任意的,无顺序)。
        3,notifyAll();
                :用于唤醒线程池中的所有线程。让线程具备了执行资格,处于运行状态或线程阻塞状态。
 
这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。可以理解为以锁来标记线程池。
 
为什么操作线程的方法wait () notiry() notifyAll()定义在了Object类中,而没有定义在Thread类中?
        因为这些方法是监视器的方法,监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方法一定定义在Object类中。
 
等待唤醒机制的经典案例:生产者与消费者
单生产者与单消费者代码示例:
public class ProducerAndConsumer {

 public static void main(String[] args) {
  Goods g = new Goods();
  Producer p = new Producer(g);
  Consumer c = new Consumer(g);
  Thread t1 = new Thread(p);
  Thread t2 = new Thread(c);
  t1.start();
  t2.start();
 }

}
class Goods{
 private String name;
 private int count = 1;
 private boolean flag = false;
 public synchronized void set(String name){
  if(flag){
   try {
    this.wait();
   } catch (InterruptedException e) {}
  }
  this.name = name + count;
  count++;
  System.out.println(Thread.currentThread().getName()+"--生产--"+this.name);
  flag = true;
  notify();
 }
 
 public synchronized void out(){
  if(!flag){
   try {
    this.wait();
   } catch (InterruptedException e) {}
  }
  System.out.println(Thread.currentThread().getName()+"--消费--------"+this.name);
  flag = false;
  notify();
 }
}

class Producer implements Runnable{
 private Goods g;
 Producer(Goods g) {
  this.g = g;
 }
 public void run(){
  while (true) {
   g.set("馒头");
  }
 }
}

class Consumer implements Runnable{
 private Goods g;
 Consumer(Goods g) {
  this.g = g;
 }
 public void run(){
  while (true) {
   g.out();
  }
 }
}

多生产者与多消费者

  if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。while判断标记,解决了线程获取执行权后,是否要运行!
 
notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义,而且while判断标记+notify会导致死锁
notifyAll解决了本方线程一定会唤醒对方线程的问题。
 
代码示例一:
public class ProducerAndConsumer {

 public static void main(String[] args) {
  Goods g = new Goods();
  Producer p = new Producer(g);
  Consumer c = new Consumer(g);
  Thread t0 = new Thread(p);
  Thread t1 = new Thread(c);
  Thread t2 = new Thread(p);
  Thread t3 = new Thread(c);
  t0.start();
  t1.start();
  t2.start();
  t3.start();
 }

}
class Goods{
 private String name;
 private int count = 1;
 private boolean flag = false;
 public synchronized void set(String name){
  while(flag){
   try {
    this.wait();
   } catch (InterruptedException e) {}
  }
  this.name = name + count;
  count++;
  System.out.println(Thread.currentThread().getName()+"--生产--"+this.name);
  flag = true;
  notifyAll();
 }
 
 public synchronized void out(){
  while(!flag){
   try {
    this.wait();
   } catch (InterruptedException e) {}
  }
  System.out.println(Thread.currentThread().getName()+"--消费--------"+this.name);
  flag = false;
  notifyAll();
 }
}

class Producer implements Runnable{
 private Goods g;
 Producer(Goods g) {
  this.g = g;
 }
 public void run(){
  while (true) {
   g.set("馒头");
  }
 }
}

class Consumer implements Runnable{
 private Goods g;
 Consumer(Goods g) {
  this.g = g;
 }
 public void run(){
  while (true) {
   g.out();
  }
 }
}

jdk1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显式动作

Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式锁操作编程了显式锁操作,同时更为灵活,可以一个锁上加上多组监视器
 
        lock():获取锁
        unlock():释放锁,通常需要定义finally代码块中。
Condition接口:出现替代了Object中的wait / notify  / notifyAll 方法
                    将这些监视器方法单独进行了封装,变成了Condition监视器对象。
                    可以和任意锁进行组合。
await();
signal();
signalAll();
 
 
代码示例二:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerAndConsumer {

 public static void main(String[] args) {
  Goods g = new Goods();
  Producer p = new Producer(g);
  Consumer c = new Consumer(g);
  Thread t0 = new Thread(p);
  Thread t1 = new Thread(c);
  Thread t2 = new Thread(p);
  Thread t3 = new Thread(c);
  t0.start();
  t1.start();
  t2.start();
  t3.start();
 }

}
class Goods{
 private String name;
 private int count = 1;
 private boolean flag = false;
 
 Lock lock = new ReentrantLock();
 
 Condition c1 = lock.newCondition();
 Condition c2 = lock.newCondition();
 
 public  void set(String name){
  lock.lock();
  try{
  while(flag){
   try{c1.await();
   } catch (InterruptedException e) {}
  }
  this.name = name + count;
  count++;
  System.out.println(Thread.currentThread().getName()+"--生产--"+this.name);
  flag = true;
  c2.signal();
 }finally{
  lock.unlock();
 }
 }
 
 public  void out(){
  lock.lock();
  try{
  while(!flag){
   try {c2.await();
   } catch (InterruptedException e) {}
  }
  System.out.println(Thread.currentThread().getName()+"--消费--------"+this.name);
  flag = false;
  c1.signal();

  }
  finally{
   lock.unlock();
  }
 }
}

class Producer implements Runnable{
 private Goods g;
 Producer(Goods g) {
  this.g = g;
 }
 public void run(){
  while (true) {
   g.set("馒头");
  }
 }
}

class Consumer implements Runnable{
 private Goods g;
 Consumer(Goods g) {
  this.g = g;
 }
 public void run(){
  while (true) {
   g.out();
  }
 }
}

补充:


wait和sleep的区别:
    1,wait可以指定时间也可以不指定。
        sleep必须指定时间。
    2,在同步中时,对cpu的执行权和锁的处理不同。
        wait:释放执行权,释放锁。
        sleep:释放执行权,不释放锁。
 
停止线程方法:
    run方法结束。(stop等方法已过时,且不安全)
那么如何控制线程的任务结束?
    任务中都会有循环结构,只要控制住循环就可以结束任务。
 
控制循环通常就用定义标记来完成,但是如果线程处于了冻结状态,无法读取标记,那么就无法结束任务。此时可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格,但是强制动作会发生InterruptException,要进行处理。
 
posted @ 2014-12-13 16:58  taylor*longhua  阅读(251)  评论(0编辑  收藏  举报