美文网首页
Java 线程基础

Java 线程基础

作者: zheting | 来源:发表于2018-03-16 07:54 被阅读9次

    何时需要多线程?

    程序需要同时执行两个或多个任务。
    程序实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索。
    需要一些后台运行的程序时。比如 垃圾回收。

    线程的创建方式一 -- 通过继承Thread类创建线程类

    package com.sheting.thread.demo1;
    
    /**
     * Create Time: 2018-03-11 06:02
     *
     * @author sheting
     */
    public class TestThread {
    
        public static void main(String[] args) {
            //创建一个线程
            SubTread subTread = new SubTread();
            //启动线程
            subTread.start();
            //一个线程的实例只能调用一次start()方法启动一个线程,如果要启动用多个线程,要创建多个线程实例,各自调用start()方法
            //subTread.start();
            //不能直接运行run方法,这样没有启动一个线程,只是普通类,调用了普通方法。
            //subTread.run();
    
            //主线程
            for (int i = 0; i < 100; i++) {
                System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
            }
        }
    }
    
    /**
     *  通过继承Thread类,重写run方法 创建线程类。
     */
    class SubTread extends Thread {
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
            }
        }
    }
    
    

    线程常用的方法

    • start() 启动线程并执行run方法
    • run() 子线程要执行的代码放入run()方法中
    • currentThread() 静态的,调取当前的线程
    • getName() 获取线程的名字
    • setName() 设置线程的名字
    • yield() 调用此方法的线程释放当前CPU的执行权
    • join() 在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕。
    • isAlive() 判断当前线程是否存活
    • sleep(long l) 显示的让当前线程睡眠指定毫秒数
    • 线程通信 wait() nofigy() natifyAll()
    • 设置线程的优先级 setPriority(), getPriority()
    package com.sheting.thread.demo1;
    
    /**
     * Create Time: 2018-03-11 06:02
     * 线程常用的方法
     * @author sheting
     */
    public class TestThread {
    
        public static void main(String[] args) {
            //创建一个线程
            SubTread subTread = new SubTread();
            //setName()设置线程名称
            subTread.setName("子线程");
            //设置线程的优先级(优先级从1到10, 默认是5。 数字越大抢占到CPU的执行权的机会变大)
            subTread.setPriority(Thread.MAX_PRIORITY);
            //启动线程,并执行 run()方法
            subTread.start();
    
            //主线程名字设置
            Thread.currentThread().setName("主线程");
    
            /*
            for (int i = 0; i < 1000; i++) {
                System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
                if(i%10 == 0){
                    //不应该通过类实例访问静态成员(避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问)
                    //Thread.currentThread().yield();
                    //调用此方法的线程释放当前CPU的执行权,但是也有可能重新被当前线程获取CPU执行权
                    Thread.yield();
                }
            }*/
    
            for (int i = 0; i < 100; i++) {
                System.out.println(String.format("%s--%s", Thread.currentThread().getName(), i));
                if (i == 20) {
                    try {
                        //在A线程中调用B线程的join()方法,表示当执行到此方法,线程A停止执行,直至B线程执行完成。
                        subTread.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            //判断当前线程是否存活
            boolean alive = subTread.isAlive();
            System.out.println(alive);
        }
    }
    
    /**
     * 通过继承Thread类,重写run方法 创建线程类。
     */
    class SubTread extends Thread {
    
        /**
         * 子线程要执行的代码,放入run()方法
         */
        @Override
        public void run() {
            for (int i = 0; i < 300; i++) {
                try {
                    //显示指定当前线程睡眠多少毫秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //currentThread() 静态的,获取当前线程
                //getName() 获取线程的名字
                System.out.println(String.format("%s--%s", Thread.currentThread().getName(), i));
            }
        }
    }
    
    

    线程的创建方式二 --实现Runnable接口

    package com.sheting.thread.demo1;
    
    /**
     * Create Time: 2018-03-13 06:10
     *
     * @author sheting
     */
    public class TestRunnable {
    
        public static void main(String[] args) {
            //启动一个线程,必须调用start()
            PrintNum printNum = new PrintNum();
            Thread thread = new Thread(printNum);
            thread.start();
    
            //再启动一个线程
            Thread thread2 = new Thread(printNum);
            thread2.start();
        }
    
    }
    
    class PrintNum implements Runnable {
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) {
                    System.out.println(String.format("%s: %s", Thread.currentThread().getName(), i));
                }
            }
        }
    }
    
    继承Thread 和 实现Runnable比较
    • 1.public class Thread implements Runnable {} Thread 也实现了Runnable接口
      1. 实现的方式 优于继承的方式
        (1) 避免了Java单继承的局限性
        (2) 如果多个线程要操作同一份资源(数据),更适合使用实现的方式
        比如 如下代码 printNum对象只new了一次, thread和 thread2两个线程共享printNum对象的成员属性等
            PrintNum printNum = new PrintNum();
            Thread thread = new Thread(printNum);
            Thread thread2 = new Thread(printNum);
            thread.start();
            thread2.start();
    

    多线程程序的优点

    (1)提供应用程序的响应。对图形化界面更有意义,可增强用户体验。
    (2) 提供计算机系统CPU的利用率。
    (3) 改善程序结构。将既长又负载的进程分为多个线程,独立运行,利于理解和修改。

    守护线程

    Java中的线程分为两类:一种是守护线程, 一种是用户线程
    守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变为一个守护线程。

    守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。Java垃圾回收就是一个典型的守护线程。

    在使用守护线程时需要注意一下几点:

    (1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

    (2) 在Daemon线程中产生的新线程也是Daemon的。

    (3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

    线程的生命周期

    新建:当一个Thread类或其自雷的对象被声明并创建时,新生的线程对象处于新建状态。
    就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件。
    运行: 当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
    阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
    死亡:线程完成了它的全部工作或线程被提前强制性中止。

    synchronized关键字

    线程安全问题存在的原因?
    由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。

    如何解决线程的安全问题?
    必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作。

    多线程只有有共享数据,才涉及同步。

    Java如何实现线程的安全:线程的同步机制
    方式一: 同步代码块

    synchronized(同步监视器){
          //需要被同步的代码块(即 操作共享数据的代码)
    }
    
    共享数据: 多个线程共同操作的同一个数据(变量)
    同步监视器:由一个类的对象来充当。那个线程获取此监视器,它就执行大括号里被同步的代码。俗称 锁
    

    特别重要,重在理解
    注意:所有的线程要共用同一把锁(也就是同一个对象)。
    在实现Runnable接口的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this, 而且锁对象最好是静态的全局变量。

    方式二:同步方法
    将操作共享数据的方法声明为synchronized。即此方法为同步方法,能够保证 当其中一个线程执行此方法时,其它线程在外等待直至此线程执行完此方法。
    同步方法的锁:this 因此同步方法不一定能保证线程安全,必须同步方法也必须是同一个this.

    线程同步的弊端:由于同一个时间只能有一个线程访问共享数据,效率变低了。

    释放锁的操作:

    • 当前线程的同步方法、同步代码块执行结束
    • 当前线程在同步代码块、同步方法中遇到break、return终止了代该代码块、该方法的继续执行。
    • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
    • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

    不会释放锁的操作:

    • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()/Thread.yield()方法暂停当前线程的执行。
    • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
      应尽量避免使用suspend()和resume()来控制线程。

    单例模式之懒汉式线程安全

    package com.sheting.singleton;
    
    /**
     * Create Time: 2018-03-14 08:50
     *
     * @author sheting
     */
    public class Singleton {
    
        private Singleton() {
        }
    
        private static Singleton instance = null;
    
        public static Singleton getInstance() {
            //if的作用是当多个线程时,如果已经实例化了instance,就不需要等待
            if(instance == null){
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    
    线程同步练习

    线程的通信

    wait() 令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。
    notify() 唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
    notifyAll() 唤醒正在排队等待资源的所有线程结束等待。

    java.lang.Object提供的这三个方法只有在synchronized方法或者synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常

    package com.sheting.thread.demo;
    
    /**
     * Create Time: 2018-03-16 05:54
     *
     * @author sheting
     */
    public class TestCommunication {
    
        public static void main(String[] args) {
            PrintNum printNum = new PrintNum();
            Thread thread1 = new Thread(printNum);
            Thread thread2 = new Thread(printNum);
    
            thread1.start();
            thread2.start();
        }
    }
    
    class PrintNum implements Runnable {
        int num = 1;
    
        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    notify();
                    if (num < 100) {
                        System.out.println(String.format("%s:%s", Thread.currentThread().getName(), num));
                        num++;
                    } else {
                        break;
                    }
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    生产者消费者

    package com.sheting.thread.demo;
    
    /**
     * Create Time: 2018-03-16 07:00
     *
     * @author sheting
     */
    public class TestProductConsume {
    
        public static void main(String[] args) {
            Dispatcher dispatcher = new Dispatcher();
            Producer producer = new Producer(dispatcher);
            Consumer consumer = new Consumer(dispatcher);
    
            new Thread(producer).start();
            new Thread(consumer).start();
        }
    }
    
    
    class Producer implements Runnable{
        Dispatcher dispatcher;
    
        public Producer(Dispatcher dispatcher){
            this.dispatcher = dispatcher;
        }
    
        @Override
        public void run() {
            while (true){
                dispatcher.addProducer();
            }
        }
    }
    
    class Consumer implements Runnable{
        Dispatcher dispatcher;
    
        public Consumer(Dispatcher dispatcher){
            this.dispatcher = dispatcher;
        }
    
        @Override
        public void run() {
            while (true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                dispatcher.consumeProduct();
            }
        }
    }
    
    class Dispatcher{
    
        int producerNum;
    
        public synchronized void addProducer()  {
            if(producerNum >= 20){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                producerNum++;
                System.out.println(String.format("%s:生成了第%s个产品", Thread.currentThread().getName(), producerNum));
                notifyAll();
            }
        }
    
        public synchronized void consumeProduct(){
            if(producerNum <= 0){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                System.out.println(String.format("%s:消费了第%s个产品", Thread.currentThread().getName(), producerNum));
                producerNum--;
                notifyAll();
            }
        }
    }
    

    线程的死锁

    死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

    package com.sheting.thread.demo;
    
    /**
     * Create Time: 2018-03-16 07:36
     *
     * @author sheting
     */
    public class DeadLock {
    
        static StringBuffer sb1 = new StringBuffer();
        static StringBuffer sb2 = new StringBuffer();
    
        public static void main(String[] args) {
            new Thread(){
                @Override
                public void run() {
                    synchronized (sb1){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sb1.append("A");
                        synchronized (sb2){
                            sb2.append("B");
                        }
                    }
                }
            }.start();
    
            new Thread(){
                @Override
                public void run() {
                    synchronized (sb2){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sb1.append("C");
                        synchronized (sb1){
                            sb2.append("D");
                        }
                    }
                }
            }.start();
        }
    }
    
    

    相关文章

      网友评论

          本文标题:Java 线程基础

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