java多线程相关基础

来源:互联网 发布:蛐蛐钢琴软件 编辑:程序博客网 时间:2024/05/17 09:01

今天我会将学习Java多线程的做个笔记,方便以后复习。

自定义一个线程(extends Thread)

class MyThread extends Thread {    @Override    public void run() {        for(int i = 0 ; i < 5 ; i++)            System.out.println(this.getName() +"--->"+i);        super.run();    }}

主函数

MyThread thread = new MyThread();        MyThread thread1 = new MyThread();        thread.start();        thread1.start();        try {            Thread.sleep(1000);            System.out.println("------------------");        } catch (InterruptedException e) {            e.printStackTrace();        }        thread.run();        thread1.run();

运行结果:

Thread-0--->0Thread-1--->0Thread-0--->1Thread-1--->1Thread-0--->2Thread-1--->2Thread-0--->3Thread-1--->3Thread-0--->4Thread-1--->4------------------Thread-0--->0Thread-0--->1Thread-0--->2Thread-0--->3Thread-0--->4Thread-1--->0Thread-1--->1Thread-1--->2Thread-1--->3Thread-1--->4

当run方式时,其实并非多线程,只是普通的执行了方法而已,start方法才是符合多线程。为什么?看源码

public synchronized void start() {        if (threadStatus != 0)            throw new IllegalThreadStateException();        /* Notify the group that this thread is about to be started         * so that it can be added to the group's list of threads         * and the group's unstarted count can be decremented. */        group.add(this);        boolean started = false;        try {            start0();            started = true;        } finally {            try {                if (!started) {                    group.threadStartFailed(this);                }            } catch (Throwable ignore) {                /* do nothing. If start0 threw a Throwable then                  it will be passed up the call stack */            }        }    }    private native void start0();    /**     * If this thread was constructed using a separate     * <code>Runnable</code> run object, then that     * <code>Runnable</code> object's <code>run</code> method is called;     * otherwise, this method does nothing and returns.     * <p>     * Subclasses of <code>Thread</code> should override this method.     *     * @see     #start()     * @see     #stop()     * @see     #Thread(ThreadGroup, Runnable, String)     */    @Override    public void run() {        if (target != null) {            target.run();        }    }

看到start0方法,有native进行修饰。简单地讲,一个native Method就是一个java调用非java代码的接口。一个native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C。因为我们知道多线程是与操作系统有关的。

自定义一个线程(implements Runnable)

class MyRunnable implements Runnable{    @Override    public void run() {        for(int i = 0 ; i < 5 ; i++)            System.out.println(this.hashCode()+"--->"+i);    }}

主函数

    Thread t1 = new Thread(new MyRunnable());    Thread t2 = new Thread(new MyRunnable());    t1.start();    t2.start();

结果我就不演示了。两者有什么区别,看接下来的小程序就明白了

class MyThread extends Thread {    private int num;    public MyThread(int num){        this.num = num;    }    @Override    public void run() {        while(num >= 0 )            System.out.println(this.getName()+"num数量--->"+(num--));        super.run();    }}class MyRunnable implements Runnable{    private int num;    public MyRunnable(int num){        this.num = num;    }    @Override    public void run() {        while(num >= 0 )            System.out.println(Thread.currentThread().getName()+"num数量--->"+(num--));    }}public class Test {    public static void main(String[] args) {        int num = 3;                MyThread thread = new MyThread(num);        MyThread thread1 = new MyThread(num);        thread.start();        thread1.start();        try {            Thread.sleep(1000);            System.out.println("------------------");        } catch (InterruptedException e) {            e.printStackTrace();        }        MyRunnable mRun = new MyRunnable(num);        Thread t1 = new Thread(mRun);        Thread t2 = new Thread(mRun);        t1.start();        t2.start();    }}

结果

Thread-0num数量--->3Thread-0num数量--->2Thread-0num数量--->1Thread-0num数量--->0Thread-1num数量--->3Thread-1num数量--->2Thread-1num数量--->1Thread-1num数量--->0------------------Thread-2num数量--->3Thread-3num数量--->2Thread-3num数量--->0Thread-2num数量--->1

通过可以看到runnable对于num进行了我们想要的操作,两个线程操作统一个共享资源。就好像不同的售票窗口去买数据库总的票一样。总结一下Runnable的优点。

1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

看Thread的源码你会发现继承了Runnable接口

publicclass Thread implements Runnable {    /* Make sure registerNatives is the first thing <clinit> does. */    private static native void registerNatives();    static {        registerNatives();    }...    ...  }

线程的相关方法

 public final native boolean isAlive();//是否活着 /* *join方法的功能就是使异步执行的线程变成同步执行 *使用join方法后,直到这个线程退出,程序才会往下执行。 */ public final synchronized void join(int millis );//等待该线程终止的时间最长为 millis 毫秒。 public void interrupt();//中断 /** *不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源 * public final static int MIN_PRIORITY = 1; * public final static int NORM_PRIORITY = 5; * public final static int MAX_PRIORITY = 10; */ public final void setPriority(int newPriority); /** yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。 */ public static native void yield();

看一下一般线程的生命周期图
这里写图片描述

守护线程和用户线程
这里我具体讲一下守护线程和用户线程 。
所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。
在使用守护线程时需要注意一下几点:
(1)thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2)在Daemon线程中产生的新线程也是Daemon的。
(3)守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

class DaemonRunnable implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "开始运行");        // 每隔一秒钟显示守护线程在运行        int count = 1000;        while (count > 0 && Thread.currentThread().isAlive()) {            try {                Thread.sleep(1000);                System.out.println(Thread.currentThread().getName() + "正在运行");                count--;            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName() + "结束运行");    }}public class Test {    public static void main(String[] args) {        System.out.println("主线程开始运行");        Thread thread = new Thread(new DaemonRunnable(), "守护线程");        thread.setDaemon(true);        thread.start();        try {            Thread.sleep(5 * 1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("主线程退出运行");    }}

结果是当主线程作为唯一的用户线程退出后,守护线程也就退出,不会将“守护线程正在运行”打印1000次

主线程开始运行守护线程开始运行守护线程正在运行守护线程正在运行守护线程正在运行守护线程正在运行主线程退出运行

同步问题(synchronized)

有问题的程序

class MyRunnable implements Runnable {    private int num;    public MyRunnable(int num) {        this.num = num;    }    @Override    public void run() {        while (num >= 0) {            /*try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }*/            System.out.println(Thread.currentThread().getName() + "num数量--->" + (num--));        }    }}public class Test {    public static void main(String[] args) {        int num = 5;        MyRunnable mRun = new MyRunnable(num);        Thread t1 = new Thread(mRun);        Thread t2 = new Thread(mRun);        Thread t3 = new Thread(mRun);        t1.setName("线程1");        t2.setName("线程2");        t3.setName("线程3");        t1.start();        t2.start();        t3.start();    }}

运行n遍后的结果:

线程2num数量--->5线程2num数量--->4线程2num数量--->3线程2num数量--->2线程2num数量--->1线程2num数量--->0

没有问题呀!真的没有嘛,其实不然,那是因为run占用的时间太短了,真实的run一定是有比较长的。把Thead.sleep()方法打开后,问题就来了:

线程2num数量--->5线程3num数量--->4线程1num数量--->3线程2num数量--->2线程3num数量--->1线程1num数量--->0线程2num数量--->-1线程3num数量--->-2

为什么?因为多个线程可以操作num–,当线程1操作时num=0,可是线程2,线程3已经进入while(num>0)的判断中,当时num确实是>0的,所以出现了-1,-2的结果。如何处理?synchronized 当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码

class MyRunnable implements Runnable {    private int num;    public MyRunnable(int num) {        this.num = num;    }    @Override    public void run() {        des();    }    public synchronized void des() {        while (num >= 0) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + "num数量--->" + (num--));        }    }}

结果

线程2num数量--->5线程2num数量--->4线程2num数量--->3线程2num数量--->2线程2num数量--->1线程2num数量--->0

下面浅显的讲一下线程的可见性和原子性,以及synchronized,volatile实现共享变量的原理

首先了解一下Java内存模型(JMM):围绕着在并发过程中如何处理可见性、原子性、有序性这三个特性而建立的模型。

可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。
1.synchronized如何实现可见性:
在加锁前,清空工作内存中的共享变量的值,重新从主内存中去最新的共享变量的值。
在解锁前,将最新的工作内存中的共享变量的值刷到主内存中。
2.volatile如何实现可见性
深入的讲:volatile通过加入内存屏障和禁止指令重排序的方法实现可见性。
通俗的讲:volatile修饰的变量,每次被线程访问时都要从主内存中去,当变量发生变化时,都要强制刷的主内存中。
3.final如何实现可见性:
这个最简单,所有的final变量都是不可变的,当然是可见的

原子性:JMM提供保证了访问基本数据类型的原子性(其实在写一个工作内存变量到主内存是分主要两步:store、write),但是实际业务处理场景往往是需要更大的范围的原子性保证,所以模型也提供了synchronized块来保证。对比一下下面两个例子

public class Test {    public volatile int num = 0;    public void inc() {        num++;    }    public static void main(String[] args) {        Test test = new Test();        for (int i = 0; i < 500; i++) {            Thread thread = new Thread(new Runnable() {                public void run() {                    test.inc();                }            });            thread.start();        }        // 如果还有子线程在运行,主线程就让出CPU资源,        // 直到所有的子线程都运行完了,主线程再继续往下执行        while (Thread.activeCount() > 1) {            Thread.yield();        }        System.out.println("num = " + test.num);    }}

多运行几遍你会发现

num = 499

这是因为volatile不具有原子性,num++分为3个步骤:取num,num+1,num赋值回来。执行到任何一步都有可能被别的线程插入进来。导致结果num<=500。synchronized则不是这样,把这3个步骤看做一个原子操作。没有执行完是不会被别的线程抢占执行的。num一定等于500

public class Test {    public int num = 0;    public synchronized void inc() {        num++;    }    public static void main(String[] args) {        Test test = new Test();        for (int i = 0; i < 500; i++) {            Thread thread = new Thread(new Runnable() {                public void run() {                    test.inc();                }            });            thread.start();        }        // 如果还有子线程在运行,主线程就让出CPU资源,        // 直到所有的子线程都运行完了,主线程再继续往下执行        while (Thread.activeCount() > 1) {            Thread.yield();        }        System.out.println("num = " + test.num);    }}

结果

num = 500

其实多线程的内容还有很多。这篇博客的内容也是我在学习多线程的一些笔记而已。我也参考了一些资料。作为以后温习用吧!有不对的地方请多多指教。

0 0
原创粉丝点击