线程与并发(一) 多线程基础

一 .任务,进程与线程

多任务: 指用户在同一时间内运行多个应用程序,每个应用程序可以认为是一个任务.常见的操作系统Linux、windows等就是支持多任务的操作系统。 进程: 计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,通俗讲就是对操作系统中运行的应用程序及其运行环境的统称。比如window上运行QQ程序,还需占用一定内存 线程: 指应用程序中一个单一的顺序控制流程。是进程内有一个相对独立的、可调度的执行单元,可共享进程的内存资源。 多线程: 在单个应用程序(进程)中同时运行多个线程完成不同的工作 并发: 在同一个进程中,同一个时间段,多个线程争夺共享资源过程

java中的线程

Java程序都运行在Java虚拟机(JVM)中,每用java命令启动一个java应用程序,也即启动一个JVM进程。在这个JVM环境中,所有程序代码的运行都是以线程来运行。举个例子:执行java Helloworld命令,操作系统会在创建一个jvm进程,并分配相应的内存资源,接着运行程序入口main方法,创建一个main线程,用于执行helloworld流程。通常,我们将这个线程称之为主线程,当主线程运行结束后,如果没有其他存活线程,JVM进程也随即退出,释放持有的资源。

线程生命周期:

创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度 运行(running)状态: 执行run()方法 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

二 . 创建线程的方式

  • 继承 Thread

  • 实现 Runable

  • 实现 Callable

2.1 继承Thread 重写run方法

public class Test001  {
    public static void main(String[] args) {
        new MyThread().run();
    }

}
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getId());
    }
}

thread类源码:

package java.lang;
public class Thread implements Runnable {
    // 构造方法
    public Thread(Runnable target);
    public Thread(Runnable target, String name);
    
    public synchronized void start();
}

Runnable 接口:

package java.lang;
@FunctionalInterface
public interface Runnable {
    pubic abstract void run();
}

2.2 实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装:

public class Test002 {
    public static void main(String[] args) {
        MyThread002 thread002 = new MyThread002();
        new Thread(thread002).run();
    }
}

class MyThread002 implements Runnable{
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

  两种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式。

其他变体写法:

public class Main {
    public static void main(String[] args) {
        // 匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
            }
        }).start();

        // 尾部代码块, 是对匿名内部类形式的语法糖
        new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
            }
        }.start();

        // Runnable是函数式接口,所以可以使用Lamda表达式形式
        Runnable runnable = () -> {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());};
        new Thread(runnable).start();
    }
}

2.3 实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread

Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕

public class Main {
    public static void main(String[] args) throws Exception {
         // 将Callable包装成FutureTask,FutureTask也是一种Runnable
        MyCallable callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();
        // get方法会阻塞调用的线程
        Integer sum = futureTask.get();
        System.out.println(Thread.currentThread().getName() +                                        Thread.currentThread().getId() + "=" + sum);
    }
}

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "\t" +                             Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            sum += i;
        }
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName() + "\t" +                             Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
        return sum;
    }
}

三 . 几个常见API之间的区别

3.1 start() 与 run()

public static void main(String[] args) throws Exception {
   new Thread(()-> {
       for (int i = 0; i < 5; i++) {
           System.out.println(Thread.currentThread().getName() + " " + i);
           try { Thread.sleep(200); } catch (InterruptedException e) { }
       }
   }, "Thread-A").start();

   new Thread(()-> {
       for (int j = 0; j < 5; j++) {
           System.out.println(Thread.currentThread().getName() + " " + j);
           try { Thread.sleep(200); } catch (InterruptedException e) { }
       }
   }, "Thread-B").start();
}

start(): 启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。

public static void main(String[] args) throws Exception {
    new Thread(()-> {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            try { Thread.sleep(200); } catch (InterruptedException e) { }
        }
    }, "Thread-A").run();

    new Thread(()-> {
        for (int j = 0; j < 5; j++) {
            System.out.println(Thread.currentThread().getName() + " " + j);
            try { Thread.sleep(200); } catch (InterruptedException e) { }
        }
    }, "Thread-B").run();
}

执行的都是主线程:

 

run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。

3.2 sleep() 与 interrupt()

public static native void sleep(long millis) throws InterruptedException;
public void interrupt();

sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。

sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒。interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去.

3.3 wait() 与 notify()

wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()

wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

wait(long timeout): 时间到了自动执行,类似于sleep(long millis) notify(): 该方法只能在同步方法或同步块内部调用, 随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态 notifyAll(): 唤醒所有的wait对象

public class WaitNotifyTest {
    public static void main(String[] args) throws Exception {
        WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
        new Thread(() -> {
            try {
                waitNotifyTest.printFile();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        new Thread(() -> {
            try {
                waitNotifyTest.printFile();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        new Thread(() -> {
            try {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡觉1秒中,目的是让上面的线程先执行,即先执行wait()");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            waitNotifyTest.notifyPrint();
        }).start();
    }

    private synchronized void printFile() throws InterruptedException {
        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
        this.wait();
        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
    }

    private synchronized void notifyPrint() {
        this.notify();
        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成...");
    }
}

wait():让程序暂停执行,相当于让当前,线程进入当前实例的等待队列,这个队列属于该实例对象,所以调用notify也必须使用该对象来调用,不能使用别的对象来调用。调用wait和notify必须使用同一个对象来调用。

3.4 sleep() 与 wait()

Thread.sleep(long millis): 睡眠时不会释放锁

public static void main(String[] args) throws InterruptedException {
    Object lock = new Object();

    new Thread(() -> {
        synchronized (lock) {
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                try { Thread.sleep(1000); } catch (InterruptedException e) { }
            }
        }
    }).start();

    Thread.sleep(1000);

    new Thread(() -> {
        synchronized (lock) {
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
            }
        }
    }).start();
}

因main方法中Thread.sleep(1000)所以上面的线程Thread-0先被执行,当循环第一次时就会Thread.sleep(1000)睡眠,因为sleep并不会释放锁,所以Thread-1得不到执行的机会,所以直到Thread-0执行完毕释放锁对象lock,Thread-1才能拿到锁,然后执行Thread-1;

3.5 守护线程setDaemon(boolean on)

线程分两种:

  • 用户线程:如果主线程main停止掉,不会影响用户线程,用户线程可以继续运行。

  • 守护线程:如果主线程死亡,守护线程如果没有执行完毕也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC垃圾回收线程就是守护线程

public static void main(String[] args) {
    Thread thread = new Thread() {
        @Override
        public void run() {
            IntStream.range(0, 5).forEach(i -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\ti=" + i);
            });
        }
    };
    thread.start();


    for (int i = 0; i < 2; i++) {
        System.out.println(Thread.currentThread().getName() + "\ti=" + i);
    }
    System.out.println("主线程执行结束,子线程仍然继续执行,主线程和用户线程的生命周期各自独立。");
}

public static void main(String[] args) {
    Thread thread = new Thread() {
        @Override
        public void run() {
            IntStream.range(0, 5).forEach(i -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\ti=" + i);
            });
        }
    };
    thread.setDaemon(true);
    thread.start();


    for (int i = 0; i < 2; i++) {
        System.out.println(Thread.currentThread().getName() + "\ti=" + i);
    }
    System.out.println("主线程死亡,子线程也要陪着一块死!");
}

猜你喜欢

转载自www.cnblogs.com/huqingan/p/12096143.html
今日推荐