- 如何建立线程(两种方式,区别,使用场景)
- 线程状态调度
- 多线程数据共享(会有什么问题,如何实现共享,多线程操做同一个变量会有什么问题,若是不但愿有问题怎么作)
- 数据传递
- 线程池相关(如何建立线程池,要注意什么(初始化线程内部变量),几种经常使用的使用方式)
一般建立线程有两种方式,一个是继承
Thread
, 一个是实现Runnable
; 下面则分别实现以作演示,而后说一下这两种的区别,应该如何选择html
建立线程和使用的一个小case以下, 注意的是线程启动是调用start
方法, 而不是 run
方法; 其次实现Runnable
接口的类,启动依然是放在一个Thread
对象中java
public class ThreadCreate { /** * 经过继承 Thread 方式来建立一个新的线程 */ public static class ThreadExtend extends Thread { @Test public void run() { System.out.println("new extend thread"); } } /** * 经过实现 Runnable 方式来建立一个线程 */ public static class RunnableImplement implements Runnable { @Override public void run() { System.out.println("new runnable thread"); } } @Test public void testCreate() { new ThreadExtend().start(); new Thread(new RunnableImplement()).start(); System.out.println("main!"); } }
为何会有两种方式呢?这两种的区别何在?数组
经过上面的描述能够知道一点,若是你但愿数据多线程内共享,不妨考虑实现 Runnable
接口(固然继承Thread也是ok的);若是但愿隔离,则不妨考虑继承Thread
(实际上使用 Runnable接口的实现也是ok的,多建立几个实现类接口对象而已,每一个对象放在一个新的Thread中执行)多线程
按照我的的理解,网上说的实现Runnable
方便资源共享,更多的是倾向于代码的共享,一般是一个Runnable
对象,放在多个 Thread
实例中执行;而继承 Thead
类,从出发点来看,继承的通常是做为一个独立线程来执行使用,若是你真要像下面这么作,也不会报错,也能正常运行,只是有点违反设计理念而已并发
MyThread extreds Thread {...}; MyThread mythread = new MyThread(); new Thread(mythread).start();
举一个例子,车站卖票,假设如今有三个窗口,总共只有30张车票,卖完就不卖了,怎么实现?若是每一个窗口有10张车票,各个窗口把本身的卖完了就不卖了,怎么实现?dom
第一个case,符合数据共享的一种场景,那么咱们的实现能够以下:ide
public static class TotalSaleTick implements Runnable { private int total = 30; @Override public void run() { while (true) { if (total > 0) { System.out.println(Thread.currentThread().getName() + "售出一张,剩余:" + --total); } else { break; } } } } @Test public void testTotalSale() { TotalSaleTick totalSaleTick = new TotalSaleTick(); Thread thread1 = new Thread(totalSaleTick, "窗口1"); Thread thread2 = new Thread(totalSaleTick, "窗口2"); Thread thread3 = new Thread(totalSaleTick, "窗口3"); thread1.start(); thread2.start(); thread3.start(); System.out.println("master over!"); }
输出以下, 基本上每次跑的输出结果都不同, 能够看出的一点是三个窗口售出的票数不一样,一个问题,上面这种状况,可能形成超卖么?学习
窗口1售出一张,剩余:29 master over! 窗口2售出一张,剩余:28 窗口2售出一张,剩余:25 窗口2售出一张,剩余:24 窗口1售出一张,剩余:27 窗口3售出一张,剩余:26 窗口1售出一张,剩余:22 窗口1售出一张,剩余:20 窗口1售出一张,剩余:19 窗口1售出一张,剩余:18 窗口1售出一张,剩余:17 窗口1售出一张,剩余:16 窗口1售出一张,剩余:15 窗口1售出一张,剩余:14 窗口1售出一张,剩余:13 窗口1售出一张,剩余:12 窗口1售出一张,剩余:11 窗口1售出一张,剩余:10 窗口2售出一张,剩余:23 窗口2售出一张,剩余:8 窗口2售出一张,剩余:7 窗口2售出一张,剩余:6 窗口2售出一张,剩余:5 窗口2售出一张,剩余:4 窗口1售出一张,剩余:9 窗口3售出一张,剩余:21 窗口3售出一张,剩余:1 窗口3售出一张,剩余:0 窗口1售出一张,剩余:2 窗口2售出一张,剩余:3
第二个case,则显然更倾向于继承 Thread
来实现了测试
public static class SplitSaleTick extends Thread { private int total = 10; public SplitSaleTick(String name) { super(name); } @Override public void run() { while (true) { if (total > 0) { System.out.println(Thread.currentThread().getName() + "售出一张,剩余:" + --total); } else { break; } } } } @Test public void testSplitSaleTick() { SplitSaleTick splitSaleTick1 = new SplitSaleTick("窗口1"); SplitSaleTick splitSaleTick2 = new SplitSaleTick("窗口2"); SplitSaleTick splitSaleTick3 = new SplitSaleTick("窗口3"); splitSaleTick1.start(); splitSaleTick2.start(); splitSaleTick3.start(); System.out.println("master over"); } /** * 继承 Thread 也能够实现共享, 只不过比较恶心而已 */ @Test public void testSplitSaleTick2() { SplitSaleTick splitSaleTick1 = new SplitSaleTick("saleTick"); Thread thread1 = new Thread(splitSaleTick1, "窗口1"); Thread thread2 = new Thread(splitSaleTick1, "窗口2"); Thread thread3 = new Thread(splitSaleTick1, "窗口3"); thread1.start(); thread2.start(); thread3.start(); }
输出接过以下, 三个窗口能够并发卖,且每一个窗口卖10张,卖完即止this
窗口1售出一张,剩余:9 窗口2售出一张,剩余:9 窗口2售出一张,剩余:8 窗口1售出一张,剩余:8 窗口1售出一张,剩余:7 窗口1售出一张,剩余:6 窗口1售出一张,剩余:5 窗口1售出一张,剩余:4 窗口2售出一张,剩余:7 窗口1售出一张,剩余:3 窗口1售出一张,剩余:2 窗口1售出一张,剩余:1 窗口1售出一张,剩余:0 窗口3售出一张,剩余:9 窗口3售出一张,剩余:8 窗口3售出一张,剩余:7 窗口3售出一张,剩余:6 窗口3售出一张,剩余:5 窗口3售出一张,剩余:4 窗口3售出一张,剩余:3 窗口3售出一张,剩余:2 窗口3售出一张,剩余:1 窗口3售出一张,剩余:0 master over 窗口2售出一张,剩余:6 窗口2售出一张,剩余:5 窗口2售出一张,剩余:4 窗口2售出一张,剩余:3 窗口2售出一张,剩余:2 窗口2售出一张,剩余:1 窗口2售出一张,剩余:0 ---- test2 输出 ---- 窗口1售出一张,剩余:9 窗口1售出一张,剩余:6 窗口1售出一张,剩余:5 窗口1售出一张,剩余:4 窗口1售出一张,剩余:3 窗口1售出一张,剩余:2 窗口3售出一张,剩余:7 窗口2售出一张,剩余:8 窗口3售出一张,剩余:0 窗口1售出一张,剩余:1
线程建立以后,即调用了start方法以后,线程是否开始运行了?这个运行过程是否会暂停呢?若是须要暂停应该怎么办;若是一个线程依赖另外一个线程的计算结果,又该如何处理?
Thread thd=new Thread()
start()
方法(此时线程知识进入了线程队列,等待获取CPU服务 ,具有了运行的条件,但并不必定已经开始运行了)run()
方法执行完毕,或者线程调用了stop()
方法,线程便进入终止状态sleep()
方法join
等待其余线程终止。在当前线程中调用另外一个线程的join()方法,则当前线程转入阻塞状态,直到另外一个进程运行结束,当前线程再由阻塞转为就绪状态一个Thread实例有一些经常使用的方法如:
start
,sleep
,run
,yield
,join
,wait
等, 这些方法是干吗用的,什么场景下使用,使用时须要注意些什么?方法的执行,将对应线程状态进行说明
run 方法中为具体的线程执行的代码逻辑,通常而言,都不该该被直接进行调用
不管咱们采用哪一种方法建立线程,基本上都是要重写run
方法,这个方法会在线程执行时调用
执行该方法以后,线程进入就绪状态,对使用者而言,但愿线程执行就是调用的这个方法(注意调用以后不会当即执行)
这个方法的主要目的就是告诉系统,咱们的线程准备好了,cpu有空了赶忙来执行咱们的线程
睡眠一段时间,这个过程当中不会释放线程持有的锁, 传入int类型的参数,表示睡眠多少ms
让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留必定时间给其余线程执行的机会
咱们最多见的一种使用方式是在主线程中直接调用 Thread.sleep(100)
, 表示先等个100ms, 而后再继续执行
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还须要返还对象锁);其余线程能够访问
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程
一般咱们执行wait方法是由于当前线程的执行,可能依赖到其余线程,如登陆线程中,若发现用户没有注册,则等待,等用户注册成功后继续走登陆流程(咱们不考虑这个逻辑是否符合实际),
这里就能够在登陆线程中调用 wait方法, 在注册线程中,在执行完毕以后,调用notify方法通知登陆线程,注册完毕,而后继续进行登陆后续action
暂停当前正在执行的线程对象,并执行其余线程
yield()应该作的是让当前运行线程回到可运行状态,以容许具备相同优先级的其余线程得到运行机会。所以,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。可是,实际中没法保证yield()达到让步目的,由于让步的线程还有可能被线程调度程序再次选中
这个方法的执行,有点像一个拿到面包的人对另外几我的说,我把面包放在桌上,咱们重新开始抢,那么下一个拿到面包的仍是这些人中的某个(你们机会均等)
想象不出啥时候会这么干
启动线程后直接调用,即join()的做用是:“等待该线程终止”,这里须要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
从上面的描述也能够很容易看出什么场景须要调用这个方法,主线程和子线程谁先结束很差说,若是主线程提早结束了,致使整个应用都关了,这个时候子线程没执行完,就呵呵了;其次就是子线程执行一系列计算,主线程会用到计算结果,那么就能够执行这个方法,保证子线程执行完毕后再使用计算结果
多线程间数据共享,当多线程公用一个Runnable对象时,这个对象中的成员变量便可以达到数据共享的目的;多线程采用不一样的Runnable对象时,数据怎么共享
Runnable
对象时上面的售票例子中,其实就有这个场景,上面提出了一个问题,是否会出现超卖的状况?
由于咱们知道 ++
不是原子操做, 实际能够拆分为三步:
假设num为10时, 线程A和线程B都调用 ++num操做;对于内存到寄存器这一步,两个线程都到了这一步,A自增将11写回内存,B也进行自增将11写会内存,这个时候就少+1了
读一个long,double类型的共享变量时,也不是原子操做,在32位操做系统上对64位的数据的读写要分两步完成,每一步取32位数据,若是有两个线程同时写一个变量内存,一个进程写低32位,而另外一个写高32位,这样将致使获取的64位数据是失效的数据
在多线程中,共享数据的获取or更新,请确保是原子操做;能够考虑同步锁(synchronized
)修改共享变量,共享变量前添加volatile
, 使用原子数据类型 AtomicInteger
修改上面的售票代码以下
public static class TotalSaleTick implements Runnable { private int total = 30; @Override public void run() { while (true) { synchronized (this) { if (total > 0) { System.out.println(Thread.currentThread().getName() + "售出一张,剩余:" + --total); } else { break; } } } } }
一个小疑惑,在实际的测试中,即使是上面不加上同步块,好像也没有出问题,对于上面的操做可能运行不少遍都是正确的, 好像和咱们预期的不相符,有没有多是由于总数太少,致使冲突的机率变小了?
private AtomicInteger count = new AtomicInteger(0); private int sum = 3000; public class MyThread extends Thread { public void run() { while (true) { if (sum > 0) { count.addAndGet(1); --sum; }else { break; } } System.out.println(Thread.currentThread().getName() + " over " + sum); } } @Test public void testAdd() throws InterruptedException { MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); myThread1.start(); myThread2.start(); myThread1.join(); myThread2.join(); System.out.println("num: " + sum + " count: " + count.get()); }
对上面的场景,多运行几回,发现输出结果果真是超卖了
Thread-1 over -1 Thread-0 over -1 num: -1 count: 3008
Runnable
对象时共享全局变量 + 共享局部变量两种状况,有点区别
上面的case就是一个共享全局变量的demo,上面出现了并发冲突,能够以下解决, 针对类进行加锁
public class ThreadShareTest { private AtomicInteger count = new AtomicInteger(0); private int sum = 3000; public class MyThread extends Thread { public void run() { while (true) { if (sum > 0) { synchronized (ThreadShareTest.class) { if (sum > 0) { count.addAndGet(1); --sum; } } }else { break; } } System.out.println(Thread.currentThread().getName() + " over " + sum); } } @Test public void testAdd() throws InterruptedException { MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); myThread1.start(); myThread2.start(); myThread1.join(); myThread2.join(); System.out.println("num: " + sum + " count: " + count.get()); } }
共享局部变量,须要注意的是局部变量要求是final, 因此下面的int采用了数组的形式(基本类型没法修改,引用类型能够改其内部的值, 不能改引用)
@Test public void testAdd2() throws InterruptedException { final int[] num = {3000}; final AtomicInteger c = new AtomicInteger(0); Runnable runnable = new Runnable() { @Override public void run() { while (true) { if (num[0] > 0) { c.addAndGet(1); num[0]--; } else { break; } } System.out.println(Thread.currentThread().getName() + " over " + num[0]); } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("num: " + num[0] + " count: " + c.get()); }
多运行几回,输出以下,说明也存在并发的问题了, 修正方式一样是加锁
Thread-0 over -1 Thread-1 over -1 num: -1 count: 3001
修改后的run方法内部以下
while (true) { if (num[0] > 0) { synchronized (this) { if (num[0] > 0) { c.addAndGet(1); num[0]--; } else { break; } } } else { break; } }
上面是数据在多线程中共享,很容易出现的就是并发问题;还有一个场景就是我但愿不存在数据共享,线程操做的内部变量不影响其余的线程; 最简单的想法就是一个继承了Thread的类,其内部类正常来说就是隔离的,只要你不把它当成
Runnable
接口的使用方式就行
使用 ThreadLocal
来保证变量在线程之间的隔离, 下面是一个简单的演示,两个线程都是在修改threadLocal中的值, 可是两个线程的修改,对彼此而言是独立的
public static class LocalT implements Runnable { ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); @Override public void run() { int start = (int) (Math.random() * 100); for (int i =0 ; i < 100; i = i+2) { threadLocal.set(start + i); System.out.println(Thread.currentThread().getName() + " : " + get()); } } public int get() { return threadLocal.get(); } } @Test public void testLocal() throws InterruptedException { LocalT local = new LocalT(); Thread thread1 = new Thread(local); Thread thread2 = new Thread(local); thread1.start(); thread2.start(); thread1.join(); thread2.join(); }
数据如何传递给线程,有如何把线程计算的结果抛出来
比较容易想到的就是在建立对象时,传入数据;或者调用线程对象的setXXX方法传入数据, 当作正常的对来操做处理便可
须要注意的是,在线程的执行期间,你修改了其中的局部变量,会出现什么状况呢?
public static class ThreadData implements Runnable { private int num = 0; public void run() { while (num < 100) { System.out.println(Thread.currentThread().getName() + " now: " + num++); } System.out.println(Thread.currentThread().getName() + " num: " + num); } public void setNum(int num) { System.out.println(this.num + " now set to " + num); this.num = num; } } @Test public void testThreadSetData() throws InterruptedException { ThreadData threadData = new ThreadData(); Thread thread1 = new Thread(threadData); Thread thread2 = new Thread(threadData); thread1.start(); thread2.start(); threadData.setNum(200); thread1.join(); thread1.join(); }
输出以下, 将num设置为200以后,并无如咱们预期的结束线程,依然在往下走, 这里就至关因而有一个你修改了这个数据,是否会立马就生效呢?特别是对其余的线程而言
... Thread-1 now: 24 Thread-1 now: 25 Thread-0 now: 14 26 now set to 200 Thread-0 now: 27 Thread-0 now: 28 Thread-1 now: 26 Thread-0 now: 29 Thread-1 now: 30 ....
线程执行了一个任务以后,输出的结果能够怎么处理
一个实例,一个线程实现累加的过程,我如今但愿实现1 加到 100, 开四个线程,怎么作?
下面是一个实现,不知道有没有什么问题
public static class CalculateThread extends Thread { private int start; private int end; private int ans; public CalculateThread(int start, int end) { this.start = start; this.end = end; } public void run() { for (int i = start; i <= end; i++) { ans += i; } } public int getAns() { return ans; } } @Test public void testCalculate() throws InterruptedException { CalculateThread c1 = new CalculateThread(1, 25); CalculateThread c2 = new CalculateThread(26, 50); CalculateThread c3 = new CalculateThread(51, 75); CalculateThread c4 = new CalculateThread(76, 100); c1.start(); c2.start(); c3.start(); c4.start(); c1.join(); c2.join(); c3.join(); c4.join(); System.out.println("ans1: " + c1.getAns() + " ans2: " + c2.getAns() + " ans3: " + c3.getAns() + " ans4: " + c4.getAns()); int ans = c1.getAns() + c2.getAns() + c3.getAns() + c4.getAns(); System.out.println("ans : " + ans); }
多线程技术主要解决处理器单元内多个线程执行的问题,它能够显著减小处理器单元的闲置时间,增长处理器单元的吞吐能力 线程的频繁建立和销毁可能浪费大量的时间,线程池就是为了解决这个问题而产生