java多线程相关基础

今天我会将学习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--->0
Thread-1--->0
Thread-0--->1
Thread-1--->1
Thread-0--->2
Thread-1--->2
Thread-0--->3
Thread-1--->3
Thread-0--->4
Thread-1--->4
------------------
Thread-0--->0
Thread-0--->1
Thread-0--->2
Thread-0--->3
Thread-0--->4
Thread-1--->0
Thread-1--->1
Thread-1--->2
Thread-1--->3
Thread-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数量--->3
Thread-0num数量--->2
Thread-0num数量--->1
Thread-0num数量--->0
Thread-1num数量--->3
Thread-1num数量--->2
Thread-1num数量--->1
Thread-1num数量--->0
------------------
Thread-2num数量--->3
Thread-3num数量--->2
Thread-3num数量--->0
Thread-2num数量--->1

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

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

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

public
class 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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值