本文已参加「新人创造礼」活动,一起敞开创造之路。

  上一节中咱们已经介绍了如何用承继Thread类的办法来创立线程。可是Java中具有单承继的约束,一旦承继了Thread类以后就无法承继其他类了,这样就存在隐性的约束。所以一般来说,更推荐用完结接口的办法来创立多线程。
  Thread类本身承继了Runnable接口,咱们需要重写的run办法正是来自于Runnable接口。一起,Thread类中还提供了一种结构办法,它需要的参数便是一个完结了Runnable接口的类。由以上条件,新的创立线程的办法就很明显了,咱们对上一节结尾的demo稍作改造:

//除了将承继改为完结接口,这部分代码没有任何差异
class threadTest implements Runnable{
    String name;
    threadTest(String name){
        this.name=name;
    }
    @Override
    public void run() {
        for(int i=0;i<500;++i){
        }
        System.out.println(name+" finished!");
    }
}
public class LearnThread {
    public static void main(String[] args) {
        threadTest t1=new threadTest("线程1");
        threadTest t2=new threadTest("线程2");
        threadTest t3=new threadTest("线程3");
        //就像最初提到的,传入一个完结了Runnable接口的类,再调用Thread中的start办法
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}

  你能够将其与上一节结尾的代码进行对比,相信你能够很好的了解这两种创立线程的办法的共通之处。
  现在考虑一个问题,咱们有一个完结了Runnable接口的类A,它能够传递进Thread类中来敞开一个新线程,那么是不是也能够把A传递进多个Thread类中来敞开好几个线程?答案是必定的。Thread的结构函数允许你在传递进一个完结了Runnable接口的类之后再加上一个字符串,来表示这个线程的姓名,至于传进去的类(比方A)是否在被其他线程使用并不做约束。将上面的代码稍加改造如下:

class threadTest implements Runnable{
    private static int i=50;
    @Override
    public void run() {
        while(i>0){
        //这儿的Thread.currentThread.getName办法能够拿到正在履行的线程的姓名
            System.out.println(Thread.currentThread().getName()+" 拿到了 "+i);
            i--;
        }
    }
}
public class LearnThread {
    public static void main(String[] args) {
        threadTest t1=new threadTest();
        new Thread(t1,"线程1").start();//咱们为t1一起敞开了三个线程!
        new Thread(t1,"线程2").start();
        new Thread(t1,"线程3").start();
    }
}

  看看我的履行成果(部分),你会发现一些意料之外的状况

线程1 拿到了 50
线程3 拿到了 50
线程3 拿到了 48
线程3 拿到了 47
线程3 拿到了 46
线程3 拿到了 45
线程3 拿到了 44
线程3 拿到了 43
线程2 拿到了 50
线程3 拿到了 42
线程1 拿到了 49

  你能够发现,50被三个线程分别都拿到了一次,可是在咱们的run办法中,每次i被拿到都应该要减1,这实在是一种奇怪的状况。这实际上被叫做并发问题,下面我会关于它做具体的解说。
  当多个线程拜访同一个目标,并且其间某些线程试图去修正该目标时,就需要完结线程同步。线程同步的条件是行列+锁。这两个概念实际上在现实生活中也被广泛地运用着。想象一下你在自主取款机取款的场景,人们在一台取款机前排队,你加入到部队,当部队轮到你时,你能够进入到斗室间里,把门锁上,然后安全地进行你的取款过程。当你取完款后,把门翻开,这样后一个人就能够进来处理他的事务。
  计算机中的线程不懂所谓的礼节,线程们不会自发地排队。可是咱们能够把它们都希望得到的共享目标加上锁,只有当时一个线程完毕对该目标的使用后(其余所有线程都在堵塞行列中等待着),才能够有另一个线程获得操纵该共享目标的资格(究竟是哪个线程胜出呢?这还涉及到线程间的竞争问题)。假如没有锁(取款机斗室间的门)的存在,那么排着队的线程们会试图一窝蜂地一起挤到斗室间里去,就像上面的demo中表现的那样。三个线程几乎一起挤进了斗室间,而且都拿到了50这个数字(由于还没有任何一个线程来得及把i修正为49)。
  接下来我会介绍一种java中常见加锁的办法,即使用synchronized关键字,它有两种使用办法。
  1.在某个办法的润饰符上加synchronized,这样相当于给这个办法所在的目标,即this加锁。
来看看这个demo

public class LearnThread implements Runnable {
    private static int count=0;
    @Override
    public synchronized void run() {
            for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + " get " + (count++));
            }
    }
    public static void main(String[] args) {
        LearnThread t1 = new LearnThread();
        new Thread(t1, "线程1").start();
        new Thread(t1, "线程2").start();
    }
}

  咱们在LearnThread类的run办法上加了synchronized关键字,现在这个类被锁住了(有了一个斗室间把它关在里面)。咱们的三个线程只能老老实实地一个一个进入房间来操作这个加了锁的目标。下面是程序输出:

线程1 get 0
线程1 get 1
线程1 get 2
线程1 get 3
线程1 get 4
线程2 get 5
线程2 get 6
线程2 get 7
线程2 get 8
线程2 get 9

  线程1率先拿到了锁,等它履行完自己的使命后,锁才被交给了线程2。假如没有synchronized关键字,那么成果将是紊乱的,你能够试试看以加深印象。

  2.用schronized润饰一个代码块,其他线程只有在拜访被润饰的代码块时才会被堵塞,你能够运转这段代码来看到这样润饰的不同之处

public class LearnThread implements Runnable {
    private static int count=0;
    private static int count2=0;
    @Override
    public  void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 不堵塞的 " + (count2++));
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized (this) {//小括号里一般放this或者你要锁的类的名称
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " 堵塞的 " + (count++));
            }
        }
    }
    public static void main(String[] args) {
        LearnThread t1 = new LearnThread();
        new Thread(t1, "线程1").start();
        new Thread(t1, "线程2").start();
    }
}

我的运转成果如下

线程2 不堵塞的 1
线程1 不堵塞的 0
线程1 不堵塞的 2
线程2 不堵塞的 3
线程1 不堵塞的 4
线程2 不堵塞的 5
线程1 不堵塞的 6
线程2 不堵塞的 7
线程2 不堵塞的 8
线程1 不堵塞的 9
线程2 堵塞的 0
线程2 堵塞的 1
线程2 堵塞的 2
线程2 堵塞的 3
线程2 堵塞的 4
线程1 堵塞的 5
线程1 堵塞的 6
线程1 堵塞的 7
线程1 堵塞的 8
线程1 堵塞的 9

  能够看到,没有被syncronized润饰的代码块的拜访是紊乱的,由于两个线程能够一起拜访到这个部分。而被组塞的部分,线程必须一个接一个地完结。