• java线程的支持主要体现在Thread类以及Runable接口上,他们都位于java.lang包下,无论是Thread类还是Runable接口,它们都有public void run()方法,这个run方法为我们提供了线程实际工作时的代码,换句话说,我们...

    1. java对线程的支持

    java对线程的支持主要体现在Thread类以及Runable接口上,他们都位于java.lang包下,无论是Thread类还是Runable接口,它们都有public void run()方法,这个run方法为我们提供了线程实际工作时的代码,换句话说,我们的逻辑代码就可以写在run方法体中。
    

    那么什么时候该用Thread,什么时候该用Runable呢
    继承Thread实现的模式是 定义多个线程,各自完成各自的任务. 实现Runnable实现的模式是 定义多个线程,实现一个任务.其实在实现一个任务用多个线程来做也可以用继承Thread类来实现只是比较麻烦,一般我们用实现Runnable接口来实现,简洁明了。大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。

    2. 线程的常用方法
    这里写图片描述
    3. 如何正确地停止线程
    不要调用线程的stop()方法来停止线程,调用stop方法停止线程会有一种戛然而止的感觉,你并不知道它是执行到哪一步停止的,也不知道还有什么逻辑没执行完。应该设置一个结束标志来结束线程。也不要调用interrupt()方法来停止线程,因为interrupt()方法的初衷并不是用于停止线程的,而是用于中断线程。
    4 interrupt
    ① interrupt的作用就是中断线程,调用interput()方法会设置线程的中断状态。
    ② 然后调用线程的interrupted()【这是一个静态方法】方法或isInterrupted()【这是一个实例方法】方法将会返回一个boolean值(线程的中断状态)。
    ③ 当一个线程,调用join,sleep方法而被阻塞时,会使线程的中断状态被清除,然后后面调用interrupted()或isInterrupted()方法都不能得到一个正确的结果,并且当前线程会收到一个InterruptedException异常,这也就是为什么我们需要在代码sleep方法和join方法中进行try catch的原因。

        package com.glassbox.thread.demo;
    
    /**
     * @auther xiehuaxin
     * @create 2018-08-08 10:16
     * @todo
     */
    public class InterruptTest extends Thread {
        public static void main(String[] args) {
            System.out.println("begin>>>>>>>>>>>>>>>");
            Thread interruptTest = new InterruptTest();
            interruptTest.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("interrupt Thread>>>>>>>");
            interruptTest.interrupt();
            System.out.println("stop>>>>>>>>>>>>>>>>");
        }
    
        @Override
        public void run() {
            while (true) {
                System.out.println("Thread is running...");
                /**
                 * 这里用这种方式代替sleep是因为调用sleep的时候会改变当前线程的“中断状态”,从而抛出一个InterreptException异常
                 */
                long time = System.currentTimeMillis();
                while (System.currentTimeMillis() - time < 1000) {
    
                }
            }
        }
    }
    

    输出: 可以看到,当调用interrupt方法后,线程仍在输出

    begin>>>>>>>>>>>>>>>
    Thread is running...
    Thread is running...
    Thread is running...
    Thread is running...
    interrupt Thread>>>>>>>
    stop>>>>>>>>>>>>>>>>
    Thread is running...
    Thread is running...
    Thread is running...
    Thread is running...
    

    可以把isInterrupted()的返回结果当做结束标志

       @Override
        public void run() {
            while (!this.isInterrupted()) {
                System.out.println("Thread is running...");
                /**
                 * 这里用这种方式代替sleep是因为调用sleep的时候会改变当前线程的“中断状态”,从而抛出一个InterreptException异常
                 */
                long time = System.currentTimeMillis();
                while (System.currentTimeMillis() - time < 1000) {
    
                }
            }
        }

    5. 线程中的能量不守恒
    “争用条件”:当多个线程同时共享访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏(corrupted),这种现象称为争用条件。

    package com.glassbox.thread.demo2;
    
    /**
     * @auther xiehuaxin
     * @create 2018-08-08 10:52
     * @todo
     */
    
    /**
     * 宇宙的能量系统
     * 遵循能量守恒定律
     * 能量不会凭空产生也不会凭空消失,只会从一处转移到另一处
     */
    public class EnergySystem {
    
        //能量盒子,能存储能量的地方
        private final double[] energyBox;
    
        /**
         *
         * @param n 能量盒子的数量
         * @param initalEnergy 每个能量盒子初始含有的能量值
         */
        public EnergySystem(int n, double initalEnergy) {
            this.energyBox = new double[n];
            for (int i = 0; i < energyBox.length; i++) {
                energyBox[i] = initalEnergy;
    
            }
        }
    
        /**
         * 能量的转移,从一个盒子转移到另外一个盒子
         * @param from 能量源
         * @param to 能量终点
         * @param amount 要转移的能量值
         */
        public void transfer(int from, int to, double amount) {
    
            if(energyBox[from] < amount) {
                return;
            }
            System.out.println(Thread.currentThread().getName());
            energyBox[from] -= amount;
            System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
            energyBox[to] += amount;
            System.out.printf("能量总和:%10.2f%n", getTotalEnergies());
        }
    
        /**
         * 获取能量世界的能量总和
         * @return
         */
        private double getTotalEnergies() {
            double sum = 0;
            for (double amount : energyBox) {
                sum += amount;
            }
            return sum;
        }
    
        /**
         * 返回能量盒子的长度
         * @return
         */
        public int getBoxAmount() {
            return energyBox.length;
        }
    }
    package com.glassbox.thread.demo2;
    
    /**
     * @auther xiehuaxin
     * @create 2018-08-08 11:48
     * @todo
     */
    public class EnergyTransferTask implements Runnable {
    
        //共享的能量世界
        private EnergySystem energySystem;
        //能量转移的能源盒子下班
        private int fromBoxIndex;
        //单次能量转移最大单元
        private double maxAmount;
        //最大休眠时间(毫秒)
        private int DELAY = 10;
    
        public EnergyTransferTask(EnergySystem energySystem, int fromBoxIndex, double max) {
            this.energySystem = energySystem;
            this.fromBoxIndex = fromBoxIndex;
            this.maxAmount = max;
        }
    
        @Override
        public void run() {
            try {
                while (true) {
                    int toBox = (int) (energySystem.getBoxAmount() * Math.random());
                    double amount = maxAmount * Math.random();
                    energySystem.transfer(fromBoxIndex, toBox, amount);
                    Thread.sleep((int) (DELAY * Math.random()));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    package com.glassbox.thread.demo2;
    
    /**
     * @auther xiehuaxin
     * @create 2018-08-08 11:59
     * @todo
     */
    public class EnergySystemTest {
        //要构建的能量世界的盒子的数量
        public static final int BOX_AMOUNT = 100;
        //每个盒子的初始能量
        public static final double INITIAL_ENERGY = 1000;
    
        public static void main(String[] args) {
            EnergySystem energySystem = new EnergySystem(BOX_AMOUNT,INITIAL_ENERGY);
            for (int i = 0; i < BOX_AMOUNT; i++) {
                EnergyTransferTask task = new EnergyTransferTask(energySystem,i,INITIAL_ENERGY);
                Thread thread = new Thread(task,"TransferThread:" + i);
                thread.start();
            }
        }
    }

    输出

    38转移    174.83单位能量到0能量总和: 100000.00
    TransferThread:3838转移     27.28单位能量到9能量总和: 100000.00
    TransferThread:52
    TransferThread:9898转移    584.27单位能量到49能量总和:  99390.63
    TransferThread:9898转移    126.47单位能量到20能量总和:  99390.63
    TransferThread:9898转移    264.87单位能量到98能量总和:  99390.63
    TransferThread:7474转移    873.16单位能量到42能量总和:  99390.63
    TransferThread:5050转移    439.71单位能量到47能量总和:  99390.63
    TransferThread:7070转移     29.80单位能量到34能量总和:  99390.63
    能量总和: 100000.00
    TransferThread:7575转移    377.40单位能量到78能量总和:  99390.63
    TransferThread:9595转移     74.24单位能量到76能量总和:  99390.63
    能量总和: 100000.00
    TransferThread:7171转移    285.92单位能量到10能量总和:  99390.63
    TransferThread:8383转移    778.90单位能量到66能量总和:  99390.63
    TransferThread:4646转移    515.10单位能量到63能量总和:  99390.6352转移    609.37单位能量到40能量总和: 100000.00

    6. 那么如何保持能量守恒呢
    互斥与同步
    互斥:字面意思是互相排斥,实际含义是,同一时间只能有一个线程对临界区或关键数据进行操作(通过锁)。
    同步:线程间的一种通信机制。一个线程它可以做一件事情,然后它可以用某种方式告诉别的线程它已经做完了(notify,wait,notifyAll)。

    package com.glassbox.thread.demo2;
    
    /**
     * @auther xiehuaxin
     * @create 2018-08-08 10:52
     * @todo
     */
    
    /**
     * 宇宙的能量系统
     * 遵循能量守恒定律
     * 能量不会凭空产生也不会凭空消失,只会从一处转移到另一处
     */
    public class EnergySystem {
    
        //能量盒子,能存储能量的地方
        private final double[] energyBox;
    
        //添加一个锁的对象
        private final Object lockObj = new Object();
    
        /**
         *
         * @param n 能量盒子的数量
         * @param initalEnergy 每个能量盒子初始含有的能量值
         */
        public EnergySystem(int n, double initalEnergy) {
            this.energyBox = new double[n];
            for (int i = 0; i < energyBox.length; i++) {
                energyBox[i] = initalEnergy;
    
            }
        }
    
        /**
         * 能量的转移,从一个盒子转移到另外一个盒子
         * @param from 能量源
         * @param to 能量终点
         * @param amount 要转移的能量值
         */
        public void transfer(int from, int to, double amount) {
    
            /**
             * 通过对lockObj对象加锁实现互斥
             */
            synchronized (lockObj) {
                //这里当能量不足的时候就退出,但是在退出之后,这条线程仍然有机会去获取cpu资源,从而再次要求加锁,但是加锁操作时有开销的,这样会降低系统的性能,所以当条件不满足的时候我们可以让这条线程等待,从而降低这条线程获取锁的开销
                /*if(energyBox[from] < amount) {
                    return;
                }*/
    
                /**
                 * while循环,保证条件不满足时任务都会被阻挡,而不是竞争cpu资源
                 */
                while (energyBox[from] < amount) {
                    try {
                        //当线程调用lockObj对象的wait()方法之后,当前的线程就会被置于lockObj对象的“等待集合wait set”中
                        lockObj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
    
                System.out.println(Thread.currentThread().getName());
                energyBox[from] -= amount;
                System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);
                energyBox[to] += amount;
                System.out.printf("能量总和:%10.2f%n", getTotalEnergies());
                //唤醒所有在lockObj对象上等待的线程
                lockObj.notifyAll();
            }
    
        }
    
        /**
         * 获取能量世界的能量总和
         * @return
         */
        private double getTotalEnergies() {
            double sum = 0;
            for (double amount : energyBox) {
                sum += amount;
            }
            return sum;
        }
    
        /**
         * 返回能量盒子的长度
         * @return
         */
        public int getBoxAmount() {
            return energyBox.length;
        }
    }

    输出

    68转移    765.10单位能量到80能量总和: 100000.00
    TransferThread:6969转移    564.22单位能量到33能量总和: 100000.00
    TransferThread:6464转移    941.32单位能量到96能量总和: 100000.00
    TransferThread:9696转移    511.90单位能量到74能量总和: 100000.00
    TransferThread:44转移    424.99单位能量到70能量总和: 100000.00
    TransferThread:77转移    429.73单位能量到33能量总和: 100000.00

    7. 如何扩展java并发编程的知识
    ① 了解Java Memory Model
    JMM描述了java线程如何通过内存进行交互的(通过这部分学习可以了解什么是happens-before原则)
    happens-before
    synchronized,volatile&final(java是如何通过这几个关键字来实现happens-before原则的)

    ② Locks对象和Condition对象(通过这两个对象可以了解如何对程序进行加锁以及同步的通信)
    java锁机制和等待条件的高层实现
    java.util.concurrent.locks

    ③ 线程安全性
    原子性、可见性
    java.util.concurrent.atomic(如何通过这个包来避免原子性编程的问题)
    synchronized & volatile(当一个原子操作由多个语句构成时,又是如何通过synchronized实现原子性操作的)
    DeadLocks

    ④ 多线程常用的交互模型(在java并发实现当中,有哪些类是实现了这些模型,可以供我们直接调用的)
    Producer-Consumer模型
    Read-Write Lock模型
    Future模型
    Woker Thread模型

    ⑤ Java5引入的并发编程工具
    java.util.concurrent
    线程池ExecutorService
    Callable & Future
    BlockingQueue

    最后推荐两边书
    这里写图片描述

    练习巩固
    题目:创建两个线程,一个线程输出1、3、5、…、99,另一个线程输出2、4、6、…、100。最后的效果是两个线程相互交替输出,达到输出1、2、3、4、…、99、100这样顺序输出的效果。
    以下是我自己的实现:

    package com.glassbox.thread.test;
    
    /**
     * @auther xiehuaxin
     * @create 2018-08-08 16:08
     * @todo
     */
    public class PrintNum implements Runnable {
    
        int i = 1;
        @Override
        public void run() {
            while (true) {
                //如果不是使用实现Runnable这种方式创建线程,这里不要用this(自己创建一个锁对象)
                synchronized (this) {
                    //这里先调用锁对象的notify方法再调用wait方法是巧妙之处,大家细细体会,如果还不明白的话可以把notify方法放到wait方法后,然后运行输出结果就一目了然了
                    notify();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(i <= 100) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                        i++;
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    
    package com.glassbox.thread.test;
    
    import java.util.concurrent.*;
    
    /**
     * @auther xiehuaxin
     * @create 2018-08-08 15:41
     * @todo
     */
    public class ThreadExercise {
        /**
         * 任务队列
         */
        private static ArrayBlockingQueue workQueue = new ArrayBlockingQueue(10);
    
        public static void main(String[] args) {
    
            /*
          线程池最大数量
         */
            int maximumPoolSizeSize = 100;
            /*
          线程活动保持时间
         */
            long keepAliveTime = 1;
            /*
          线程池的基本大小
         */
            int corePoolSize = 10;
            //建议手动创建线程池并且以线程池的形式创建线程
            ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSizeSize, keepAliveTime, TimeUnit.SECONDS, workQueue, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("hahaThread");
                    return thread;
                }
            });
            PrintNum num = new PrintNum();
            executor.execute(num);
            executor.execute(num);
        }
    }
    展开全文
  • 声明:原文出处...以下原作者为林炳文Evankaka的原创作品。转载出处:http://blog.csdn.net/evankakahttp://blog.csdn.net/evankaka/article/details/44153709#t1 (java线程学习的入门...

    声明:原文出处http://www.cnblogs.com/nsw2018/p/5822270.html,本人出于学习,收集干货,不作商业用途!


    以下原作者为林炳文Evankaka的原创作品。转载出处:http://blog.csdn.net/evankaka

    http://blog.csdn.net/evankaka/article/details/44153709#t1  (java线程学习的入门基础知识)

     

    http://blog.csdn.net/evankaka/article/details/51866242  (java线程并发与技术内幕  上)

     

    http://blog.csdn.net/evankaka/article/details/51705661  (ThreadFactory和ThreadLocal)

     

    http://blog.csdn.net/evankaka/article/details/51627380   (TheadGroup线程组应用)

     

    http://blog.csdn.net/evankaka/article/details/51610635  (Callable、Future、FutureTask、CompletionService这几个类的学习总结)

     

    http://blog.csdn.net/evankaka/article/details/51489322   (线程池的深入理解)

     

    http://blog.csdn.net/evankaka   (博主林炳文Evankaka

     

    http://blog.csdn.net/column/details/javahhighconcurrence.html  (汇总)

    展开全文
  • 说到Java线程大家应该都听说过,但真正了解和熟悉线程,却并不容易。从这篇文章开始,我将以自学和实践的方式,和大家一起学习线程的内容。本篇主要讲java线程的并发和忙等待。 2.内容: java线程最基本的两个内容...

    1.前序:

    说到Java线程大家应该都听说过,但真正了解和熟悉线程,却并不容易。从这篇文章开始,我将以自学和实践的方式,和大家一起学习线程的内容。本篇主要讲java线程的并发和忙等待。


    2.内容:

    java线程最基本的两个内容在这里提一下,那就是线程的创建以及生命周期。

    ①java线程的创建:可以通过继承Thread类或实现Runnable接口。

    ②线程的生命周期:线程的创建(初始化)→调用start方法(等待cpu分配资源)→得到资源后运行run方法→阻塞(可能被sleep,wait进入阻塞状态,可通过interrupt或notify唤醒)→线程死亡(线程内程序运行完毕后)。

    线程因为运行时,数据都是存在每个线程的各自的堆栈之中,而由于现在计算机的硬件结构原因,很多数据甚至是先存在线程各自CPU缓存和CPU寄存器中,所以在多个线程进行共享资源的写入问题上,就会出现所谓的线程安全问题。例如:

    package com.jokerchen.test;
    
    /**
     * 线程安全问题
     * @author Administrator
     *
     */
    public class ThreadDemo {
    	
    	private int count = 0;
    	
    	public int add()
    	{
    		return count++;
    	}
    	
    	class ActionThread extends Thread
    	{
    		public ActionThread(String name)
    		{
    			this.setName(name);
    		}
    		
    		@Override
    		public void run() {
    			// TODO Auto-generated method stub
    			int num = add();
    			System.out.println("当前线程为:"+Thread.currentThread().getName()+",现在count等于:"+count);
    		}
    	}
    	
    	public static void main(String[] args) {
    		ThreadDemo threadDemo = new ThreadDemo();
    		ActionThread at_one = threadDemo.new ActionThread("AA");
    		ActionThread at_two = threadDemo.new ActionThread("BB");
    		at_one.start();
    		at_two.start();
    	}
    	
    }
    
    (这里偷个懒,直接用的内部类来创建的线程类对象)结果为:


    很奇怪,这里出现了两种结果对不对。而这里就是因为线程数据的存储问题,因其中一个线程的改写数据,没有及时更新到主存中,导致其他的线程也没有识别更新后的数据。
    在说说并发和并行。个人理解:并发就是指多个线程在一时间段内通过cup资源的分配切换来运行;而并行则是指多个线程在同一时刻同时运行。就比如最经典的卖票实例:
    package com.jokerchen.test;
    
    public class ThreadTest {
    	
    	class Ticket implements Runnable
    	{
    		public int num = 100;
    		@Override
    		public void run() {
    			// TODO Auto-generated method stub
    			while(num > 0){
    				try {
    					Thread.currentThread().sleep(10);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				num--;
    				System.out.println("当前线程为:"+Thread.currentThread().getName()+",目前所剩票数为:"+num);
    			}
    			
    		}
    		
    	}
    	
    	public static void main(String[] args) {
    		Ticket tt = new ThreadTest().new Ticket();
    		new Thread(tt,"AA").start();
    		new Thread(tt,"BB").start();
    		
    	}
    	
    }
    
    结果如下:


    大家也能看到,出现了同号票和负数票。这就是在线程切换时所产生的并发问题。这是因为在线程切换期间,因为票数做减法操作时,还没来及的判断和打印,另外的线程就已经运行了。那么此时我们需要加入同步块来解决这种情况:
    class Ticket implements Runnable
    	{
    		public int num = 100;
    		@Override
    		public void run() {
    			// TODO Auto-generated method stub
    			while(true){
    				try {
    					Thread.currentThread().sleep(10);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				synchronized (this) {
    					num--;
    					if(num < 0) break;
    					System.out.println("当前线程为:"+Thread.currentThread().getName()+",目前所剩票数为:"+num);
    				}
    				
    			}
    			
    		}
    		
    	}
    那么此时的重票和负票情况,就得以解决了,结果如下:


    再来谈下线程的忙等待情形。忙等待即是一个线程在等待另外一个线程所给出的信号,而这个信号必然是在一个共享对象中存在的,这样才能达到多线程的信息共享。当等到信号之后才会进行下一步运行的情况。当然,这里也就涉及到了另外一种情况,如果一直等不到呢,那么就会进入我们常说的 死锁 状态。下面来看一下我所写的忙等待实例:
    实体类(共享对象)代码
    package com.jokerchen.test;
    
    public class MyProcess {
    	
    	private boolean flag = false;
    	
    	public synchronized boolean getProcess()
    	{
    		return this.flag;
    	}
    	
    	public synchronized void setProcess(boolean bool)
    	{
    		this.flag = bool;
    	}
    	
    }
    实现类
    package com.jokerchen.test;
    
    public class BusyWait {
    	
    	
    	/**
    	 * 忙等待
    	 */
    	public static void main(String[] args) {
    		//实例共享对象
    		final MyProcess mp = new MyProcess();
    		Thread thread1 = new Thread("AA"){
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				
    				try {
    					//睡眠五秒
    					Thread.currentThread().sleep(5000);
    					mp.setProcess(true);
    					System.out.println("当前线程为:"+Thread.currentThread().getName()+",并已设置共享对象信号属性flag为true!");
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		};
    		
    		Thread thread2 = new Thread("BB")
    		{
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				//等待其他线程的信号
    				while (true) {
    					//睡眠1秒
    					try {
    						Thread.currentThread().sleep(1000);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					if(mp.getProcess())
    					{
    						System.out.println("当前线程为:"+Thread.currentThread().getName()+",已经获得其他线程的信号!");
    						break;
    					}
    					else
    					{
    						System.out.println("当前线程为:"+Thread.currentThread().getName()+",正在等待其他线程的信号......");	
    					}
    				}
    			}
    		};
    		
    		//开始忙等待....
    		thread1.start();
    		thread2.start();
    	}
    	
    }
    
    运行结果如下:


    3.总结

    多线程使用起来,有时候能简化我们的运行程序。当然,在多数情况下,还是比单体运行程序要复杂一些,所以需要注意很多问题。因博主也是刚开始深入学习和熟悉多线程,所以文章中可能有不足或者有太正确的观点,希望大家在借鉴之时也能提出宝贵的意见,让我们一起学习一起进步。。

    展开全文
  • Java线程学习(一)Java多线程入门 Java线程学习(二)synchronized关键字(1) Java线程学习(二)synchronized关键字(2) Java线程学习(三)volatile关键字 Java线程学习(...

    部分图片无法访问请跳转自:https://juejin.im/post/5ab116875188255561411b8a 阅读。

    该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb/JavaGuide.

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79640870

    Java 并发的基础知识,可能会在笔试中遇到,技术面试中也可能以并发知识环节提问的第一个问题出现。比如面试官可能会问你:“谈谈自己对于进程和线程的理解,两者的区别是什么?”

    一 进程和多线程简介

    1.1 进程和线程

    进程和线程的对比这一知识点由于过于基础,所以在面试中很少碰到,但是极有可能会在笔试题中碰到。常见的提问形式是这样的:“什么是线程和进程?,请简要描述线程与进程的关系、区别及优缺点? ”。

    1.2 何为进程?

    进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe文件的运行)。

    进程

    1.3 何为线程?

    线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

    1.4 何为多线程?

    多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。

    1.5 为什么多线程是必要的?

    个人觉得可以用一句话概括:开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

    1.6 为什么提倡多线程而不是多进程?

    线程就是轻量级进程,是程序执行的最小单位。使用多线程而不是用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。

    二 几个重要的概念

    2.1 同步和异步

    同步和异步通常用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。

    关于异步目前比较经典以及常用的实现方式就是消息队列:在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。

    2.2 并发(Concurrency)和并行(Parallelism)

    并发和并行是两个非常容易被混淆的概念。它们都可以表示两个或者多个任务一起执行,但是偏重点有些不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。而并行是真正意义上的“同时执行”。

    多线程在单核CPU的话是顺序执行,也就是交替运行(并发)。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行(并行)。

    2.3 高并发

    高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

    高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。

    2.4 临界区

    临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。在并行程序中,临界区资源是保护的对象。

    2.5 阻塞和非阻塞

    非阻塞指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回,而阻塞与之相反。

    三 使用多线程常见的三种方式

    前两种实际上很少使用,一般都是用线程池的方式比较多一点。

    ①继承Thread类

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		System.out.println("MyThread");
    	}
    }
    

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		MyThread mythread = new MyThread();
    		mythread.start();
    		System.out.println("运行结束");
    	}
    
    }
    
    

    运行结果:

    运行结束
    MyThread
    

    从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。

    ②实现Runnable接口

    推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。

    MyRunnable.java

    public class MyRunnable implements Runnable {
    	@Override
    	public void run() {
    		System.out.println("MyRunnable");
    	}
    }
    

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		Runnable runnable=new MyRunnable();
    		Thread thread=new Thread(runnable);
    		thread.start();
    		System.out.println("运行结束!");
    	}
    
    }
    

    运行结果:

    运行结束
    MyThread
    

    ③使用线程池

    使用线程池的方式也是最推荐的一种方式,另外,《阿里巴巴Java开发手册》在第一章第六节并发处理这一部分也强调到“线程资源必须通过线程池提供,不允许在应用中自行显示创建线程”。这里就不给大家演示代码了,线程池这一节会详细介绍到这部分内容。

    四 实例变量和线程安全

    线程类中的实例变量针对其他线程可以有共享和不共享之分。下面通过两个简单的例子来说明!

    4.1 不共享数据的情况

    
    /**
     * 
     * @author SnailClimb
     * @date 2018年10月30日
     * @Description: 多个线程之间不共享变量线程安全的情况
     */
    public class MyThread extends Thread {
    
    	private int count = 5;
    
    	public MyThread(String name) {
    		super();
    		this.setName(name);
    	}
    
    	@Override
    	public void run() {
    		super.run();
    		while (count > 0) {
    			count--;
    			System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count);
    		}
    	}
    
    	public static void main(String[] args) {
    		MyThread a = new MyThread("A");
    		MyThread b = new MyThread("B");
    		MyThread c = new MyThread("C");
    		a.start();
    		b.start();
    		c.start();
    	}
    }
    

    运行结果:
    在这里插入图片描述
    可以看出每个线程都有一个属于自己的实例变量count,它们之间互不影响。我们再来看看另一种情况。

    4.2 共享数据的情况

    /**
     * 
     * @author SnailClimb
     * @date 2018年10月30日
     * @Description: 多个线程之间共享变量线程不安全的情况
     */
    public class SharedVariableThread extends Thread {
    	private int count = 5;
    
    	@Override
    	public void run() {
    		super.run();
    		count--;
    		System.out.println("由 " + SharedVariableThread.currentThread().getName() + " 计算,count=" + count);
    	}
    
    	public static void main(String[] args) {
    
    		SharedVariableThread mythread = new SharedVariableThread();
    		// 下列线程都是通过mythread对象创建的
    		Thread a = new Thread(mythread, "A");
    		Thread b = new Thread(mythread, "B");
    		Thread c = new Thread(mythread, "C");
    		Thread d = new Thread(mythread, "D");
    		Thread e = new Thread(mythread, "E");
    		a.start();
    		b.start();
    		c.start();
    		d.start();
    		e.start();
    	}
    }
    

    运行结果:

    在这里插入图片描述

    可以看出这里已经出现了错误,我们想要的是依次递减的结果。为什么呢??

    因为在大多数jvm中,count–的操作分为如下下三步:

    1. 取得原有count值
    2. 计算i -1
    3. 对i进行赋值

    所以多个线程同时访问时出现问题就是难以避免的了。

    那么有没有什么解决办法呢?

    答案是:当然有,而且很简单。给大家提供两种解决办法:一种是利用 synchronized 关键字(保证任意时刻只能有一个线程执行该方法),一种是利用 AtomicInteger 类(JUC 中的 Atomic 原子类)。大家如果之前没有接触 Java 多线程的话,可能对这两个概念不太熟悉,不过不要担心我后面会一一向你介绍到!这里不能用 volatile 关键字,因为 volatile 关键字不能保证复合操作的原子性。

    五 一些常用方法

    5.1 currentThread()

    返回对当前正在执行的线程对象的引用。

    5.2 getId()

    返回此线程的标识符

    5.3 getName()

    返回此线程的名称

    5.4 getPriority()

    返回此线程的优先级

    5.5 isAlive()

    测试这个线程是否还处于活动状态。

    什么是活动状态呢?

    活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。

    5.6 sleep(long millis)

    使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

    5.7 interrupt()

    中断这个线程。

    5.8 interrupted() 和isInterrupted()

    interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能

    isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志

    5.9 setName(String name)

    将此线程的名称更改为等于参数 name 。

    5.10 isDaemon()

    测试这个线程是否是守护线程。

    5.11 setDaemon(boolean on)

    将此线程标记为 daemon线程或用户线程。

    5.12 join()

    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是 主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了

    join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行

    5.13 yield()

    yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。

    5.14 setPriority(int newPriority)

    更改此线程的优先级

    六 如何停止一个线程呢?

    stop(),suspend(),resume()(仅用于与suspend()一起使用)这些方法已被弃用,所以我这里不予讲解。

    在这里插入图片描述

    6.1 使用interrupt()方法

    我们上面提到了interrupt()方法,先来试一下interrupt()方法能不能停止线程

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 5000000; i++) {
    			System.out.println("i=" + (i + 1));
    		}
    	}
    		public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    	}
    }
    

    运行上诉代码你会发现,线程并不会终止

    针对上面代码的一个改进:

    interrupted()方法判断线程是否停止,如果是停止状态则break

    
    /**
     * 
     * @author SnailClimb
     * @date 2018年10月30日
     * @Description: 使用interrupt()方法终止线程
     */
    public class InterruptThread2 extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 500000; i++) {
    			if (this.interrupted()) {
    				System.out.println("已经是停止状态了!我要退出了!");
    				break;
    			}
    			System.out.println("i=" + (i + 1));
    		}
    		System.out.println("看到这句话说明线程并未终止------");
    	}
    
    	public static void main(String[] args) {
    		try {
    			InterruptThread2 thread = new InterruptThread2();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    	}
    }
    

    运行结果:
    运行结果
    for循环虽然停止执行了,但是for循环下面的语句还是会执行,说明线程并未被停止。

    6.2 使用return停止线程

    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    			while (true) {
    				if (this.isInterrupted()) {
    					System.out.println("ֹͣ停止了!");
    					return;
    				}
    				System.out.println("timer=" + System.currentTimeMillis());
    			}
    	}
    	public static void main(String[] args) throws InterruptedException {
    		MyThread t=new MyThread();
    		t.start();
    		Thread.sleep(2000);
    		t.interrupt();
    	}
    
    }
    

    运行结果:

    在这里插入图片描述

    当然还有其他停止线程的方法,后面再做介绍。

    七 线程的优先级

    每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低
    优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。

    线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。

    线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。

    Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数1)Thread.NORM_PRIORITY(常数5),
    Thread.MAX_PRIORITY(常数10)。其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1)Thread.MAX_PRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)

    学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。

    线程优先级具有继承特性测试代码:

    MyThread1.java:

    public class MyThread1 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread1 run priority=" + this.getPriority());
    		MyThread2 thread2 = new MyThread2();
    		thread2.start();
    	}
    }
    
    

    MyThread2.java:

    public class MyThread2 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread2 run priority=" + this.getPriority());
    	}
    }
    

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		System.out.println("main thread begin priority="
    				+ Thread.currentThread().getPriority());
    		Thread.currentThread().setPriority(6);
    		System.out.println("main thread end   priority="
    				+ Thread.currentThread().getPriority());
    		MyThread1 thread1 = new MyThread1();
    		thread1.start();
    	}
    }
    
    

    运行结果:
    运行结果

    八 Java多线程分类

    8.1 多线程分类

    用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

    守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”

    特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

    应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程

    最常见的守护线程:垃圾回收线程

    8.2 如何设置守护线程?

    可以通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程

    注意事项:

    1.  setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
    2. 在守护线程中产生的新线程也是守护线程
    3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
    

    MyThread.java:

    public class MyThread extends Thread {
    	private int i = 0;
    
    	@Override
    	public void run() {
    		try {
    			while (true) {
    				i++;
    				System.out.println("i=" + (i));
    				Thread.sleep(100);
    			}
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.setDaemon(true);
    			thread.start();
    			Thread.sleep(5000);
    			System.out.println("我离开thread对象也不再打印了,也就是停止了!");
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    

    运行结果:
    守护线程

    展开全文
  • java线程的基础 线程创建与执行 线程的停止与中断 等待与唤醒

    Java线程思维记录图

    在这里插入图片描述

    java线程的基础

    Thread的几个方法和线程组

    展开全文
  • JAVA线程学习

    2017-08-01 21:37:42
    一、线程与进程1、线程:程序中单独顺序的控制流 线程本身是依靠程序进行运行,线程是程序中的顺序控制流,只能使用分配给程序的资源和环境。 2、进程:执行中的程序 一个进程可以包含一个或多个线程,即至少包含...
  • 1.分析一个Java线程的转储 为了可以理解/分析线程转储,首先要理解线程转储的各个部分。让我们先拿一个简单的线程堆栈为例,并且去了解他的每个部分。 "ExecuteThread: '1' " daemon prio=5 tid=0x628330 nid=0xf ...
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  • Java线程学习

    2017-04-25 10:53:12
    java线程 /* 通过对API查找,java已经提供了对这类事物的描述,就是Thread类 创建线程的第一种方式,继承Thread类 步骤: 1、定义类继承Thread 2、复写Thread类中的run方法 目的:将自定义的代码存储在run方法中...
  • 线程等待wait()和通知notify(),主要用于多线程之间的协作,而且这两个方法都是属于Object类,说明任何对象都可以调用这两个方法。 当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。直到另外的...
  • Java线程学习和总结

    2018-10-16 15:58:49
    线程Java的一大特色,从语言上直接支持线程线程对于进程来讲的优势在于创建的代价很小,上下文切换迅速,当然其他的优势还有很多,缺点也是有的,比如说对于开发人员来讲要求比较高,不容易操作,但是Java线程...
  • 线程学习路线图
  • 线程沉睡(sleep)并不会让线程释放它所持有的同步锁,而且在这期间也不会阻碍其他线程的运行。唤醒(interrupt)可以将沉睡或阻塞的线程唤醒。 线程沉睡:线程沉睡可以使当前线程沉睡一段时间,在这段时间内不会有时间...
  • Java 线程学习总结(一) —— 创建线程的三种方式 方式一:继承Thread,重写run()方法 以两个同学分别报数为例: /** * 方法一 * 继承Thread 重写run()方法 * @author Sirm * */ public class creat1{ ...
  • 最近在github上写了几个关于多线程的练习, 项目地址:https://github.com/jndf/multithreading-pratice ...这两种方法都是最基础的实现线程的方法,声明线程对象后,通过调用对象的start()方法,来执行线程内...
  • 基于JDK8演示了Java线程的wait与notify在多线程共享数据的用法,如何通过synchronized关键字实现对Java中的数据对象上锁
  • 线程的基本概念 进程以及使用环境 程序是计算机指令的集合,它以文件形式存储在磁盘上,而进程就是一个执行中的程序,每一个进程都有其独立的内存空间和系统资源。 进程就是一个运行的程序,Windows操作系统是支持多...
  • 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属于一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一...
  • java线程学习笔记

    2013-10-11 19:13:47
    守护线程:setDeamon(true);然后开启start();用于后台运行(垃圾回收线程), 默认生命周期是jvm停止运行前被清理。 线程中断:设置循环标志位实现中断while(!flag){}  ----但如果线程处于冻结状态就无法读取标记...
  • Java_Day12 多线程01 进程:正在进行中的程序(直译). 线程:就是进程中一个负责程序执行的控制单元(执行路径) 一个进程中可以多执行路径,称之为多线程。 一个进程中至少要有一个线程。 开启多个线程是为了...
1 2 3 4 5 ... 20
收藏数 269,851
精华内容 107,940