概念

  • 进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
  • 线程是进程中的一个执行单元(执行路径),负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
  • 多线程就是一个程序中有多个线程在同时执行,一个核心的CPU在多个线程之间进行着随即切换动作,由于切换时间很短(毫秒甚至是纳秒级别),导致我们感觉不出来
  • 单线程程序即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如去网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网。
  • 多线程程序即,若有多个任务可以同时执行。如,去网吧上网,网吧能够让多个人同时上网。

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

线程的运行模式

  • 分时调度

  • 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

  • 大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

  • 实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

线程操作共享数据的安全问题

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

访问同一个数据时出现意外的结果

比如商城秒杀活动,如果不做任何的代码改进,就很容易出现商品超卖的情况,之前php在解决这个问题的时候使用了redis解决这个问题,java有专门处理这方面问题的代码。

public class Threadtest implements Runnable{
  //定义出售的票源
  private int ticket = 100;
  public void run(){
    while(true){
      //对票数判断,大于0,可以出售,变量--操作
      if( ticket > 0){
        try{Thread.sleep(10);}catch(Exception ex){} //加了休眠让其他线程有执行机会
        System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
      }
    }
  }
}
  • 在测试代码中加入10毫秒的延迟,加大出现安全问题的几率,main方法中开启3个线程去运行

    public class Test {
      public static void main(String[] args) throws Exception {
          //创建Runnable接口实现类对象
          Threadtest t = new Threadtest();
          //创建3个Thread类对象,传递Runnable接口实现类
          Thread t0 = new Thread(t);
          Thread t1 = new Thread(t);
          Thread t2 = new Thread(t);
    
          t0.start();
          t1.start();
          t2.start();
      }
    }

解决多线程的安全问题

// 同步代码块: 在代码块声明上 加上synchronized
synchronized (Object obj) {
  // 可能会产生线程安全问题的代码
}
  • 同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

    public class Threadtest implements Runnable{
      //定义出售的票源
      private int ticket = 100;
      public Object obj = new Object();
      public void run(){
          while(true){
              synchronized(obj){
                  //对票数判断,大于0,可以出售,变量--操作
                  if( ticket > 0){
                      try{Thread.sleep(10);}catch(Exception ex){} //加了休眠让其他线程有执行机会
                      System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                  }
              }
          }
      }
    }
  • 在测试代码中加入synchronized(),保证只有1个线程进入,其他线程只能等之前的线程执行完成才能执行方法内的代码

    public class Test {
      public static void main(String[] args) throws Exception {
          //创建Runnable接口实现类对象
          Threadtest t = new Threadtest();
          //创建3个Thread类对象,传递Runnable接口实现类
          Thread t0 = new Thread(t);
          Thread t1 = new Thread(t);
          Thread t2 = new Thread(t);
    
          t0.start();
          t1.start();
          t2.start();
      }
    }

解决多线程的安全问题 同步方法

/*
 *  采用同步方法形式,解决线程的安全问题
 *  好处: 代码简洁
 *  将线程共享数据,和同步,抽取到一个方法中
 *  在方法的声明上,加入同步关键字
 *  
 *  问题:
 *    同步方法有锁吗,肯定有,同步方法中的对象锁,是本类对象引用 this
 *    如果方法是静态的呢,同步有锁吗,绝对不是this
 *    锁是本类自己.class 属性
 *    静态方法,同步锁,是本类类名.class属性
 */

public class Threadtest implements Runnable{
    //定义出售的票源
    private int ticket = 100;
    public void run(){
        while(true){
            payTicket();
        }
    }
    public synchronized void payTicket(){  
        if( ticket > 0){
            try{Thread.sleep(10);}catch(Exception ex){}
            System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
        }
    }
}
  • 在测试代码中加入synchronized(),保证只有1个线程进入,其他线程只能等之前的线程执行完成才能执行方法内的代码
public class Test {
    public static void main(String[] args) throws Exception {
        //创建Runnable接口实现类对象
        Threadtest t = new Threadtest();
        //创建3个Thread类对象,传递Runnable接口实现类
        Thread t0 = new Thread(t);
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t0.start();
        t1.start();
        t2.start();
    }
}

JDK1.5新特性Lock接口

Modifier and Type Method and Description
void lock()
获得锁。
void lockInterruptibly()
获取锁定,除非当前线程是 interrupted 。
Condition newCondition()
返回一个新Condition绑定到该实例Lock实例。
boolean tryLock()
只有在调用时才可以获得锁。
boolean tryLock(long time, TimeUnit unit)
如果在给定的等待时间内是空闲的,并且当前的线程尚未得到 interrupted,则获取该锁。
void unlock()
释放锁。
/*
 * 使用JDK1.5 的接口Lock,替换同步代码块,实现线程的安全性
 * Lock接口方法:
 *  lock() 获取锁
 *  unlock()释放锁
 * 实现类ReentrantLock
 */
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Threadtest implements Runnable{
    //定义出售的票源
    private int ticket = 100;
    //在类的成员位置,创建Lock接口的实现类对象
    private Lock lock = new ReentrantLock();
    public void run(){
        while(true){
            //调用Lock接口方法lock获取锁
            lock.lock();
            //对票数判断,大于0,可以出售,变量--操作
            if( ticket > 0){
                try{
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                }catch(Exception ex){

                }finally{
                    //释放锁,调用Lock接口方法unlock
                    lock.unlock();
                }
            }
        }
    }
}
  • 在判断前加入获得锁和结束后释放锁

    public class Test {
      public static void main(String[] args) throws Exception {
          //创建Runnable接口实现类对象
          Threadtest t = new Threadtest();
          //创建3个Thread类对象,传递Runnable接口实现类
          Thread t0 = new Thread(t);
          Thread t1 = new Thread(t);
          Thread t2 = new Thread(t);
    
          t0.start();
          t1.start();
          t2.start();
      }
    }

线程死锁

  • 当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
synchronzied(A锁){
    synchronized(B锁){

    }
}

死锁代码实现

  • a锁
public class LockA {
    private LockA(){}
    public static final LockA locka = new LockA();
}
  • b锁
public class LockB {
    private LockB(){}
    public static final LockB lockb = new LockB();
}
  • 死锁逻辑部分
public class DeadLock implements Runnable{
    private int i = 0;
    public void run(){
        while(true){
            if(i%2==0){
                //先进入A同步,再进入B同步
                synchronized(LockA.locka){
                    System.out.println("if...locka");
                    synchronized(LockB.lockb){
                        System.out.println("if...lockb");
                    }
                }
            }else{
                //先进入B同步,再进入A同步
                synchronized(LockB.lockb){
                    System.out.println("else...lockb");
                    synchronized(LockA.locka){
                        System.out.println("else...locka");
                    }
                }
            }
            i++;
        }
    }
}
  • main方法
public static void main(String[] args) throws Exception {
    //创建Runnable接口实现类对象
    Threadtest t = new Threadtest();
    //创建2个Thread类对象,传递Runnable接口实现类
    Thread t0 = new Thread(t);
    Thread t1 = new Thread(t);

    t0.start();
    t1.start();
}
  • 代码运行后不会停止,会因为死锁而一直等待另一个线程释放锁

线程等待与唤醒

  • wait():等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
  • notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
  • notifyAll():唤醒全部:可以将线程池中的所有wait()线程都唤醒。

其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

正常的多线程输入与输出

public class Resource {
    public String name;
    public String sex;
}
  • 输入部分
public class Input implements Runnable {
    Resource r;
    public Input(Resource r)
    {
        this.r = r;
    }
    public void run() {
        int i = 0;
        while(true)
        {
            if(i%2==0)
            {
                r.name="a";
                r.sex="b";
            }
            else
            {
                r.name="A";
                r.sex="B";
            }
            i++;
        }
    }
}
  • 输出部分
public class Output implements Runnable {
    Resource r;
    public Output(Resource r)
    {
        this.r = r;
    }
    public void run() {
        while(true)
        {
            System.out.println(r.name+"..."+r.sex);
        }
    }
}
  • main方法
public static void main(String[] args) throws Exception {
    Resource r = new Resource();

    Input i = new Input(r); 
    Output o = new Output(r); 

    Thread t0 = new Thread(i);
    Thread t1 = new Thread(o);

    t0.start();
    t1.start();
}
  • 因为多线程,所以控制台打印的结果是大小写夹杂的特别混乱

加入同步机制

public class Resource {
    public String name;
    public String sex;
}
  • 输入部分
public class Input implements Runnable {
    Resource r;
    public Input(Resource r)
    {
        this.r = r;
    }
    public void run() {
        int i = 0;
        while(true)
        {
            synchronized (r) {
                if(i%2==0)
                {
                    r.name="a";
                    r.sex="b";
                }
                else
                {
                    r.name="A";
                    r.sex="B";
                }
                i++;
            }
        }
    }
}
  • 输出部分
public class Output implements Runnable {
    Resource r;
    public Output(Resource r)
    {
        this.r = r;
    }
    public void run() {
        while(true)
        {
            synchronized (r) {
                System.out.println(r.name+"..."+r.sex);
            }
        }
    }
}
  • main方法
public static void main(String[] args) throws Exception {
    Resource r = new Resource();

    Input i = new Input(r); 
    Output o = new Output(r); 

    Thread t0 = new Thread(i);
    Thread t1 = new Thread(o);

    t0.start();
    t1.start();
}
  • 加入同步以后可以保证输出的字母同时为大写和小写,但是并不能保证是大小写相间

加入线程等待与线程唤醒

  • 输入:赋值后,执行方法wait()永远等待
  • 输出:变量值打印输出,在输出等待之前,唤醒
  • 输入的notify(),自己在wait()永远等待
  • 输入:被唤醒后,重新对变量赋值,赋值后,必须唤醒输出的线程notify(),自己的wait()
public class Resource {
    public String name;
    public String sex;
    public boolean flag = false;
}
  • 输入部分
public class Input implements Runnable {
    Resource r;
    public Input(Resource r)
    {
        this.r = r;
    }
    public void run() {
        int i = 0;
        while(true)
        {
            synchronized (r) {
                if(r.flag){
                    try{r.wait();}catch (Exception e) {}
                }
                if(i%2==0)
                {
                    r.name="a";
                    r.sex="b";
                }
                else
                {
                    r.name="A";
                    r.sex="B";
                }
                i++;
                r.flag=true;
                r.notify();
            }
        }
    }
}
  • 输出部分
public class Output implements Runnable {
    Resource r;
    public Output(Resource 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();
            }
        }
    }
}
  • main方法
public static void main(String[] args) throws Exception {
    Resource r = new Resource();

    Input i = new Input(r); 
    Output o = new Output(r); 

    Thread t0 = new Thread(i);
    Thread t1 = new Thread(o);

    t0.start();
    t1.start();
}
  • 加入线程等待和线程唤醒以后可以保证大小写字母对应和大小写相间输出