java多线程_java多线程详解 - CSDN
精华内容
参与话题
  • java多线程编程详细入门教程

    万次阅读 多人点赞 2018-10-31 15:10:49
    1、概念  ...线程是jvm调度的最小单元,也叫做轻量级进程,进程是由...这里提出一个问题,为什么要用多线程?有一下几点,首先,随着cpu核心数的增加,计算机硬件的并行计算能力得到提升,

    ##1、概念
         线程是jvm调度的最小单元,也叫做轻量级进程,进程是由线程组成,线程拥有私有的程序技术器以及栈,并且能够访问堆中的共享资源。这里提出一个问题,为什么要用多线程?有一下几点,首先,随着cpu核心数的增加,计算机硬件的并行计算能力得到提升,而同一个时刻一个线程只能运行在一个cpu上,那么计算机的资源被浪费了,所以需要使用多线程。其次,也是为了提高系统的响应速度,如果系统只有一个线程可以执行,那么当不同用户有不同的请求时,由于上一个请求没处理完,那么其他的用户必定需要在一个队列中等待,大大降低响应速度,所以需要多线程。这里继续介绍多线程的几种状态:
    这里写图片描述
    这里可以看到多线程有六种状态,分别是就绪态,运行态,死亡态,阻塞态,等待态,和超时等待态,各种状态之间的切换如上图所示。这里的状态切换是通过synchronized锁下的方法实现,对应的Lock锁下的方法同样可以实现这些切换。

    ##2、线程的创建
         线程的创建有两种方式,第一种是继承Thread类,第二种是实现Runnable接口。第一个代码:

    class MyThread extends Thread{
    	int j=20;
    	public void run(){
    		for (int i = 0; i < 20; i++) {
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			System.out.println(this.getName()+",i="+j--);
    		}
    	}
    }
    

    然后main函数中创建:

    class MyThread extends Thread{
    	int j=20;
    	public void run(){
    		for (int i = 0; i < 20; i++) {
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			System.out.println(this.getName()+",i="+j--);
    		}
    	}
    }
    

    第二种方法:

    class MyRunnable implements Runnable{
    	int j=20;
    	@Override
    	public void run() {
    		for (int i = 0; i < 20; i++) {
    			System.out.println(Thread.currentThread().getName()+",j="+this.j--);
    		}
    	}
    	
    }
    

    main函数中:

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

    这就是两种创建线程的方法,在这两种方法中第二种方法时一般情况下的用法,因为继承只能继承一个类,但是可以实现多个接口,这样拓展性更好。

    ##3、线程安全测试
         线程安全是多线程编程中经常需要考虑的一个问题,线程安全是指多线程环境下多个线程可能会同时对同一段代码或者共享变量进行执行,如果每次运行的结果和单线程下的结果是一致的,那么就是线程安全的,如果每次运行的结果不一致,那么就是线程不安全的。这里对线程安全做一个测试:

    class MyRunnable implements Runnable{
    	static int j=10000;
    	@Override
    	public void run() {
    		for (int i = 0; i < 5000; i++) {
    			System.out.println(j--);
    		}
    	}
    	
    }
    

    main函数中:

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

    可以看到,这里同时两个线程同时对共享变量j进行访问,并且减1,但最后的输出结果却是:

    48
    47
    46
    

    并且多次执行程序的结果还不一致,这就是线程不安全的情况,通过加锁可以保证线程安全。

    ##4、锁
         java中有两种锁,一种是重量级锁synchronized,jdk1.6经过锁优化加入了偏向锁和轻量级锁,一种是JUC并发包下的Lock锁,synchronized锁也称对象锁,每个对象都有一个对象锁。这里通过加锁的方式实现线程安全:
    代码:

    class MyRunnable implements Runnable{
    	static int j=10000;
    	@Override
    	public synchronized void run() {
    		for (int i = 0; i < 5000; i++) {
    			System.out.println(j--);
    		}
    	}
    	
    }
    

    main中创建两个线程,测试多次的结果都是:

    3
    2
    1
    

    说明实现的线程安全,因为当加锁过后,每次只能有一个线程访问被加锁的代码,这样就不会出现线程安全了。

    ##5、sleep
         sleep是让当前线程睡眠,睡眠一段时间后重新获取到cpu的使用权。
    代码如下:

    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    

    这里表示线程会睡眠100ms后再次到就绪状态中,这里为什么sleep是Thread的类方法而不是线程的方法,因为,能调用sleep的线程肯定是运行着的,而其他线程也是未运行的,所以调用其他线程的sleep是没有意义的。

    ##6、wait、notify
         wait表示当前线程释放掉锁进入等待状态,所以调用wait和notify的前提是已经获取到对象的锁,如果没有获取到锁就使用wait那么会出异常信息。而进入等待状态的线程需要通过notify或者通过中断来唤醒进入到阻塞状态来等待锁的获取。这里对这种情况进行测试,使用notify唤醒一个等待状态的线程:

    class MyThreadd extends Thread{
    	public MyThreadd(String name) {
    		// TODO Auto-generated constructor stub
    		super(name);
    	}
    	public void run(){
    		synchronized (this) {
    			System.out.println(Thread.currentThread().getName()+" notify a thread");
    			notify();
    		}
    		while(true);
    	}
    

    main中:

    	MyThreadd t1 = new MyThreadd("t1");
    	synchronized (t1) {
    		System.out.println(Thread.currentThread().getName()+" start t1");
    		t1.start();
    		System.out.println(Thread.currentThread().getName()+" wait");
    		t1.wait();
    		System.out.println(Thread.currentThread().getName()+" waked up");
    	}
    

    这里可以看到,在main函数中,主线程将创建一个线程t1然后进入t1的锁的同步块中启动线程t1,然后调用wait进入等待状态,这个时候线程t1也进入到同步块中,调用notify后释放掉锁,可以看到主线程后续的东西继续被输出。当有多个线程调用了wait之后如果采用notify只会随机的唤醒其中的一个线程进入阻塞态,而采用notifyall会将所有的线程给唤醒。在线程运行结束后会调用notifyall将所有等待状态的线程唤醒。

    ##7、join
         join的作用是让父线程等待子线程运行结束后在运行,通过查看源码可以知道:
    这里写图片描述
    其实也是调用了先获取到子线程的锁然后调用wait方法来实现的,因为当子线程运行结束后会调用notifyall所以主线程会被唤醒并且再次获取到子线程的锁继续运行。

    class MyRuu extends Thread{
    	public MyRuu(String name) {
    		super(name);
    	}
    	public void run() {
    		System.out.println(Thread.currentThread().getName());
    		//while(true);
    	}
    }
    

    main函数中:

    		MyRuu myRuu = new MyRuu("t1");
    		System.out.println(Thread.currentThread().getName()+" start t1");
    		myRuu.start();
    		System.out.println(Thread.currentThread().getName() +" join");
    		myRuu.join();
    		System.out.println(Thread.currentThread().getName() +" waked up");
    

    运行结果:

    main start t1
    main join
    t1
    main waked up
    

    可以看到,当主线程调用join后子线程开始运行,等子线程运行结束后主线程被唤醒。

    ##8、yeild
         yeild的作用是线程让步,当前线调用yeild方法后线程从运行态进入到就绪态重新进入到CPU资源的竞争中。这里进行测试:

    class MyRun extends Thread{
    	Object obj;
    	public MyRun(String name,Object obj) {
    		// TODO Auto-generated constructor stub
    		super(name);
    		this.obj = obj;
    	}
    	public void run(){
    //		synchronized (obj) {
    			for (int i = 0; i < 10; i++) {
    				System.out.println(Thread.currentThread().getName()+ " i="+i);
    				if(i%2 == 0)
    					Thread.yield();
    			}
    //		}
    	}
    }
    

    main函数中:

    		Object obj = new Object();
    		// TODO Auto-generated method stub
    		MyRun t1 = new MyRun("t1",obj);
    		MyRun t2 = new MyRun("t2",obj);
    		t1.start();
    		t2.start();
    

    结果:

    t1 i=0
    t2 i=0
    t1 i=1
    t2 i=1
    t2 i=2
    t1 i=2
    t2 i=3
    t2 i=4
    t1 i=3
    t1 i=4
    t2 i=5
    t2 i=6
    t1 i=5
    t1 i=6
    t2 i=7
    t2 i=8
    t1 i=7
    t1 i=8
    t2 i=9
    t1 i=9
    

    可以看到他们两个基本上是交替运行,而不用yeild让步的话大概率一个线程执行完成了另一个线程才会执行。

    ##9、priority
         priority代表线程的优先级,在JVM中优先级高的线程不一定会先运行,只是先运行的概率会比低优先级的线程大。

    ##10、中断
         对于一个正常运行的线程中,中断基本是没有作用的,只是作为一个标志位来查询。而线程的其他几种状态下如sleep、join、wait状态下是可以被中断,并且通过中断来跳出当前状态的。

    class RunInt extends Thread{
    	public void run() {
    		while(!this.isInterrupted()){
    			synchronized (this) {
    				for (int i = 0; i < 10; i++) {
    					System.out.println(Thread.currentThread().getName()+" i="+i);
    				}
    				try {
    					Thread.sleep(3000);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					System.out.println(Thread.currentThread().getName()+" interrupted!");
    					break;
    				}
    			}
    		}
    		System.out.println(Thread.currentThread().getName()+" dead");
    	}
    }
    

    main中:

    		RunInt r1 = new RunInt();
    		r1.start();
    		Thread.yield();
    		synchronized (r1) {
    			System.out.println(Thread.currentThread().getName()+" intertupt r1");
    			r1.interrupt();
    		}
    

    结果

    main intertupt r1
    Thread-0 i=0
    Thread-0 i=1
    Thread-0 i=2
    Thread-0 i=3
    Thread-0 i=4
    Thread-0 i=5
    Thread-0 i=6
    Thread-0 i=7
    Thread-0 i=8
    Thread-0 i=9
    Thread-0 interrupted!
    Thread-0 dead
    

    可以看到,当主线程启动子线程后,子线程会进入到循环中并且进入到睡眠状态,然后主线程通过调用中断让子线程唤醒并且推出循环后死亡。

    ##11、死锁
         死锁指的是,两个线程互相等待对方释放资源导致卡死。例子:

    		Thread t1 = new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				synchronized (A) {
    					try {
    						Thread.sleep(1000);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					synchronized (B) {
    						System.out.println("haha");
    					}
    				}
    			}
    		});
    		Thread t2 = new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				synchronized (B) {
    					synchronized (A) {
    						System.out.println("xixi");
    					}
    				}
    			}
    		});
    		t1.start();
    		t2.start();
    

    可以看到t1线程获得A的锁然后睡眠,然后t2线程获得B的锁然后再等待A释放锁,而线程t1睡眠完成后在等待t2释放B的锁,导致程序卡死。

    ##12、生产者与消费者
         生产者和消费者是多线程中一个很常见的应用场景,这里首先用一个共享变量实现生产者和消费者,接着再使用阻塞队列实现。首先实现第一种:
    仓库代码:

    class Depot{
    	private int capacity;
    	private int size=0;
    	public Depot(int c) {
    		// TODO Auto-generated constructor stub
    		this.capacity = c;
    	}
    	public synchronized void product(int count) throws InterruptedException{
    		while(count>0){
    			if(size >= capacity)
    				wait();
    			//真实生产数量
    			int realcount = (capacity-size)>=count?count:(capacity-size);
    			System.out.print(Thread.currentThread().getName()+"--本次想要生产:"+count+",本次实际生产:"+realcount);
    			//下次生产数量
    			count = count - realcount;
    			//仓库剩余
    			size += realcount;
    			System.out.println(",下次想要生产:"+count+",仓库真实容量:"+size);
    			notifyAll();
    		}
    	}
    	public synchronized void comsume(int count) throws InterruptedException {
    		while(count>0){
    			if(size <= 0)
    				wait();
    			//真实消费数量
    			int realcount = (size>=count)?count:size;
    			System.out.print(Thread.currentThread().getName()+"--本次想要消费:"+count+",本次真实消费:"+realcount);
    			//下次消费数量
    			count = count - realcount;
    			//仓库剩余
    			size -= realcount;
    			System.out.println("下次想要消费:"+count+",仓库剩余:"+size);
    			notify();
    		}
    	}
    }
    

    生产者代码:

    class Producer {
    	Depot depot;
    	public Producer(Depot depot) {
    		// TODO Auto-generated constructor stub
    		this.depot = depot;
    	}
    	public void produce(final int count) {
    		new Thread(){
    			public void run() {
    				try {
    					depot.product(count);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		}.start();
    	}
    }
    

    消费者代码:

    class Consumer{
    	Depot depot;
    	public Consumer(Depot depot) {
    		// TODO Auto-generated constructor stub
    		this.depot = depot;
    	}
    	public void consume(final int count) {
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				try {
    					depot.comsume(count);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		}).start();
    	}
    }
    

    main中:

    		Depot depot = new Depot(100);
    		Producer producer = new Producer(depot);
    		Consumer consumer = new Consumer(depot);
    		producer.produce(60);
    		producer.produce(50);
    		producer.produce(30);
    		consumer.consume(50);
    		consumer.consume(110);
    		producer.produce(40);
    

    结果:

    Thread-0--本次想要生产:60,本次实际生产:60,下次想要生产:0,仓库真实容量:60
    Thread-1--本次想要生产:50,本次实际生产:40,下次想要生产:10,仓库真实容量:100
    Thread-4--本次想要消费:110,本次真实消费:100下次想要消费:10,仓库剩余:0
    Thread-1--本次想要生产:10,本次实际生产:10,下次想要生产:0,仓库真实容量:10
    Thread-4--本次想要消费:10,本次真实消费:10下次想要消费:0,仓库剩余:0
    Thread-5--本次想要生产:40,本次实际生产:40,下次想要生产:0,仓库真实容量:40
    Thread-2--本次想要生产:30,本次实际生产:30,下次想要生产:0,仓库真实容量:70
    Thread-3--本次想要消费:50,本次真实消费:50下次想要消费:0,仓库剩余:20
    

    可以看到实现了生产者消费者模型。
    第二种利用阻塞队列实现。直接利用阻塞队列当做仓库,生产者:

    class Pro1{
    	private BlockingQueue<Integer> blockingQueue1;
    	public Pro1(BlockingQueue<Integer> blockingQueue) {
    		// TODO Auto-generated constructor stub
    		this.blockingQueue1 = blockingQueue;
    	}
    	public void produce(final int count) {
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				for (int i = 0; i < count; i++) {
    					try {
    						Thread.sleep(100);
    						blockingQueue1.put(100);
    						System.out.println("生产者,仓库剩余容量"+blockingQueue1.size());
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}
    			}
    		}).start();
    
    	}
    }
    

    消费者:

    class Con1{
    	private BlockingQueue<Integer> blockingQueue;
    	public Con1(BlockingQueue<Integer> blockingQueue) {
    		// TODO Auto-generated constructor stub
    		this.blockingQueue = blockingQueue;
    	}
    	public void consume(final int count) {
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				for (int i = 0; i < count; i++) {
    					try {
    						Thread.sleep(100);
    						blockingQueue.take();
    						System.out.println("消费者,本次仓库剩余:"+blockingQueue.size());
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}
    			}
    		}).start();
    	}
    }
    

    main函数:

    		BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(5);
    		Pro1 pro1 = new Pro1(blockingQueue);
    		Con1 con1 = new Con1(blockingQueue);
    		pro1.produce(10);
    		con1.consume(7);
    

    结果:

    消费者,本次仓库剩余:0
    生产者,仓库剩余容量0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量0
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    消费者,本次仓库剩余:0
    生产者,仓库剩余容量1
    生产者,仓库剩余容量2
    生产者,仓库剩余容量3
    
    

    这里阻塞队列的作用是,当容量不足的消费者进入等待队列,而当容量有剩余的时候消费者被唤醒,当容量已满的时候生产者进入等待队列,当容量被消费后生产者被唤醒。

    展开全文
  • java多线程编程】三种多线程的实现方式

    万次阅读 多人点赞 2019-01-02 08:49:46
    文章目录前言进程与线程继承Thread类,实现多线程FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?...在java语言最大的特点是支持多线程的开发(也是为数不多...

    前言

          在java语言最大的特点是支持多线程的开发(也是为数不多支持多线程的编程语言),所以在整个的java技术的学习里面,如果你不能够对多线程的概念有一个全面并且细致的了解,则在日后进行一些项目设计的过程中尤其是并发访问设计过程之中就会出现严重的技术缺陷。

          如果要想理解我们的线程,那么首先就需要了解一下进程的概念,在传统的DOS系统的时代,其本身有特征:如果你的电脑上出现了病毒,那么我们所有的程序无法执行,因为传统的DOS采用的是单进程处理,而单进程处理的最大特点:在同一时间段上只允许一个程序在执行。

          那么后来到了windows的时代就开启了多进程的设计,于是就表示在一个时间段上可以同时运行多个程序,并且这些程序将进行资源的轮流抢占。所以在同一时段上会有多个程序依次执行,但是在同一个时间点上只会有一个进行执行,而后来到了多核的CPU,由于可以处理的CPU多了,那么即便有再多的进程出现,也可以比单核CPU处理的速度有多提升。

    Java是多线程编程语言,所以Java在进行并发访问处理的时候,可以得到更高的处理性能。

    进程与线程

          如果想要在java之中实现多线程的定义,那么就需要有一个专门的线程的主体类进行线程的执行任务的定义,
    而这主体类的定义是有要求的,必须实现特定的接口或者继承特定的父类才可以完成。

    继承Thread类,实现多线程

          java里面提供了一个java.lang.Thread的程序类,那么一个类只要继承了此类就表示这个类为线程的主体类,
    但是并不是说这个类就可以实现多线程处理了,因为还需要覆写Thread类中提供的一个run()方法(public void run()),而这个方法就属于线程的主方法。
    范例:

    class MyThread extends Thread {//线程主体类
        private String title;
        public MyThread(String title) {
            this.title = title;
        }
        @Override
        public void run() {//线程的主体方法
            for(int x = 0; x < 10 ; x++) {
                System.out.println(this.title + "运行,x = " + x);
            }
        }
    }
    

          多线程要执行的功能,都应该在run()方法中进行定义,但是需要说明的是:
    在正常情况下,如果要想使用一个类中的方法,那么肯定要产生实例化对象,而后去调用类中提供的方法,但是run()方法不能直接调用的,因为这牵扯到一个操作系统的资源调度问题,所以要想启动多线程必须使用start()方法。
    范例:

    public class ThreadDemo {
        public static void main(String[] args) {
            new MyThread("线程A").start();
            new MyThread("线程B").start();
            new MyThread("线程C").start();
        }
    }
    
    

          通过此时的调用你可以发现,虽然调用了start()方法,但是最终执行的run()方法,并且所有的线程对象都是交替执行的。执行顺序不可控。

    FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?

          如果想清楚这个问题,最好的做法是查看一下start()方法的实现操作,可以直接通过源代码观察。

      public synchronized void start() {
    
            if (threadStatus != 0) // 判断线程的状态
                throw new IllegalThreadStateException();  // 抛出一个异常
            group.add(this);
            boolean started = false;
            try {
                start0();   //  在start()中调用了start0()
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                }
            }
        }
     private native void start0();           
    

          发现start()方法里面会抛出一个"IllegalThreadStateException()"异常类对象,但整个程序并没有进行try…catch处理,因为该异常一定是RuntimeException的子类,每一个线程类的对象只允许启动一次,如果重复启动就会抛出异常,例如:下面的代码就会抛出异常。

    public class ThreadDemo {
        public static void main(String[] args) {
            MyThread mt = new MyThread("线程A");
            mt.start();
            mt.start();  //重复进行线程的启动
        }
    }
    
    Exception in thread "main" java.lang.IllegalThreadStateException
    

          在JAVA程序执行的过程之中考虑到对于不同层次开发者的需求,所以其支持有本地的操作系统函数调用,而这项技术就被称为JNI(Java Native Inteface)技术,但是JAVA开发过程之中并不推荐这样使用,利用这项技术可以使用一些操作系统提供的底层函数进行一些特殊的处理,而在Thread类里面提供了start()就表示需要将此方法依赖于不同的操作系统实现。
    在这里插入图片描述
    任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread类中的start()方法。

    基于Runnable接口实现多线程

          虽然可以通过Thread类的继承来实现多线程的定义,但是在Java程序里面对于继承永远都是存在有单继承局限的,所以在Java里面又提供有第二种多线程的主体定义结构形式:实现java.lang.Runnable接口,此接口定义如下:

    @FunctionalInterface    // 从JDK1.8引入了Lambda 表达式之后就变为了函数式接口
    public interface Runnable {
      public void run();
    }
    

    范例:通过Runnable 实现多线程的主体类

    class MyThread implements Runnable {//线程主体类
        private String title;
        public MyThread(String title) {
            this.title = title;
        }
        @Override
        public void run() {//线程的主体方法
            for(int x = 0; x < 10 ; x++) {
                System.out.println(this.title + "运行,x = " + x);
            }
        }
    }
    

          但是此时由于不在继承Thread父类了,那么对于此时的MyThread类中也就不在支持有start()这个继承方法,可是不使用Thread.start()方法是无法进行多线程启动的,那么这个时候就需要去观察一下Thread类所提供的构造方法了。
    - 构造方法:public Thread(Runnable target);
    范例:启动多线程

    public class ThreadDemo {
        public static void main(String[] args) {
            Thread threadA = new Thread(new MyThread("线程A"));
            Thread threadB = new Thread(new MyThread("线程B"));
            Thread threadC = new Thread(new MyThread("线程C"));
            threadA.start();
            threadB.start();
            threadC.start();
        }
    }
    
    

          这个时候的多线程实现里面可以发现,由于只是实现了Runnable接口对象,所以此时线程主体类上不再有单继承局限了,那么这样的设计才是一个标准的设计。
          可以发现从JDK1.8开始,Runnable接口使用了函数式接口定义,所以也可以直接利用Lambda表达式进行线程类的实现定义。
    范例:利用Lambda实现多线程定义

    public class ThreadDemo {
        public static void main(String[] args) {
            for( int x = 0; x < 3 ; x ++) {
                String title = "线程对象-" + x;
                    Runnable runnable = () ->{
                        for(int y = 0; y < 10 ; y ++) {
                            System.out.println(title + "运行,y = " + y);
                    }
                };
                    new Thread(runnable).start();
            }
        }
    }
    

    在以后的开发中对于多线程的实现,优先考虑Runnable接口实现,并且永恒都是通过Thread类对象启动多线程。

    Thread 与 Runnable 的关系

          经过一系列的分析之后可以发现,在多线程的实现过程之中已经有了两种做法:Thread类、Runnable接口,如果从代码的结构本身来讲肯定使用Runnable是最方便的,因为其可以避免单继承的局限,同时也可以更好的进行功能的扩充。

          但是从结构上也需要观察Thread与Runnable的联系,打开Thread类的定义:

     public class Thread extends Object implements Runnable{}
    

    发现Thread类也是Runnable 接口的子类,那么在之前继承Thread类的时候实际上覆写的还是Runnable的方法。
    在这里插入图片描述
          多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交给Thread类来处理。

          在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法。当通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread中target的属性保存,在start()方法执行的时候会调用Thread类中的run方法,而这个run()方法去调用Runnable接口子类被覆写过的run()方法。

          多线程开发的本质实质上是在于多个线程可以进行统一资源的抢占,那么Thread主要描述的是线程,那么资源的描述是通过Runnable完成的
    在这里插入图片描述

    范例:利用卖票程序来实现多个线程的资源并发访问。

    class MyThread1 implements  Runnable {
       private int ticket = 5;
        @Override
        public void run() {
            for( int x = 0 ; x < 100 ; x ++) {
                 if(this.ticket > 0) {
                     System.out.println("卖票,ticket = " +this.ticket --);
                 }
            }
        }
    }
    public class ThreadTest {
        public static void main(String[] args) {
         MyThread1 mt = new MyThread1();
         new Thread(mt).start();
         new Thread(mt).start();
         new Thread(mt).start();
        }
    }
    
    

    通过内存分析图来分析本程序的执行结构。
    在这里插入图片描述

    Callable实现多线程

          从最传统的开发来讲如果要进行多线程的实现肯定依靠的就是Runnable,但是Runnable接口有一个缺点:当线程执行完毕后,我们无法获取一个返回值,所以从JDK1.5之后就提出了一个新的线程实现接口:java.util.concurrent.Callable接口。首先观察这个接口的定义:

    @FunctionalInterface
    public interface Callable<V> {
     public V call() throws Exception;
    }
    

          可以发现Callbale定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的的好处是可以避免向下转行所带来的安全隐患。
    在这里插入图片描述
    范例:Callable多线程的实现

    class MyThread2 implements Callable<String> {
        @Override
        public String call() throws Exception {
            for ( int x = 0 ; x < 10 ; x ++ ) {
                System.out.println("******线程执行,x = " + x);
            }
            return "线程执行完毕!";
        }
    }
    public class demo {
        public static void main(String[] args) throws Exception{
            FutureTask futureTask = new FutureTask(new MyThread2());
            new Thread(futureTask).start();
            System.out.println("线程返回值:" + futureTask.get());
        }
    }
    

    面试题:请解释Runnable 与 Callable的区别:

    • Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
    • java.lang.Runnable 接口之中只提供了一个run()方法,并且没有返回值;
    • java.util.concurrent.Callable接口提供有call(),可以有返回值;

    线程运行状态

          对于多线程的开发而言,编写程序的过程之中总是按照:定义线程主体类,而后通过Thread类进行线程的启动,但是并不意味着你调用了start()方法,线程就已经开始运行了,因为整体的线程处理有自己的一套运行的状态。

    在这里插入图片描述
    1、任何一个线程的对象都应该使用Thread类进行封装,所以线程的启动使用的是start(),但是启动的时候实际上若干个线程都将进入到一种就绪状态,现在并没有执行。
    2、进入到就绪状态之后就需要等待进行资源调度,当某一个线程调度成功之后则进入到运行状态(run()方法),但是所有的线程不可能一直持续执行下去,中间需要产生一些暂停状态,例如:某个线程执行一段时间之后就需要让出资源,而后这个线程就进入到阻塞状态,随后重新回归到就绪状态;
    3、当run()方法执行完毕之后,实际上该线程的主要任务也就结束了,那么此时就可以直接进入到停止状态。

    另外:start()方法是准备执行,真正的执行要看操作系统的脸色。

    展开全文
  • Java多线程之进阶篇(一)

    万次阅读 多人点赞 2018-07-30 14:50:03
    在学习完Java多线程之基础篇(一)和Java多线程之基础篇(二)后接下来开始学习Java多线程之进阶篇的内容。 Java 5 添加了一个新的包到Java平台,这个包是java.util.concurrent包(简称JUC)。这个包包含了有一系列...

    在学习完Java多线程之基础篇(一)Java多线程之基础篇(二)后接下来开始学习Java多线程之进阶篇的内容。
    Java 5 添加了一个新的包到Java平台,这个包是java.util.concurrent包(简称JUC)。这个包包含了有一系列能够让Java的并发编程更加轻松的类。本文使用的Java 7 版本的JUC,下面让我们继续来学习吧!

    一、线程池

    提到线程线程池我们先来说一下线程池的好处,线程池的有点大概可以概括三点:
    (1)重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
    (2)能有效控制线程池的最大并发数,避免大量线程之间因互相抢夺系统资源而导致的阻塞现象。
    (3)能够对线程进行简单的管理,并提供定时执行以及指向间隔循环执行等功能。

    1.1 线程池的创建

      Java SE 5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。Executor允许你管理异步任务的执行,而无须显式的管理线程的生命周期。Executor在Java中启动任务的优选方法。

    public class CachedThreadPool {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
    
            class MyRunnable implements Runnable{
                private int a = 5;
                @Override
                public void run() {
                    synchronized(this){
                        for(int i=0;i<10;i++){
                            if(this.a>0){
                                System.out.println(Thread.currentThread().getName()+" a的值:"+this.a--);
                            }
    
                        }
                    }
                }
    
            }
            ExecutorService exec = Executors.newCachedThreadPool();
            for(int i=0;i<5;i++)
                exec.execute(new MyRunnable());
            exec.shutdown();
    
        }
    
    }
    

    运行结果:

    pool-1-thread-2 a的值:5
    pool-1-thread-1 a的值:5
    pool-1-thread-1 a的值:4
    pool-1-thread-1 a的值:3
    pool-1-thread-3 a的值:5
    pool-1-thread-2 a的值:4
    pool-1-thread-1 a的值:2
    pool-1-thread-1 a的值:1
    pool-1-thread-2 a的值:3
    pool-1-thread-2 a的值:2
    pool-1-thread-2 a的值:1
    pool-1-thread-3 a的值:4
    pool-1-thread-3 a的值:3
    pool-1-thread-3 a的值:2
    pool-1-thread-3 a的值:1
    pool-1-thread-5 a的值:5
    pool-1-thread-5 a的值:4
    pool-1-thread-5 a的值:3
    pool-1-thread-5 a的值:2
    pool-1-thread-5 a的值:1
    pool-1-thread-4 a的值:5
    pool-1-thread-4 a的值:4
    pool-1-thread-4 a的值:3
    pool-1-thread-4 a的值:2
    pool-1-thread-4 a的值:1
    

    说明:
    这个结果可以和Java多线程之基础篇(一)3.2.1定义任务(Runnable)的例子和结果做对比。发现用Executor来管理时,Runnable中的“资源不在共享”,这个疑问我还没有解决?知道的可以告诉我一声。
    ExecutorService是一个接口,并继承了接口Executor。而Executors是一个工具类,下面来看看它们之间的UML图:
    这里写图片描述
    其中最为主要的是ThreadPoolExecutor类和Executors中的四类方法,下面我们来逐个分析。

    1.1.1 ThreadPoolExecutor

    (1)ThreadPoolExecutor简介
      ThreadPoolExecutor是线程池类。对于线程池,可以通俗的将它理解为“存放一定数量的一个线程集合。线程池允许若个线程同时运行,运行同时运行的线程数量就是线程池的容量。当添加到线程池中的线程超过它的容量时,会有一部分线程阻塞等待,线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理。”
    (2)ThreadPoolExecutor的数据结构
    这里写图片描述
    下面是ThreadPoolExecutor类中比较典型的部分代码:

    public class ThreadPoolExecutor extends AbstractExecutorService {
        // 阻塞队列。
        private final BlockingQueue<Runnable> workQueue;
        // 互斥锁
        private final ReentrantLock mainLock = new ReentrantLock();
        // 线程集合。一个Worker对应一个线程。
        private final HashSet<Worker> workers = new HashSet<Worker>();
        // “终止条件”,与“mainLock”绑定。
        private final Condition termination = mainLock.newCondition();
        // 线程池中线程数量曾经达到过的最大值。
        private int largestPoolSize;
        // 已完成任务数量
        private long completedTaskCount;
        // ThreadFactory对象,用于创建线程。
        private volatile ThreadFactory threadFactory;
        // 拒绝策略的处理句柄。
        private volatile RejectedExecutionHandler handler;
        // 保持线程存活时间。
        private volatile long keepAliveTime;
    
        private volatile boolean allowCoreThreadTimeOut;
        // 核心池大小
        private volatile int corePoolSize;
        // 最大池大小
        private volatile int maximumPoolSize;
    
        //构造方法
       public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }
    }

    对一些关键的变量进行介绍:

    • workers
      workers是HashSet类型,它是一个Worker集合。而一个Worker对应一个线程,也就是说线程池通过workers包含了“一个线程集合”。当Worker对应的线程池启动时,它会执行线程池中的任务;当执行完一个任务后,它会从线程池的阻塞队列中取出一个阻塞的任务来继续运行。workers的作用是:线程池通过它来实现了“允许多个线程同时运行”。
    • workQueue
      workQueue是BlockingQueue类型,它是一个阻塞队列。当线程池中的线程超过它的容量的时候,线程会进入阻塞队列进行阻塞等待。workQueue的作用是:让线程池实现 了阻塞功能。
    • mainLock
      mainLock是互斥锁,通过mainLock实现了对线程池的互斥访问。
    • corePoolSize和maximumPoolSize
      corePoolSize是“核心池大小”,maximumPoolSize是“最大池大小”。它们的作用是:调整“线程池中实际运行的线程的数量”。
      例如,当新任务提交给线程池时(通过execute方法)。
      ——如果此时,线程池中运行的线程数量 小于 corePoolSize;则仅当阻塞队列满时才创建新线程。
      ——如果此时,线程池中运行的线程数量 大于 corePoolSize,但却是 小于 maximumPoolSize;则仅当阻塞队列慢时才创建新线程。
      ——如果此时,corePoolSize和maximumPoolSize相同,则创建了固定大小的线程池。如果maximumPoolSize设置为基本的无界值(如,Integer.MAX_VALUE),则允许线程池适应任意数量的并发任务。在大多数情况下,核心池大小和最大池大小的值在创建线程池设置的。但是,也可以使用setCorePoolSize(int)和setMaximumPoolSize(int)进行动态更改。
    • poolSize
      poolSize是当前线程池的实际大小,即线程池中任务的数量。
    • allowCoreThreadTimeOut和keepAliveTime
      allowCoreThreadTimeOut表示是否允许“线程在空闲状态时,仍然能够存活”。
      keepAliveTime表示线程池处于空闲状态的时候,超过keepAliveTime时间之后,空闲的线程会被终止。
    • threadFactory
      threadFactory是ThreadFactory对象,它是一个线程工厂类,即“线程池通ThreadFactory创建线程”
    • handler
      handler是RejectedExecutionHandler类型。它是“线程池拒绝策略”的句柄,也就是说“当某任务添加到线程池中,而线程池拒绝任务是,线程池会通过handler进行相应的处理”

    综上所述,线程池通过workers来管理“线程集合”,每个线程在启动后,会执行线程池中的任务;当一个任务执行完后,它会从线程池的阻塞队列中取出任务来继续运行。阻塞队列时管理线程池任务的队列,当添加到线程池中的任务超过线程池的容量时,该任务就会进入阻塞队列进行等候。

    1.1.2 线程池的分类

      ExecutorService是Executor直接的扩展接口,也是最常用的线程池接口,我们通常见到的线程池定时任务线程池都是它的实现类。上面的Executors.newCachedThreadPool();中的Executors还有其他静态方法可以调用,每个方法都有不同特性,它们都是直接或间接的通过配置ThreadPoolExecutor来实现自己的功能特性,这四类线程池分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor。

    (1)FixedThreadPool
      通过Executor的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程池处于空闲状态时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于FixedThreadPool只有核心线程线程并且这些核心线程不会被回收,这意味着它能过更加快速的相应外界的请求。newFixedThreadPool方法的实现如下,可以发现FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务队列也是没有大小限制的。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

      newFixedThreadPool()在调用ThreadPoolExecutor()时,它传递一个LinkedBlockingQueue()对象,而LinkedBlockingQueue是单向链表实现的阻塞队列。在线程池中,就是通过该阻塞队列来实现“当线程池中任务数量超过允许的任务数量时,部分任务会阻塞等待”。关于LinkedBlockingQueue的实现细节,在后续的文章会继续介绍。
      有了FixedThreadPool,你可以一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。这可以节省时间,因为你不用为每个任务都固定的付出创建线程的开销。在事件驱动的系统中,这种方式较好。

    (2)SingleThreadExecutor
      通过Executor的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得这些任务之间不需要处理线程同步的问题。SingleThreadExecutor方法的实现如下所示:

     public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    

    这对于你希望在另一个线程中连续运行的任何事物(长期存活的任务)来说,这是很有用的,例如监听进入的套接字连接的任务。它对于希望在线程中运行的短任务也是同样方便,例如,更新本地或远程日志的小任务,或者是事件分发线程。

    (3)ScheduledThreadPool
      通过Executors的newScheduledPool方法来创建。它的核心线程数量时固定的,而非核心线程数是没有限制的,并且当非核心线程闲置是会被立即回收。ScheduledThreadPool这类线程主要用于执行定时任务和具有固定周期的重复任务,newScheduledThreadPool方法的实现如下:

        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
                return new ScheduledThreadPoolExecutor(corePoolSize);
            }
    
        public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
                  new DelayedWorkQueue());
        }
    

    ScheduledThreadPoolExecutor继承ThreadPoolExecutor,并实现ScheduledExecutorService。

    (4)CachedThreadPool
      通过Executors的newCachedThreadPool方法来创建。它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池中的线程都是处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时长为60秒,超过60秒闲置线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为在这种场景下SynchronousQueue是无法插入任务的。SynchronousQueue是一个非常特殊的队列,在很多情况下可以把它简单理解为一个无法存储元素的队列,由于它在实际中较少使用,这里就不探讨了。从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被终止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎是不占用任何系统资源的,newCachedThreadPool的实现方法如下:

      public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }

    1.2 线程池中任务的添加

    1.2.1 execute()

    execute()定义在ThreadPoolExecutor.java中,源码如下:

    public void execute(Runnable command) {
        // 如果任务为null,则抛出异常。
        if (command == null)
            throw new NullPointerException();
        // 获取ctl对应的int值。该int值保存了"线程池中任务的数量"和"线程池状态"信息
        int c = ctl.get();
        // 当线程池中的任务数量 < "核心池大小"时,即线程池中少于corePoolSize个任务。
        // 则通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 当线程池中的任务数量 >= "核心池大小"时,
        // 而且,"线程池处于允许状态"时,则尝试将任务添加到阻塞队列中。
        if (isRunning(c) && workQueue.offer(command)) {
            // 再次确认“线程池状态”,若线程池异常终止了,则删除任务;然后通过reject()执行相应的拒绝策略的内容。
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 否则,如果"线程池中任务数量"为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
        // 如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
        else if (!addWorker(command, false))
            reject(command);
    }

    说明:execute()的作用是将任务添加到线程池中执行。它分为三种情况:
    (1)如果“线程池中任务数量” < “核心池大小” 时,即线程池中少于corePoolSize个任务;此时就新建一个线程,并将该任务添加到线程中进行执行。
    (2)如果“线程池中任务数量” >= “核心池大小” ,并且“线程池是允许状态”;此时,则将任务添加到阻塞队列中阻塞等待。在该情况下,会再次确认“线程状态”,如果“第2次读到的线程池状态”和“第1次读到的线程次状态”不同,则从阻塞队列中删除该任务。
    (3)如果非上述的两种情况,就会尝试新建一个线程,并将该任务添加到线程中进行执行。如果执行失败,则通过reject()拒绝该任务。

    1.2.2 addWorker()

    addWorker()的源码如下:

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        // 更新"线程池状态和计数"标记,即更新ctl。
        for (;;) {
            // 获取ctl对应的int值。该int值保存了"线程池中任务的数量"和"线程池状态"信息
            int c = ctl.get();
            // 获取线程池状态。
            int rs = runStateOf(c);
    
            // 有效性检查
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
    
            for (;;) {
                // 获取线程池中任务的数量。
                int wc = workerCountOf(c);
                // 如果"线程池中任务的数量"超过限制,则返回false。
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 通过CAS函数将c的值+1。操作失败的话,则退出循环。
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                // 检查"线程池状态",如果与之前的状态不同,则从retry重新开始。
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
    
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        // 添加任务到线程池,并启动任务所在的线程。
        try {
            final ReentrantLock mainLock = this.mainLock;
            // 新建Worker,并且指定firstTask为Worker的第一个任务。
            w = new Worker(firstTask);
            // 获取Worker对应的线程。
            final Thread t = w.thread;
            if (t != null) {
                // 获取锁
                mainLock.lock();
                try {
                    int c = ctl.get();
                    int rs = runStateOf(c);
    
                    // 再次确认"线程池状态"
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // 将Worker对象(w)添加到"线程池的Worker集合(workers)"中
                        workers.add(w);
                        // 更新largestPoolSize
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    // 释放锁
                    mainLock.unlock();
                }
                // 如果"成功将任务添加到线程池"中,则启动任务所在的线程。 
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 返回任务是否启动。
        return workerStarted;
    }

      addWorker()的作用是将firstTask添加到线程池中,并启动该任务。当core为true是,则以corePoolSize为界限,若“线程池中已有任务数量” >= corePoolSize ,那么返回false;当core为false时,则以maximumPoolSize为界限,若“线程池中已有任务数量” >= maximumPoolSize ,则返回false。addWorker()方法会先通过for循环不断尝试更新 ctl状态,ctl 记录了“线程池中任务数量和线程池状态”。更新成功后,在通过try模块来将任务添加到线程池中,并启动任务所在的线程。
      从addWorker()方法中,我们可以发现:线程池在添加任务时,会创建任务对应的Worker对象,而一个Worker对象包含了一个Thread对象。通过将Worker对象添加到“线程的workers集合中”,从而实现将任务添加到线程池中。通过启动Worker对应的Thread线程,则执行该任务。

    1.2.3 submit()

    submit()实际上也是通过调用execute()实现的,源码如下:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    1.3 线程池的关闭

    在ThreadPoolExecutor类中的shutdown()方法源码为:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        // 获取锁
        mainLock.lock();
        try {
            // 检查终止线程池的“线程”是否有权限。
            checkShutdownAccess();
            // 设置线程池的状态为关闭状态。
            advanceRunState(SHUTDOWN);
            // 中断线程池中空闲的线程。
            interruptIdleWorkers();
            // 钩子函数,在ThreadPoolExecutor中没有任何动作。
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            // 释放锁
            mainLock.unlock();
        }
        // 尝试终止线程池
        tryTerminate();
    }

    1.4 使用Callable

      Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE 5 中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它,下面是简单示例:

    public class CallableDemo {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
    
    
            class TaskWithResult implements Callable<String>{
    
                private int id;
                public TaskWithResult(int id){
                    this.id = id;
                }
                @Override
                public String call() throws Exception {
    
                    return "result of Callable "+id;
                }
    
            }
    
            ExecutorService exec = Executors.newCachedThreadPool();
            ArrayList<Future<String>>  results = new ArrayList<Future<String>>();
            for(int i=0;i<5;i++){
                results.add(exec.submit(new TaskWithResult(i)));
            }
    
            for(Future<String> fs:results){
                try {
                    System.out.println(fs.get());
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }finally{
                    exec.shutdown();
                }
            }
        }
    
    }
    

    输出的结果:

    result of Callable 0
    result of Callable 1
    result of Callable 2
    result of Callable 3
    result of Callable 4
    

    submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化。

    二、解决共享资源竞争

      在Java SE5 的java.util.concurrent类库中还包含有定义在java.util.concurrent.locks中的显式的互斥机制。Lcok对象必须被显示的创建、锁定、和释放。因此,它与内间的锁形式相比,代码缺乏优雅性。但是,对于解决某些类型的问题,它更加灵活。下面是用Lock写以解决共享资源的示例:

    public class LockAndUnLock {
    
        static Lock lock = new ReentrantLock();//新建锁
    
        public static void main(String[] args) {
    
            new Thread("A"){
                public void run() {
                    Thread.yield();//当前线程的让步,加快线程切换
                    numPrint();
    
                };
            }.start();
    
            new Thread("B"){
                public void run() {
                    Thread.yield();//当前线程的让步,加快线程切换
                    numPrint();
                };
            }.start();
    
        }
    
        private static void numPrint(){
            lock.lock();
            try{
                for(int i=0;i<10;i++){
                    Thread.sleep(100);
                    System.out.println("当前线程"+Thread.currentThread().getName()+":"+i);
                }
            }catch(Exception e){
    
            }finally{
                lock.unlock();
            }
        }
    
    }
    

    输出结果:

    当前线程A0
    当前线程A1
    当前线程A2
    当前线程A3
    当前线程A4
    当前线程A5
    当前线程A6
    当前线程A7
    当前线程A8
    当前线程A9
    当前线程B:0
    当前线程B:1
    当前线程B:2
    当前线程B:3
    当前线程B:4
    当前线程B:5
    当前线程B:6
    当前线程B:7
    当前线程B:8
    当前线程B:9
    

      可以看出一个被互斥调用的锁,并使用lock()和unlock()方法在numPrint()内创建了临界资源。当你在使用Lock对象时,将这里的所示的惯用法内部化是很重要的:紧接着的对lock()的调用,你必须放再finally子句中带有unlock()的try-finally语句中。尽管try-finally所需的代码比synchronized关键字要多,但是这也代表了显示的Lock对象的优点之一。如果在使用synchronized关键字,某些事务失败了,那么就会抛出一个异常。但是你没有机会去做任何清理工作,以维护系统使其处于良好状态。有了显示的Lock对象,你就可以使用finally子句将系统维护在正确的状态了。
      大体上,当你使用synchronized关键字时,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只有在解决特殊问题时,才使用显示的Lock对象。

      以上,先对线程池做了大体的介绍,然后我会逐步介绍JUC中的原子类、线程安全的集合、锁以及深层次剖析线程池原理。

    展开全文
  • Java多线程:多线程基础

    千次阅读 2018-10-25 13:12:59
    多线程基础 多线程实现-Thread和Runnable 通常使用如下代码启动一个新的线程: private void startNewThread1() { new Thread() { @Override public void run() { //耗时操作,此时target为空 } }.start(); }.....

    多线程基础

    多线程实现-Thread和Runnable

    通常使用如下代码启动一个新的线程:

    private void startNewThread1() {
        new Thread() {
            @Override
            public void run() {
                //耗时操作,此时target为空
            }
        }.start();
    }
    
    private void startNewThread2() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //耗时操作,此时target不为空
            }
        }).start();
    }
    

    Thread也是一个Runnable,它实现了Runnable接口,在Thread类中有一个Runnable类型的target字段,代表要被执行在这个子线程中的任务:

    public class Thread implements Runnable {
        private Runnable target; //线程所属的ThreadGroup
        private ThreadGroup group; //要执行的目标任务
    
        public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    
        private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
            init(g, target, name, stackSize, null, true);
        }
    
        private void init(ThreadGroup g, Runnable target, String name,
                            long stackSize, AccessControlContext acc,
                            boolean inheritThreadLocals) {
            ...
            Thread parent = currentThread();
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                if (security != null) {
                    g = security.getThreadGroup();
                }
                if (g == null) {
                    g = parent.getThreadGroup(); //group参数为null时获取当前线程的线程组
                }
            }
            ...
            this.group = g;
            ...
            this.target = target;
            ...
        }
    
        public synchronized void start() {
            ...
            group.add(this); //将thread对象添加到添加到ThreadGroup中
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                ...
            }
        }
    }
    

    实际上最终被线程执行的任务是Runnable,Thread只是对Runnable的包装,并且通过一些状态对Thread进行管理与调度

    Runnable接口定义了可执行的任务,它有一个无返回值的run()函数:

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    

    当启动一个线程时,如果Thread的target不为空,则会在子线程中执行这个target的run函数,否则虚拟机就会执行该线程自身的run函数

    线程的wait、sleep、join和yield函数

    函数名 作用
    wait 当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的机锁,使得其他线程可以访问,用户可以用notify、notifyAll或者指定睡眠时间来唤醒当前等待池中的线程,注意:wait、notify、notifyAll必须放在synchronized block中,否则会抛出异常
    sleep 该函数是Thread的静态函数,作用是使当前线程进入睡眠状态,因为其是静态方法,所以不会改变对象机锁,即使睡眠也持有对象锁,其他对象无法访问这个对象
    join 等待目标线程完成后再执行
    yield 让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行是未知的

    wait notify函数

    wait与nofity机制,通常用于等待机制的实现,当条件未满足时调用wait进入等待状态,一旦条件满足,调用notify或notifyAll唤醒等待的线程继续执行

    下面来看下wait和notify,notifyAll的运用:

        private static Object sLockObject = new Object();
    
        static void waitAndNotifyAll() {
            System.out.println("主线程  运行");
            Thread thread = new WaitThread();
            thread.start();
            long startTime = System.currentTimeMillis();
            try {
                synchronized (sLockObject) {
                    System.out.println("主线程  等待");
                    sLockObject.wait();
                }
            } catch (Exception e) {
            }
            System.out.println("主线程  继续 --> 等待耗时 : " + (System.currentTimeMillis() - startTime) + " ms");
        }
    
        static class WaitThread extends Thread {
            @Override
            public void run() {
                try {
                    synchronized (sLockObject) {
                        Thread.sleep(3000);
                        sLockObject.notifyAll();
                    }
                } catch (Exception e) {
                }
            }
        }
    

    waitAndNotifyAll函数中,先创建子线程并休眠三秒,主线程调用wait函数后进入等待状态,子线程休眠三秒结束后调用notifyAll函数,唤醒正在等待中的主线程

    join函数

    join函数的原始解释为"Blocks the current Thread(Thread.currentThread()) until the receiver finishes its execution and dies",意思就是阻塞当前调用join函数时所在的线程,直到接收完毕后再继续

        static void joinDemo() {
            Worker worker1 = new Worker("work-1");
            Worker worker2 = new Worker("work-2");
    
            worker1.start();
            System.out.println("启动线程1");
            try {
                worker1.join();
                System.out.println("启动线程2");
                worker2.start();
                worker2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("主线程继续执行");
        }
    
        static class Worker extends Thread {
    
            public Worker(String name) {
                super(name);
            }
    
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("work in " + getName());
            }
        }
    

    在join函数中,首先创建两个子线程,启动worker1后调用worker1的join函数,此时主线程进入阻塞状态直到worker1执行完毕后继续执行,因为Worker的run函数会睡眠两秒,因此每次调用join函数都会阻塞两秒

    yield函数

    yield函数官方解释为"Causes the calling Thread to yield execution time to another Thread that is ready to run",意为使调用该函数的线程让出执行时间给其他已就绪状态的线程,由于线程的执行是有时间片的,每个线程轮流占用CPU固定的时间,执行周期到了之后就让处执行权给其他线程,而yield的功能就是主动让出线程的执行权给其他线程,其他线程能否得到优先执行就得看各个线程的状态了

        static class YieldThread extends Thread {
            public YieldThread(String name) {
                super(name);
            }
    
            public synchronized void run() {
                for (int i = 0; i < MAX; i++) {
                    System.out.printf("%s ,优先级为 : %d ----> %d\n", this.getName(), this.getPriority(), i);
                    // i整除4时,调用yield
                    if (i == 2) {
                        Thread.yield();
                    }
                }
            }
        }
    
        static void yield() {
            YieldThread t1 = new YieldThread("thread-1");
            YieldThread t2 = new YieldThread("thread-2");
            YieldThread t2 = new YieldThread("thread-2");
            YieldThread t2 = new YieldThread("thread-2");
            t1.start();
            t2.start();
        }
    

    可以看到YieldThread内有一个执行5次的循环,当循环到i=2时调用yield函数,理论上在i=2时线程会让出执行权让其他线程优先执行,但有时并不一定能产生效果,因为它仅仅可以使一个线程从running状态变成Runnable状态,而不是wait或者blocked状态,并且不能保证正在执行的线程立刻变成Runnable状态

    与多线程相关的方法 Runnable、Callable、Future和FutureTask

    Runnable:既能运用在Thread中又能运用在线程池中

    Callable、Future、FutureTask:只能运用在线程池中

    Callable

    Callable与Runnable功能大致类似,不同的是Callable是一个泛型接口,它有一个泛型参数V,该接口中有一个返回值(类型为V)的call()函数

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    

    Future

    Future为线程池制定了一个可管理的任务标准,提供了对Runnable或Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作,分别对应cancel、isDone、get(阻塞直到任务返回结果)、set(在FutureTesk中定义)函数

    public interface Future<V> {
        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();
        boolean isDone();
        V get() throws InterruptedException, ExecutionException;
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }
    

    FutureTask

    FutureTask是Future的实现类,它实现了RunnableFuture<V>,而RunnableFuture实现了Runnable又实现了Future<V>这两个接口,因此FutureTask既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行,并且还可以直接通过get()函数获取执行结果,该函数会阻塞直到结果返回

    public class FutureTask<V> implements RunnableFuture<V> {
        ...
    }
    
    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }
    

    FutureTask会像Thread包装Runnable那样对Runnable和Callable<V>进行包装,Runnbale与Callable由构造函数注入:

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
    

    如果注入的是Runnable则会被Executors.callable()函数转换为Callable类型,因此FutureTask最终都是执行Callable类型的任务

    Runnable、Callable、FutureTask运用

        // 线程池
        static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    
        private static void futureWithRunnable() throws InterruptedException, ExecutionException {
            // 向线程池提交runnable则没有返回值, future没有数据
            Future<?> result = mExecutor.submit(new Runnable() {
    
                @Override
                public void run() {
                    fibc(20);
                }
            });
    
            System.out.println("future result from runnable : " + result.get());
        }
    
        private static void futureWithCallable() throws InterruptedException, ExecutionException {
            //向线程池提交Callable, 有返回值, future中能够获取返回值
            Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return fibc(20);
                }
            });
    
            System.out.println("future result from callable : "
                    + result2.get());
        }
    
        private static void futureTask() throws InterruptedException, ExecutionException {
            FutureTask<Integer> futureTask = new FutureTask<Integer>(
                    new Callable<Integer>() {
                        @Override
                        public Integer call() throws Exception {
                            return fibc(20);
                        }
                    });
            // 提交futureTask
            mExecutor.submit(futureTask);
            System.out.println("future result from futureTask : "
                    + futureTask.get());
        }
    
        // 效率底下的斐波那契数列, 耗时的操作
        private static int fibc(int num) {
            if (num == 0) {
                return 0;
            }
            if (num == 1) {
                return 1;
            }
            return fibc(num - 1) + fibc(num - 2);
        }
    

    demo代码:https://github.com/GavinAndre/JTMMultiThread

    展开全文
  • java多线程的6种实现方式详解

    万次阅读 多人点赞 2019-03-25 13:50:23
    多线程的形式上实现方式主要有两种,一种是继承Thread类,一种是实现Runnable接口。本质上实现方式都是来实现线程任务,然后启动线程执行线程任务(这里的线程任务实际上就是run方法)。这里所说的6种,实际上都是在...
  • JAVA多线程下高并发的处理经验

    万次阅读 2019-02-24 17:38:29
    java中的线程java中,每个线程都有一个调用栈存放在线程栈之中,一个java应用总是从main()函数开始运行,被称为主线程。一旦创建一个新的线程,就会产生一个线程栈。线程总体分为:用户线程和守护线程,当所有...
  • Java多线程:彻底搞懂线程池

    万次阅读 多人点赞 2020-08-06 10:22:59
    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了。 目录 1 线程池的优势 2 线程池的使用 3 线程池的工作原理 4 线程池的参数 4.1 任务队列...
  • 深入理解Java多线程(一)

    万次阅读 多人点赞 2018-09-09 15:52:27
    关于java多线程的概念以及基本用法:java多线程基础 1,停止线程 停止线程意味着在线程执行完之前停止正在做的操作,即立刻放弃当前的操作,这并不容易。停止线程可以用Thread.stop()方法,但是这个方法不安全...
  • java多线程全面详解

    千次阅读 2019-03-08 17:47:22
    多线程是什么?为什么要用多线程?  介绍多线程之前要介绍线程,介绍线程则离不开进程。  首先 , 进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制...
  • 提升对于 java 多线程的理解,要学会横向对比学习,多看些文章。 目前大部分的文章都是止步于创建和常见的类,本系列重在实践,参考书籍《java 并发编程实战》《Java并发编程的艺术》 后续原理参考《深入理解并行...
  • java多线程中的异常处理

    万次阅读 多人点赞 2012-11-27 11:32:41
    java多线程程序中,所有线程都不允许抛出未捕获的checked exception,也就是说各个线程需要自己把自己的checked exception处理掉。这一点是通过java.lang.Runnable.run()方法声明(因为此方法声明上没有throw ...
  • [Java基础]-- 怎样杀死一个指定线程

    万次阅读 2018-11-26 22:02:17
    杀死线程 一般情况下如果一个程序等待超时或者长期不使用,将会导致资源浪费,为了避免...二、线程外调用destroy()方法杀死线程,但是需要加上java监控,获取线程id,否则不知道该线程是否在执行或者已经执行完成!...
  • Java项目中什么时候需要多线程?比如什么项目中那个模块用到了
  • java 程序中怎么保证多线程的运行安全? 线程的安全性问题体现在: 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到 有序性:程序...
  • java线程停止的几种方法

    万次阅读 2018-09-09 18:31:21
    要终止一个线程,并不是简单的调用stop()方法,stop()就像linux中的kill一个线程一样是非常暴力的,虽然在Java的API中仍然可以调用stop()方法,但是和suspend,resume方法一样,是过期了的,不建议使用的,因为stop...
  • java多线程开发 如何正确关闭线程

    万次阅读 2020-06-28 21:03:39
    java高级开发中,经常会碰到多线程,关于线程的关闭,可能会用stop() 方法,但是stop是线程不安全的,一般采用interrupt,判断线程是否中止采用isInterrupted, 如果线程中有Thread.sleep方法,当设置中断后,...
  • JVM在操作系统中是作为一个进程的,java所有的线程都运行自这个JVM进程中, 所以说java线程某个时间只可能运行在一个核上。 这个说法对我的打击太大了,我不能接受。于是就开始多方求证。网上搜索 和朋友一起...
  • JAVA多线程10个线程处理1000个数据

    万次阅读 2018-11-06 11:02:14
    import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MultiT....
  • Java多线程和操作系统多线程关系

    千次阅读 2018-03-01 17:23:23
    这篇文章要讨论的是Java编程中的多线程和操作系统中的多线程的区别问题。 线程状态。首先两者的线程状态是一样的。(创建、就绪、执行、阻塞、终止),其实这五个状态也是进程的状态。 那么Java中的多线程 和 OS...
  • // 日前工作遇到一个需求,就是把硬盘的文件(大量文件)全部读取出来,然后全部解析生成文件保存到硬盘需要多线程处理,并要计算全部执行结束后的耗时。以下是实现方法. // 创建线程池 ExecutorService ...
1 2 3 4 5 ... 20
收藏数 980,467
精华内容 392,186
关键字:

java多线程