Java多线程基础

Java多线程基础

1.1 线程的生命周期:

在这里插入图片描述

  1. 新建( new ):新创建了一个线程对象。
  2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
  3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
  4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行(
    running )状态。阻塞的情况分三种: (一). 等待阻塞:运行( running )的线程执行 o . wait ()方法,
    JVM 会把该线程放 入等待队列( waitting queue )中。 (二). 同步阻塞:运行( running
    )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。 (三).
    其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join
    ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、
    join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
  5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

1.2 新建线程

java生成线程的四种方式:

  • 继承Thread类

  • 实现Runnable接口

  • 实现Callable接口

  • 使用Executor框架来创建线程池

  • 继承Thread类,重写run方法 :
package Chapter1;

public class CreateThread {
    public static void main(String[] args) {
        Rabbit t1 = new Rabbit();
        t1.start();
    }
}

class Rabbit extends Thread {
    @Override
    public void run() {
        System.out.println("i am t1");
    }
}

  • 实现Runnable接口 :
/**
 * 启动:使用静态代理
   1)、创建真实角色
   2)、创建代理角色
   3)、调用start()方法 启动线程
   优点:可以同时实现继承,Runnable接口方式更加通用一些。
   1、避免单继承的局限性
   2、便于共享资源
   通过实现Runnable接口实现多线程。(用到了静态代理设计模式)
 */
public class thread2 {
    /**
     * 推荐使用Runnable创建线程
     * 1、避免单继承的局限性
     * 2、便于共享资源
     */

        public static void main(String[] args) {
            //1)、创建真实角色
            Programmer pro = new Programmer();
            //2)、创建代理角色+真实角色引用
            Thread proxy = new Thread(pro);
            //3)、调用start()方法 启动线程
            proxy.start();

            for(int i=0;i<100;i++){
                System.out.println("一边聊QQ");
            }
        }
    }

    /**
     * 使用Runnable 创建进程
     * 1、类实现Runable接口+重写run()方法
     * 2、启动多线程 使用静态代理
     *      1)、创建真实角色
     *      2)、创建代理角色
     *      3)、调用start()方法 启动线程
     */
    class Programmer implements Runnable{
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                System.out.println("一边敲代码");
            }
        }
    }

  • 使用Callable接口方式创建多线程

Callable 和 Future接口 Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
优点:可以返回值,可以抛异常。
缺点:实现繁琐。

public class thread3 {
    public static void main(String[] args) {
        // 第三種:使用implements Callable方式,具有返回值
        List<FutureTask<Integer>> resultItems1 = new ArrayList<FutureTask<Integer>>();
        CountDownLatch countDownLatch3 = new CountDownLatch(2);
        for (int i = 0; i < 2; i++) {
            MyCallable myCallable = new MyCallable(countDownLatch3);
            FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);
            new Thread(futureTask).start();
            resultItems1.add(futureTask);
        }

        try {
            countDownLatch3.await();
            Iterator<FutureTask<Integer>> iterator = resultItems1.iterator();
            while (iterator.hasNext()) {
                try {
                    System.out.println(iterator.next().get());
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("callable complete...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyCallable implements Callable<Integer> {
        CountDownLatch countDownLatch;

        public MyCallable(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        public Integer call() throws Exception {
            try {
                Thread.sleep(2000);
                int sum = 0;
                for (int i = 0; i <= 100; i++) {
                    sum += i;
                }
                System.out.println(Thread.currentThread().getName() + ":my callable");
                return sum;
            } finally {
                countDownLatch.countDown();
            }
        }
    }
}

  • 使用Executor框架来创建线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class thread4 {
    private static int POOL_NUM = 10;

    public static void main(String[] agrs) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < POOL_NUM; i++) {
            RunnableThread thread = new RunnableThread();
            executorService.execute(thread);
        }
    }
}

class RunnableThread implements Runnable {
    private int THREAD_NUM = 10;

    public void run() {
        for (int i = 0; i < THREAD_NUM; i++) {
            System.out.println("线程" + Thread.currentThread() + i);
        }

    }
}

1.3 终止线程

不要使用jdk的stop()方法,可能会引起一些数据不一致的问题。
推荐以下方法:

public class demo extends Thread {
    volatile boolean stopme = false;

    public void stopMe() {
        stopme = true;
    }

    @Override
    public void run() {
        while(true){
            if (stopme){
                System.out.println("exit by stop me!");
                break;
            }
        }
        //业务逻辑, 想要退出时调用
        stopMe();
    }
}

1.4 等待(wait)和通知(notify)

现在有多个线程A,B,C。当线程A调用了obj.wait()方法后,那么线程A就会停止继续执行,而转为等待状态。
等待到其他线程调用了obj.notify()方法为止。obj.notify()方法会随机唤醒其中一个线程。如下:

package Chapter2;

public class SimpleWaitAndNotify {
    final static Object object = new Object();//通過object對象來进行多线程通信

    public static class Thread1 extends Thread {
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ":thread1 start !");
                try {
                    System.out.println(System.currentTimeMillis() + ":thread1 wait for object !");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":thread1 end!");
            }
        }
    }

    public static class Thread2 extends Thread {
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ":thread2 start ! notify one thread");
                object.notify();

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":thread2 end!");
            }
        }
    }

    public static class Thread3 extends Thread {
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ":thread3 start !");
                try {
                    System.out.println(System.currentTimeMillis() + ":thread3 wait for object !");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":thread3 end!");
            }
        }
    }

    public static void main(String args[]) {
        Thread thread1 = new Thread1();
        Thread thread2 = new Thread2();
        Thread thread3 = new Thread3();
        thread1.start();
        thread3.start();
        thread2.start();
    }

    //wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源.
}

1.5 挂起(supend)和继续执行(resume)线程 (已废弃,不推荐使用)

1.6 等待线程结束(join)和谦让(yield)

如下所示,显示了3个join()方法:

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public final void join(long millis,int nanos)

  • 第一个join()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。
  • 第二个join()方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
  • 第三个join()方法表示 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
如下为一个join()参考实例:
public class JoinMain {
    public volatile static int i = 0;

    public static class AddThread extends Thread {
        public void run() {
            System.out.println("add!");
            for (i = 0; i < 1000000; i++) ;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String args[]) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();//使用join()方法后,主线程会等待AddThread执行完毕,i输出为1000000,如果没有这条语句,i输出为0
                  //可以查看join的底层代码,本质即让调用线程在当前线程对象实例上等待
        System.out.println(i);
        at.join();
    }
}

Thread.yield()的定义如下:

public static native void yield();

这是一个静态方法,一旦执行,它会使当前线程让出CPU。但是让出CPU并不表示当前线程不执行了。当前线程在让出CPU后,还会进行CPU资源的争夺,是否能够再次被分配到就不一定了。

如下例子:

/**
 * 这里是 在 Thread 子类覆盖的 run 方法 新建线程的方法
 * 其中一种写法是lamda表达式
 */
public class Yield {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.print("1");
            yield();
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("3");

        });

       Thread t2 = new Thread(new Runnable() {
           @Override
           public void run() {
               System.out.println("2");
           }
       });
        t1.start();
        t2.start();
        t2.wait();
    }
}  

1.7 volatile与Java内存模型

Java使用了一些特殊的操作或者关键字来申明、告诉虚拟机,在这个地方,要尤其注意,不能随意变动优化目标指令。关键字volatile就是其中之一。
当你用volatile去申明一个变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改。

如下例子,通过volatile是无法保证i++的原子性操作的:

public class VolatileDemo {
    static volatile int i = 0;

    public static class PlusTask implements Runnable {
        @Override
        public void run() {
            for (int j = 0; j < 10000; j++) {
                i++;
            }
        }
    }

    public static void main(String args[]) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread[] threads = new Thread[100];
        for (int j = 0; j < 100; j++) {
            threads[j] = new Thread(new PlusTask());
            threads[j].start();
        }
        for (int j = 0; j < 100; j++) {
            threads[j].join();
        }
        System.out.println(i);
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("耗时:" + time);
    }
}

1.8 线程局部变量

ThreadLocal 解决多线程变量共享问题
  • ThreadLocal 的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
  • ThreadLocal 的应用场景:
    • 订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
    • 银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
ThreadLocal 的 API 提供了如下的 4 个方法。
  • protected T initialValue()返回当前线程的局部变量副本的变量初始值。
  • T get()返回当前线程的局部变量副本的变量值,如果此变量副本不存在,则通过 initialValue() 方法创建此副本并返回初始值。
  • void set(T value)设置当前线程的局部变量副本的变量值为指定值。
  • void remove()删除当前线程的局部变量副本的变量值。在实际使用中,我们一般都要重写 initialValue() 方法,设置一个特定的初始值。
简单示例:

首先,我们来看看不考虑多线程共享数据的情况。现在有小明、小刚、小红三人在同一家银行,分别向各自账户存入 200 元钱:



public class MainTest {

    public static void main(String[] args) {
        Bank bank = new Bank();
        Thread xMThread = new Thread(() -> bank.deposit(200), "小明");
        Thread xGThread = new Thread(() -> bank.deposit(200), "小刚");
        Thread xHThread = new Thread(() -> bank.deposit(200), "小红");
        xMThread.start();
        xGThread.start();
        xHThread.start();
    }
}

class Bank {

    private int money = 1000;

    public void deposit(int money) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "--当前账户余额为:" + this.money);
        this.money += money;
        System.out.println(threadName + "--存入 " + money + " 后账户余额为:" + this.money);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:小明–当前账户余额为:1000
小红–当前账户余额为:1000
小红–存入 200 后账户余额为:1400
小刚–当前账户余额为:1000
小刚–存入 200 后账户余额为:1600
小明–存入 200 后账户余额为:1200

结果是除了小明存钱和自己账户余额能对上外,小刚和小红也都只存了 200,但他们的账户余额分别多了 200 和 400?这是因为多个线程共享了同一个实例对象的局部变量所致。使用 ThreadLocal 保存对象的局部变量。package com.wuxianjiezh.demo.threadpool;

import java.util.function.Supplier;

public class MainTest {

    public static void main(String[] args) {
        Bank bank = new Bank();
        Thread xMThread = new Thread(() -> bank.deposit(200), "小明");
        Thread xGThread = new Thread(() -> bank.deposit(200), "小刚");
        Thread xHThread = new Thread(() -> bank.deposit(200), "小红");
        xMThread.start();
        xGThread.start();
        xHThread.start();
    }
}

class Bank {

    // 初始化账户余额为 100
    ThreadLocal<Integer> account = ThreadLocal.withInitial(new Supplier<Integer>() {
        @Override
        public Integer get() {
            return 1000;
        }
    });

    public void deposit(int money) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "--当前账户余额为:" + account.get());
        account.set(account.get() + money);
        System.out.println(threadName + "--存入 " + money + " 后账户余额为:" + account.get());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:小明–当前账户余额为:1000
小红–当前账户余额为:1000
小红–存入 200 后账户余额为:1200
小刚–当前账户余额为:1000
小刚–存入 200 后账户余额为:1200
小明–存入 200 后账户余额为:1200

可以看到,我们要的效果达到了。各线程间同时操作自己的变量,相互间没有影响。

ThreadLocal 与 Thread 同步机制的比较
  • 同步机制采用了以时间换空间方式,通过对象锁保证在同一个时间,对于同一个实例对象,只有一个线程访问。
  • ThreadLocal 采用以空间换时间方式,为每一个线程都提供一份变量,各线程间同时访问互不影响。

1.9 多线程共享数据

在 Java 传统线程机制中的共享数据方式,大致可以简单分两种情况:
➢ 多个线程行为一致,共同操作一个数据源。 也就是每个线程执行的代码相同,可以使用同一个 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如,卖票系统就可以这么做。
➢ 多个线程行为不一致,共同操作一个数据源。也就是每个线程执行的代码不同,这时候需要用不同的Runnable 对象。例如,银行存取款。
下面我们通过两个示例代码来分别说明这两种方式。

  • 多个线程行为一致共同操作一个数据 :

如果每个线程执行的代码相同,可以使用同一个 Runnable 对象,这个 Runnable 对象中有那个共享数据,例如,
买票系统就可以这么做。

public class demo2 {
    /**
     *共享数据类
     **/
    static class ShareData{
        private int num = 10 ;
        public synchronized void inc() {
            num++;
            System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     *多线程类
     **/
    static class RunnableCusToInc implements Runnable{
        private ShareData shareData;
        public RunnableCusToInc(ShareData data) {
            this.shareData = data;
        }
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                shareData.inc();
            }
        }
    }

    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        for (int i = 0; i < 4; i++) {
            new Thread(new RunnableCusToInc(shareData),"Thread "+ i).start();
        }
    }
}

如果inc()方法不加上synchronized 最终的结果明显得数不够,如下:

Thread 0: invoke inc method num =11
Thread 2: invoke inc method num =13
Thread 1: invoke inc method num =12
Thread 3: invoke inc method num =14
Thread 0: invoke inc method num =16
Thread 3: invoke inc method num =17
Thread 1: invoke inc method num =16
Thread 2: invoke inc method num =16
Thread 1: invoke inc method num =19
Thread 0: invoke inc method num =21
Thread 2: invoke inc method num =20
Thread 3: invoke inc method num =19
Thread 3: invoke inc method num =22
Thread 1: invoke inc method num =24
Thread 0: invoke inc method num =23
Thread 2: invoke inc method num =25
Thread 3: invoke inc method num =26
Thread 2: invoke inc method num =27
Thread 1: invoke inc method num =29
Thread 0: invoke inc method num =28

Process finished with exit code 0

  • 多个线程行为不一致共同操作一个数据

如果每个线程执行的代码不同,这时候需要用不同的 Runnable 对象,有如下两种方式来实现这些 Runnable 对
象之间的数据共享:

1. 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个 Runnable 对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

public class demo3 {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        for (int i = 0; i < 4; i++) {
            if(i%2 == 0){
                new Thread(new RunnableCusToInc(shareData),"Thread "+ i).start();
            }else{
                new Thread(new RunnableCusToDec(shareData),"Thread "+ i).start();
            }
        }
    }
}
/**
 *共享数据类
 **/
class ShareData{
    private int num = 10 ;
    public synchronized void inc() {
        num++;
        System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void dec() {
        num--;
        System.err.println(Thread.currentThread().getName()+": invoke dec method num =" + num);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

//封装共享数据类
class RunnableCusToInc implements Runnable{
    //封装共享数据
    private ShareData shareData;
    public RunnableCusToInc(ShareData data) {
        this.shareData = data;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            shareData.inc();
        }
    }
}
//封装共享数据类
class RunnableCusToDec implements Runnable{
    //封装共享数据
    private ShareData shareData;
    public RunnableCusToDec(ShareData data) {
        this.shareData = data;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            shareData.dec();
        }
    }
}

2. 将这些 Runnable 对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable 对象调用外部类的这些方法。

public class demo5 {
    public static void main(String[] args) {
//公共数据
        final ShareData shareData = new ShareData();
        for (int i = 0; i < 4; i++) {
            if (i % 2 == 0) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 5; i++) {
                            shareData.inc();
                        }
                    }
                }, "Thread " + i).start();
            } else {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 5; i++) {
                            shareData.dec();
                        }
                    }
                }, "Thread " + i).start();
            }
        }
    }
}

class ShareData {
    private int num = 10;

    public synchronized void inc() {
        num++;
        System.out.println(Thread.currentThread().getName() + ": invoke inc method num = " + num);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void dec() {
        num--;
        System.err.println(Thread.currentThread().getName() + ": invoke dec method num = " +
                "" + num);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

参考资料:《实战Java高并发程序设计》

posted @ 2019-02-03 00:06  whomhim  阅读(204)  评论(0编辑  收藏  举报