语法:多线程


面试中被问的最多的问题之一就是Java的并发与多线程了,一直想花点时间好好总结一下,今天终于下笔。


一、几个概念:

进程:简而言之就是进行中的程序
线程:是“进程”中某个单一顺序的控制流,是程序中一条独立的执行路径
单线程:整个进程中就只有一个单一的线程贯穿始终
多线程:一个进程中含多个线程,cpu在不同的线程中快速地切换执行

二、如何创建一个线程:
  1. 继承Thread类
    具体步骤:
    a. 继承Thread类
    b. 复写run()方法
    c. 用该类的对象.start()方法来创建一个线程并调用run()方法
    注意:不要直接调用run方法,如果直接调用该对象的run方法,跟普通的函数调用没有区别,不能达到开启多线程的目的

  2. 实现Runnable接口
    具体步骤:
    a. 实现Runnable接口
    b. 复写run()方法
    c. 以该类的对象为Thread类构造方法的参数建立一个Thread对象
    d. 调用start()方法

示例程序:


package wintervacation.multithreading;

/**
 * Created by wangw on 2016/3/2.
 * 演示创建多线程的两种方式:
 * a.继承Thread类
 * b.实现Runnable接口
 */
public class MultiThreadingDemo {
    public static void main(String[] args) {
        MyThread0 thread0 = new MyThread0();
        thread0.start();

        /*
        myThread1不能作为线程启动运行,必须包装在Thread对象thread1中
        但是thread1启动运行的线程是myThread1,执行的线程体也是myThread1的
         */
        MyThread1 myThread1 = new MyThread1();
        Thread thread1 = new Thread(myThread1);
        thread1.start();

        for(int i=0;i<500;i++) {
            System.out.println(Thread.currentThread().getName()+" is running-------"+i);
        }
        System.out.println(Thread.currentThread().getName()+"is over");

    }

    //方式1:继承Thread类
    static class MyThread0 extends Thread {
        @Override
        public void run() {
        for(int i=0;i<500;i++) {
                System.out.println(Thread.currentThread().getName()+" is running-------"+i);
            }
            System.out.println(Thread.currentThread().getName()+"is over");
        }
    }

    //方式2:实现Runnable接口
    static class MyThread1 implements Runnable {
        @Override
        public void run() {
            for(int i=0;i<500;i++) {
                System.out.println(Thread.currentThread().getName()+" is running-------"+i);
            }
                System.out.println(Thread.currentThread().getName()+"is over");
        }
    }

}

运行结果:
c1

二者的区别:

  • Java只允许单继承,第一种方式要求继承Thread类没有其他父类,所以第二种方式的使用范围更广
  • 对于多个具有相同程序代码的线程处理同一资源的情况,第二种方式对CPU(线程)、程序的代码和数据进行了有效的分离,较好的体现了面向对象的思想。
    请看下面两个程序:
// SellTicketSystem0.java 
package wintervacation.multithreading;

/**
 * Created by wangw on 2016/3/2.
 * 用继承Thread的方式模拟火车票售票系统,与实现Runnable接口的方式作比较
 * 假设四个系统同时出售某一车次的100张车票
 */
public class SellTicketSystem0 {
public static void main(String[] args) {
        MyThread myThread0 = new MyThread();
        myThread0.start();
        MyThread myThread1 = new MyThread();
        myThread1.start();
        MyThread myThread2 = new MyThread();
        myThread2.start();
        MyThread myThread3 = new MyThread();
        myThread3.start();

    }
}
class MyThread extends Thread {
    private int ticket = 100;
    @Override
    public void run() {
      while(ticket>0) {
              System.out.println(this.getName()+" sell out ticket" + ticket--);
      }
    }
}

运行结果:
c2
可以看到每一张票被每个线程都卖了一遍,原因是每个线程都有它自己的100张票,显然不是我们想要的
再看另一种方式:

// SellTicketSystem1.java 
package wintervacation.multithreading;

/**
 * Created by wangw on 2016/3/2.
 * 用实现Runnable接口的方式模拟火车票售票系统,与继承Thread的方式作比较
 * 假设四个系统同时出售某一车次的100张车票
 */
public class SellTicketSystem1 {
 public static void main(String[] args) {
        MyThread thread = new MyThread();
        Thread myThread0 = new Thread(thread);
        myThread0.start();
        Thread myThread1 = new Thread(thread);
        myThread1.start();
        Thread myThread2 = new Thread(thread);
        myThread2.start();
        Thread myThread3 = new Thread(thread);
        myThread3.start();
    }

 static class MyThread implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
      while(ticket>0) {
         System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
      }
    }
  }
}

运行结果:
c3
可以看到,所有的线程访问同一个变量,在不考虑同步的情况下很好的模拟了火车票售票系统。

三、线程状态的转换

线程的生命周期图:

Oracle官方给出的线程的状态分为四个:new, runnable, non-runnable 以及 terminated。
此处我们采用更容易接受的五状态生命周期图。说明如下:

  • New
    新建状态,线程的实例已经被创建但是start方法尚未被调用
  • Runnable
    当该线程的start方法被调用,但是还没有被CPU调度选中时,线程处于Runnable状态
  • Running
    CPU调度选中,获得运行权
  • Non-Runnable (Blocked)
    线程阻塞状态,线程仍然存活,但是由于某些原因,导致它的运行受阻暂时不能运行
  • Terminated
    当线程的run方法退出时,线程进入Teminated状态
四、线程的优先级和常用的方法
  • 优先级
    线程的优先级用1~10之间的一个整数表示,数值越大优先级越高,Thread类中定义了三个常量:
    最高优先级:Thread.MAX_PRIORITY————10
    最低优先级:Thread.MIN_PRIORITY————1
    默认优先级:Thread.NORM_PRIORITY———–5
  • 常用方法
    public void run(): is used to perform action for a thread.
    public void start(): starts the execution of the thread.JVM calls the run() method on the thread.
    public void sleep(long miliseconds): Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds.
    public void join(): waits for a thread to die.
    public void join(long miliseconds): waits for a thread to die for the specified miliseconds.
    public int getPriority(): returns the priority of the thread.
    public int setPriority(int priority): changes the priority of the thread.
    public String getName(): returns the name of the thread.
    public void setName(String name): changes the name of the thread.
    public Thread currentThread(): returns the reference of currently executing thread.
    public int getId(): returns the id of the thread.
    public Thread.State getState(): returns the state of the thread.
    public boolean isAlive(): tests if the thread is alive.
    public void yield(): causes the currently executing thread object to temporarily pause and allow other threads to execute.
    public void suspend(): is used to suspend the thread(depricated).
    public void resume(): is used to resume the suspended thread(depricated).
    public void stop(): is used to stop the thread(depricated).
    public boolean isDaemon(): tests if the thread is a daemon thread.
    public void setDaemon(boolean b): marks the thread as daemon or user thread.
    public void interrupt(): interrupts the thread.
    public boolean isInterrupted(): tests if the thread has been interrupted.
    public static boolean interrupted(): tests if the current thread has been interrupted.

示例程序:

// ThreadPriority.java
package wintervacation.multithreading;

/**
 * Created by wangw on 2016/3/2. 用于演示Thread的优先级,高优先级线程的执行优先于低优先级线程
 */
public class ThreadPriority {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("最高优先级:" + Thread.MAX_PRIORITY);
        System.out.println("最低优先级:" + Thread.MIN_PRIORITY);
        System.out.println("默认优先级:" + Thread.NORM_PRIORITY);

        MyThread thread0 = new MyThread();
        thread0.setPriority(Thread.MAX_PRIORITY);
        thread0.start();

        MyThread thread1 = new MyThread();
        thread1.setPriority(Thread.MIN_PRIORITY);
        thread1.start();

        MyThread thread2 = new MyThread();
        thread2.setPriority(Thread.NORM_PRIORITY);
        thread2.start();

        // join方法:当前线程执行某一线程的join方法之后,当前线程等到该线程执行结束之后才能执行
        // thread1.join();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + " is running-------" + i);
        }
        System.out.println(Thread.currentThread().getName() + "is over");

    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName() + " is running-------" + i);
            大专栏  语法:多线程ss="token punctuation">}
            System.out.println(Thread.currentThread().getName() + "is over");
        }
    }
}
五、同步问题

临界资源:同一时刻只允许一个线程访问的资源
临界区:处理临界资源的代码
涉及到临界资源的问题需要用到同步机制,否则可能会出现错误或会导致不安全,上面的卖票程序就存在这样的问题,为了使效果更加明显,我们在单个线程卖票之前让它睡眠100ms:

static class MyThread implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
        }
    }
}

运行结果:
result
我们看到Thread-0,Thread-2和Thread-1分别卖出了代号为0,-1,-2的票,这显然是错误的
原因是线程3检测到ticket=1时,进入睡眠,此时线程0检测ticket仍然为1,也进入临界区,睡眠,同理,线程2,1依次进入临界区,睡眠。
100ms后,Thread-3恢复运行,卖出标号为1的票,ticket减一变为0,线程0,2,1依次恢复运行并执行卖票和ticket–的动作,便出现了图示的结果。
为了保护共享数据的完整性,JAVA语言引入了互斥锁的概念。
互斥锁:Java中的每一个对象都有且仅有一个互斥锁,对于加锁的临界区,只有拿到该锁的线程可以访问,Java使用synchronized关键字给对象家锁。
同步方法:使用synchronized修饰的方法,开发者不用指定加锁对象,JVM默认给this对象加锁
同步代码块:使用synchronized修饰的代码块,需要指定加锁对象
改进后的火车票售票系统:
<方式1>

static class MyThread implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized ("haha") {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
                } else
                    break;
            }
        }
    }
}

<方式2>

static class MyThread implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            sell();
            if (ticket <= 0)
                break;
        }
    }

    public synchronized void sell() {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " sell out ticket" + ticket--);
        }
    }
}

运行结果:
result1

六、死锁问题

死锁:并发运行的多个线程彼此等待对方占有的资源、都无法运行的状态
synchronized关键字要慎用,不然很容易出现死锁,下面是一个例子:

// DeadLockDemo.java
package wintervacation.multithreading;

/**
 * Created by wangw on 2016/3/2. 死锁的例子
 */
public class DeadLockDemo {
    public static void main(String[] args) throws InterruptedException {
        StringBuffer sb = new StringBuffer("haha");
        MyThread thread = new MyThread(sb);
        thread.start();

        synchronized (sb) {
            thread.join();
            System.out.println(Thread.currentThread().getName());
        }
    }

    static class MyThread extends Thread {
        private StringBuffer sb = new StringBuffer();

        MyThread(StringBuffer sb) {
            this.sb = sb;
        }

        @Override
        public void run() {
            synchronized (sb) {
                System.out.println(this.getName());
            }
        }

    }
}

主线程中调用了thread的join方法,等待thread线程执行完毕,而thread线程需要sb锁,sb锁被main线程占用,故也无法执行,发生死锁。
死锁没有好的解决方式,应该尽量避免。

七、线程之间的通信

多线程程序之间如果没有通信,相互孤立,就失去了多线程的意义了。
通信问题的经典例子:
生产者消费者的同步问题(问题描述请自行上网搜索)
Java多线程实现:

// ConsumerAndProductor.java
package wintervacation.multithreading;

/**
 * Created by wangw on 2016/3/2. 消费者和生产者的例子,用于说明线程之间的通信 把生产者和消费者作为两个线程
 * 仓库作为一个类,有装入生产者生产的商品和向消费者提供商品两个方法
 */
public class ConsumerAndProductor {
    public static void main(String[] args) {
        Repo repo = new Repo();
        ComsumerThread comsumerThread = new ComsumerThread(repo);
        comsumerThread.start();
        ProductorThread productorThread = new ProductorThread(repo);
        productorThread.start();
    }
}

class Repo {
    // 仓库可以容纳6件商品
    char[] data = new char[6];
    int index = 0;

    public synchronized void in(char c) {
        if (index == 6) {
            try {
                this.wait();
                System.out.println("-----" + this);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        data[index++] = c;
        System.out.println("生产了产品" + c);
        this.notify();
    }

    public synchronized char out() {
        if (index == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        index--;
        System.out.println("消费了产品" + data[index]);
        this.notify();
        return data[index];
    }
}

class ComsumerThread extends Thread {
    Repo repo;

    ComsumerThread(Repo repo) {
        this.repo = repo;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            char c = (char) (Math.random() * 26 + 'A');
            repo.in(c);
            try {
                Thread.sleep((int) (Math.random() * 10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ProductorThread extends Thread {
    Repo repo;

    ProductorThread(Repo repo) {
        this.repo = repo;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            repo.out();
            try {
                Thread.sleep((int) (Math.random() * 100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

原文地址:https://www.cnblogs.com/lijianming180/p/12326361.html