美文网首页
Java中的线程

Java中的线程

作者: 在下陈小村 | 来源:发表于2020-02-09 15:08 被阅读0次

Java使用Thread来代表线程,所有的线程都必须是Thread或者其子类的对象。

线程的创建与启动

1.继承Thread类来创建线程
public class MyFirstThread extends Thread {
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(getName()+"  "+i);
        }
    }

    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            System.out.println(currentThread().getName()+"  "+i);
            if (i==20){
                new MyFirstThread().start();
                new MyFirstThread().start();
            }
        }


    }
}

当前程序中存在3个线程,两个子线程,一个主线程main,通过Thread.currentThread能够获取当前正在运行的线程,通过getName能够获取当前线程的名称。
通过继承Thread的方式实现线程,是无法共享实例Thread-0,和Thread-1的实例里面的内存的。

2.实现Runnable接口创建线程类
public class MySecondThread implements Runnable {
    private int i;
    @Override
    public void run() {
        for (;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }

    }

    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if (i==20){
                Runnable target=new MySecondThread();
                new Thread(target,"线程1").start();
                new Thread(target,"线程2").start();
            }
        }
    }
}

Runnable是一个函数式接口,里面有一个run方法是方法的执行体,Runnable只能作为Thread的targe放入Thread中执行,这也就使得,两个线程之间通过Runnable共享实例变量。

3.使用Callable和Future创建线程
public class MyThirdThread implements Callable<Integer> {
    public static void main(String[] args) {
        MyThirdThread myThirdThread=new MyThirdThread();
//        FutureTask<Integer> task=new FutureTask<>((Callable<Integer>)()->{//使用lambda表达式更方便
//            int i=0;
//            for (;i<100;i++){
//                System.out.println(Thread.currentThread().getName()+" "+i);
//            }
//            return i;
//        });
        FutureTask<Integer>task=new FutureTask<>(myThirdThread);
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if (i==20){
                new Thread(task,"线程1").start();
            }
        }
        try{
            System.out.println("子线程的返回值"+task.get());
        }catch (Exception e){

        }
    }

    @Override
    public Integer call() throws Exception {
        int i=0;
        for (;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }
}

Callable也是函数式接口,方法的执行体在call方法里面,但是Thread不能直接使用Callable的对象作为target,所以有了FutureTask来包装Callable。call方法可以有返回值,future可以通过get方法来获取返回值,只是调用这个方法将导致线程阻塞。

4.线程的生命周期

线程有5种状态,新建、就绪、运行、阻塞、死亡等五种状态。
new一个Thread就是新建了一个线程,.start()就是把线程置于就绪状态,当线程被分配到资源的时候就是运行的状态,当线程失去资源的时候就是阻塞状态,最后线程运行结束,或者抛出异常,或者被stop就是死亡状态了。需要注意的是.start()方法只能在新建的时候调用,死亡了不能再调用.start()方法了。线程是抢占式的,而且不是一直能够占有CPU资源的。

5.控制线程

1、join()把一个线程加到另一个线程当中,需要加入的线程会阻塞直至被加入的线程运行完成。
2、可以通过setDaemon(true)的方式把线程设置为后台线程。效果就是当所有的前台线程都结束时,后台线程也会马上结束。
3、sleep和yield。sleep会使得线程阻塞,而yield不会,他只是将资源让给线程优先级比他高或者相同的线程,也会出现让出了资源,JVM继续把资源分配给他。sleep需要捕捉异常,yield则不用。
4、线程的优先级。通过setPriority()可以改变程序的优先级,默认为5,norm_priority。

6.线程同步

1.线程的安全问题
一个经典的案例就是,银行取钱问题。同一个账户account,两个人同时取钱,当余额大于取款金额时取款成功,取款成功后改写账户余额,否则取款失败。两个人就相当于两个线程,在java线程中是以抢占式的方式来运行的,所以很有可能出现在一个人取完钱还没有改写余额的情况下,另一个人来取钱,取款成功了,导致最后账户余额为负数。

为了解决这个问题,java提供了下面这些方法。
1.synchronize同步代码块,也就是synchronize写在方法当中,被synchronize包裹的代码被称为临界区,synchronize方法()中的对象代表同步监视器,表示根据什么条件来执行同步的效果。

2.同步方法,在需要同步的方法加上 synchronize,这个同步监视器就是this,该类就变成了线程安全的类,synchronize只需要加在有竞争资源的方法上,不需要把该类的所有方法都加上synchronize。

3.使用Lock来实现,在方法中加锁lock。lock(),在tryfinally中释放锁,lock。unlock。原理和synchronize一致,只是更加直观。

在什么情况下会释放同步监视器的锁定
1.同步代码块和同步方法执行结束后
2.同步代码块和同步方法中有break和return方法
3.程序在同步代码块或方法中出现了error
4.当前线程调用了wait()方法

在线程sleep的时候不会释放锁

写一个死锁

public class DeadLock implements Runnable {
    class A{
        synchronized void foo(B b){
            System.out.println("当前线程为"+Thread.currentThread().getName()+"进入了A的foo方法");
            try {
                Thread.sleep(200);
            }catch (Exception e){

            }
            System.out.println("当前线程为"+Thread.currentThread().getName()+"想要进入了B的last方法");
            b.last();
        }
        synchronized void last(){
            System.out.println("进入了A的last方法");
        }
    }

    class B{
        synchronized void bar(A a){
            System.out.println("当前线程为"+Thread.currentThread().getName()+"进入了B的bar方法");
            try {
                Thread.sleep(200);
            }catch (Exception e){

            }
            System.out.println("当前线程为"+Thread.currentThread().getName()+"想要进入了A的last方法");
            a.last();
        }
        synchronized void last(){
            System.out.println("进入了B的last方法");
        }
    }
    A a=new A();
    B b=new B();
    public void init(){
        Thread.currentThread().setName("主线程");
        a.foo(b);
        System.out.println("进入了主线程");
    }
    @Override
    public void run() {
        Thread.currentThread().setName("副线程");
        b.bar(a);
        System.out.println("进入了副线程");
    }

    public static void main(String[] args) {
        DeadLock deadLock=new DeadLock();
        new Thread(deadLock).start();
        deadLock.init();
    }
}

7.线程通信

1.使用wait(),notify(),notifyAll()这三个方法
这三个方法是object的方法,需要配合synchronize同步锁进行使用,用synchronize的锁定对象来调用wait(),来使得线程进行等待,直到同步监视器的调用notify(),才会重新唤醒线程。

2.使用Condition控制线程
这个就是配合Lock使用的,在lock中获得condition,然后用Condition中的await和signal来控制等待和唤醒。

3.使用阻塞队列BlockingQueue来控制

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ProducerAndConsumer {
    static class Producer extends Thread{
        private BlockingQueue<String>blockingDeque;
        public Producer(BlockingQueue<String>blockingDeque){
            this.blockingDeque=blockingDeque;
        }

        @Override
        public void run() {
            String[]strArr=new String[]{"Java","Struts","Spring"};
            for (int i=0;i<999999;i++){
                System.out.println(getName()+"生产者准备生产集合元素!");
                try{
                    Thread.sleep(200);
                    blockingDeque.put(strArr[i%3]);
                }catch (Exception e){

                }
                System.out.println(getName()+"生产完成"+blockingDeque);
            }
        }
    }

    static class Consumer extends Thread{
        private BlockingQueue<String>blockingDeque;
        public Consumer(BlockingQueue<String>blockingDeque){
            this.blockingDeque=blockingDeque;
        }

        @Override
        public void run() {
            while (true){
                System.out.println(getName()+"消费者准备消费集合元素!");
                try{
                    sleep(200);
                    blockingDeque.take();
                }catch (Exception e){

                }
                System.out.println(getName()+"消费者完成"+blockingDeque);
            }
        }
    }

    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<String>(1);
        new Producer(blockingQueue).start();
        new Producer(blockingQueue).start();
        new Consumer(blockingQueue).start();
    }
}

8.线程组和异常捕获

1.线程只能在创建的时候指定线程组,定且不能在使用过程中改变线程组。
2.没有指定线程组的线程,默认和创建它的线程同一个线程组。
3.异常捕获有一个setuncaughtExceptionHandler方法,在哪个线程中设置就捕获哪个线程的异常。

9.线程池

1.首先线程池是什么,这就好比一个景区里面的自行车的租赁点。一开始配置多少辆自行车已经规划好了,自行车就相当于线程池里面的线程,游客就相当于Runnable和call对象。一开始所有的自行车都是空闲的,当游客租赁使用后,线程池里面的线程也少了,当游客归还线程后,线程池里面的线程也就多了空闲等待的线程。景区里如果像共享单车一样铺设了大量的单车,那么景区就会变得相当混乱。同样在系统中如果存在大量并发线程,会导致系统性能剧烈下降,甚至导致JVM崩溃。线程池的最大线程参数就像是规划好的自行车数量,控制了线程的数量。

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService pool= Executors.newFixedThreadPool(6);
        Runnable runnable=()->{
          for (int i=0;i<100;i++){
              System.out.println(Thread.currentThread().getName()+"的i值为:"+i);
          }
        };
        pool.submit(runnable);
        pool.submit(runnable);
        pool.shutdown();
    }
}

2.ForkJoinPool是java为了适应多核CPU,挺高运行效率,通过拆分任务的形式,从而使得多个小任务能够在多个cpu中执行。
awaitTermination()
第一个参数指定的是时间,第二个参数指定的是时间单位(当前是秒)。返回值类型为boolean型。

1️⃣如果等待的时间超过指定的时间,但是线程池中的线程运行完毕,那么awaitTermination()返回true。执行分线程已结束。

2️⃣如果等待的时间超过指定的时间,但是线程池中的线程未运行完毕,那么awaitTermination()返回false。不执行分线程已结束。

3️⃣如果等待时间没有超过指定时间,等待!

可以用awaitTermination()方法来判断线程池中是否有继续运行的线程。

public class ForkJoinPoolTest extends RecursiveAction {
    private  static  final int THRESHOLD=50;
    private  int start;
    private int end;
    public ForkJoinPoolTest(int start,int end){
        this.start=start;
        this.end=end;
    }
    @Override
    protected void compute() {
        if (end-start<THRESHOLD){
            for (int i=start;i<end;i++){
                System.out.println(Thread.currentThread().getName()+"的i值为:"+i);
            }
        }else {
            int middle=(start+end)/2;
            ForkJoinPoolTest left=new ForkJoinPoolTest(start,middle);
            ForkJoinPoolTest right=new ForkJoinPoolTest(middle,end);
            left.fork();
            right.fork();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ForkJoinPool pool=new ForkJoinPool();
        pool.submit(new ForkJoinPoolTest(0,300));
        pool.awaitTermination(2, TimeUnit.SECONDS);
        pool.shutdown();
    }
}

10.ThreadLocal

他的作用就是为每个线程复制一个对象,在一个线程中改变这个对象时,不会影响其他线程的值,这和线程同步共享同一份资源有这很大的区别的。

相关文章

网友评论

      本文标题:Java中的线程

      本文链接:https://www.haomeiwen.com/subject/spddzctx.html