Java-线程-多线程详解 线程,进程,多线程
线程与线程之间互不干扰,独立执行
程序运行时,没有建立自己的线程,后台也会有多个线程,比如主线程,gc线程(垃圾回收线程)
main() 称之为主线程,为系统入,用于执行整个程序
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,如cpu调度时间,并发控制开销
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
法一创建线程 - 继承Thread类 三种创建方式
继承thread类
重写run方法
调用start()开启线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class TestThread1 extends Thread { @Override public void run () { for (int i=0 ;i<20 ;i++){ System.out.println("我在看代码---" +i); } } public static void main (String[] args) { TestThread1 testThread1 = new TestThread1 (); testThread1.start(); for (int i = 0 ; i < 20 ; i++) { System.out.println("我在学习多线程---" +i); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 我在看代码---0 我在看代码---1 我在学习多线程---0 我在看代码---2 我在学习多线程---1 我在看代码---3 我在学习多线程---2 我在看代码---4 我在学习多线程---3 我在看代码---5 我在学习多线程---4 我在学习多线程---5 我在看代码---6 我在学习多线程---6 我在学习多线程---7 我在学习多线程---8 我在学习多线程---9 我在学习多线程---10 我在学习多线程---11 我在看代码---7 我在看代码---8 我在学习多线程---12 我在学习多线程---13 我在看代码---9 我在学习多线程---14 我在看代码---10 我在学习多线程---15 我在学习多线程---16 我在学习多线程---17 我在学习多线程---18 我在学习多线程---19 我在看代码---11 我在看代码---12 我在看代码---13 我在看代码---14 我在看代码---15 我在看代码---16 我在看代码---17 我在看代码---18 我在看代码---19
注意
案例:下载图片 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package Demo01;import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.URL;public class TestThread2 extends Thread { private String url; private String name; public TestThread2 (String url,String name) { this .url = url; this .name = name; } @Override public void run () { WebDownloader webDownloader = new WebDownloader (); webDownloader.downloader(url,name); System.out.println("下载了文件名为:" +name); } public static void main (String[] args) { TestThread2 t1 = new TestThread2 ("http://static.runoob.com/images/demo/demo1.jpg" ,"1.jpg" ); TestThread2 t2 = new TestThread2 ("http://static.runoob.com/images/demo/demo2.jpg" ,"2.jpg" ); TestThread2 t3 = new TestThread2 ("http://static.runoob.com/images/demo/demo3.jpg" ,"3.jpg" ); t1.start(); t2.start(); t3.start(); } } class WebDownloader { public void downloader (String url,String name) { try { FileUtils.copyURLToFile(new URL (url), new File (name)); }catch (IOException e){ e.printStackTrace(); System.out.println("IO异常,download方法出现问题" ); } } }
法二创建线程 - 实现Runnable接口
实现runnable接口,
重写run方法,
执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package Demo01;public class TestThread3 implements Runnable { @Override public void run () { for (int i=0 ;i<20 ;i++){ System.out.println("我在看代码---" +i); } } public static void main (String[] args) { TestThread3 testThread3 = new TestThread3 (); Thread thread = new Thread (testThread3); thread.start(); for (int i = 0 ; i < 20 ; i++) { System.out.println("我在学习多线程---" +i); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 我在看代码---0 我在看代码---1 我在学习多线程---0 我在看代码---2 我在看代码---3 我在学习多线程---1 我在看代码---4 我在学习多线程---2 我在看代码---5 我在学习多线程---3 我在看代码---6 我在看代码---7 我在看代码---8 我在看代码---9 我在看代码---10 我在看代码---11 我在看代码---12 我在看代码---13 我在看代码---14 我在看代码---15 我在看代码---16 我在看代码---17 我在看代码---18 我在学习多线程---4 我在看代码---19 我在学习多线程---5 我在学习多线程---6 我在学习多线程---7 我在学习多线程---8 我在学习多线程---9 我在学习多线程---10 我在学习多线程---11 我在学习多线程---12 我在学习多线程---13 我在学习多线程---14 我在学习多线程---15 我在学习多线程---16 我在学习多线程---17 我在学习多线程---18 我在学习多线程---19
小结
继承Thread类
子类继承Thread类具备多线程能力
启动线程:子类对象.start()
不建议使用:避免OOP单继承局限性
实现Runnable接口
实现接口Runnable具有多线程能力
启动线程:传入目标对象+Thread()对象.start()
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
初识并发问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package Demo01;public class TestThread4 implements Runnable { int ticketNums = 10 ; @Override public void run () { while (true ){ if (ticketNums<=0 ){ break ; } try { Thread.sleep(200 ); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"拿到了第" +ticketNums--+"票" ); } } public static void main (String[] args) { TestThread4 ticket = new TestThread4 (); new Thread (ticket,"小明" ).start(); new Thread (ticket,"老师" ).start(); new Thread (ticket,"黄牛" ).start(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 黄牛拿到了第9 票 老师拿到了第10 票 小明拿到了第8 票 老师拿到了第7 票 黄牛拿到了第7 票 小明拿到了第6 票 老师拿到了第5 票 小明拿到了第3 票 黄牛拿到了第4 票 小明拿到了第2 票 黄牛拿到了第0 票 老师拿到了第1 票 小明拿到了第-1 票
龟兔赛跑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package Demo01;public class Race implements Runnable { private static String winner; @Override public void run () { for (int i = 0 ; i<=100 ;i++){ if (Thread.currentThread().getName().equals("兔子" ) && i%10 ==0 ){ try { Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } } boolean flag = gameover(i); if (flag){ break ; } System.out.println(Thread.currentThread().getName()+"-->跑了" +i+"步" ); } } private boolean gameover (int steps) { if (winner!=null ){ return true ; }{ if (steps>=100 ){ winner = Thread.currentThread().getName(); System.out.println("Winner is" +winner); return true ; } } return false ; } public static void main (String[] args) { Race race = new Race (); new Thread (race,"兔子" ).start(); new Thread (race,"乌龟" ).start(); } }
法三创建线程 - 实现Callable接口
实现Callable接口,需要返回值类型
重写call方法,需要抛出异常
创建目标对象
创建执行服务:
提交执行:
获取结果:
关闭服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package Demo02;import Demo01.TestThread2;import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.URL;import java.util.concurrent.*;public class TestCallable implements Callable <Boolean> { private String url; private String name; public TestCallable (String url, String name) { this .url = url; this .name = name; } @Override public Boolean call () { WebDownloader webDownloader = new WebDownloader (); webDownloader.downloader(url,name); System.out.println("下载了文件名为:" +name); return true ; } public static void main (String[] args) throws ExecutionException, InterruptedException { TestCallable t1 = new TestCallable ("http://static.runoob.com/images/demo/demo1.jpg" ,"1.jpg" ); TestCallable t2 = new TestCallable ("http://static.runoob.com/images/demo/demo2.jpg" ,"2.jpg" ); TestCallable t3 = new TestCallable ("http://static.runoob.com/images/demo/demo3.jpg" ,"3.jpg" ); ExecutorService ser = Executors.newFixedThreadPool(3 ); Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3); boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get(); System.out.println(rs1); System.out.println(rs2); System.out.println(rs3); ser.shutdownNow(); } } class WebDownloader { public void downloader (String url,String name) { try { FileUtils.copyURLToFile(new URL (url), new File (name)); }catch (IOException e){ e.printStackTrace(); System.out.println("IO异常,download方法出现问题" ); } } }
1 2 3 4 5 6 下载了文件名为:1. jpg 下载了文件名为:3. jpg 下载了文件名为:2. jpg true true true
静态代理
真实对象与代理对象实现同一个接口
代理对象要代理真实角色
好处:
代理对象可以做真实对象做不了的事情
真实对象专注做自己的事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package proxystatic;public class StaticProxy { public static void main (String[] args) { You you = new You (); WeddingCompany weddingCompany = new WeddingCompany (you); weddingCompany.HappyMarry(); } } interface Marry { void HappyMarry () ; } class You implements Marry { @Override public void HappyMarry () { System.out.println("秦老师要结婚了,超开心" ); } } class WeddingCompany implements Marry { private Marry target; public WeddingCompany (Marry target) { this .target = target; } @Override public void HappyMarry () { before(); this .target.HappyMarry(); after(); } private void before () { System.out.println("结婚之前,布置现场" ); } private void after () { System.out.println("结婚之后,收尾款" ); } }
1 2 3 结婚之前,布置现场 秦老师要结婚了,超开心 结婚之后,收尾款
Thread类是一个代理,实现了Runnable接口
里面真实的类也实现了Runnable接口
所以Thread类可以做真实类的代理
Lambda表达式 目的是简化程序
各种类与lambda表达式
越来越方便书写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package lambdaShow;public class TestLambda1 { static class Like2 implements ILike { @Override public void lambda () { System.out.println("i like lambda2" ); } } public static void main (String[] args) { ILike like = new Like (); like.lambda(); like = new Like2 (); like.lambda(); class Like3 implements ILike { @Override public void lambda () { System.out.println("i like lambda3" ); } } like = new Like3 (); like.lambda(); like = new ILike () { @Override public void lambda () { System.out.println("i like lambda4" ); } }; like.lambda(); like = () -> { System.out.println("i like lambda5" ); }; like.lambda(); } } interface ILike { void lambda () ; } class Like implements ILike { @Override public void lambda () { System.out.println("i like lambda" ); } }
1 2 3 4 5 i like lambda i like lambda2 i like lambda3 i like lambda4 i like lambda5
带参数的lambda表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package lambdaShow;public class TestLambda2 { public static void main (String[] args) { ILove love = (int a)->{ System.out.println("i love you-->" +a); }; love.love(2 ); } } interface ILove { void love (int a) ; }
微小简化lambda表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package lambdaShow;public class TestLambda2 { public static void main (String[] args) { ILove love = null ; love = a-> System.out.println("i love you-->" +a); love.love(2 ); } } interface ILove { void love (int a) ; }
回顾
Runnable接口中,就只有一个方法,所以我们可以使用lambda表达式来实现这个接口
线程停止
停止线程
不推荐使用JDK提供的stop(), destroy()方法【已废弃】
推荐线程自己停下来
建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
1 2 3 4 5 6 7 8 9 10 11 12 run...Thread816 run...Thread817 main899 main900 run...Thread818 run...Thread819 run...Thread820 run...Thread821 线程停止了 main901 main902 main903
线程休眠_sleep
sleep指定当前线程阻塞的毫秒数
sleep存在异常InterruptedException
sleep时间达到后,线程进入就绪状态
sleep可以模拟网络延时,倒计时等
每一个对象都有一个锁,sleep不会释放锁
比如之前的买票代码,没有延迟,就可能一个人买完了票
模拟倒计时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package lambdaShow;public class TestSleep2 { public static void main (String[] args) { try { tenDown(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void tenDown () throws InterruptedException { int num = 10 ; while (true ){ Thread.sleep(1000 ); System.out.println(num--); if (num<=0 ){ break ; } } } }
1 2 3 4 5 6 7 8 9 10 11 10 9 8 7 6 5 4 3 2 1 0
模拟时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package state;import java.text.SimpleDateFormat;import java.util.Date;public class TestSleep2 { public static void main (String[] args) { Date startTime = new Date (System.currentTimeMillis()); while (true ){ try { Thread.sleep(1000 ); System.out.println(new SimpleDateFormat ("HH:mm:ss" ).format(startTime)); startTime = new Date (System.currentTimeMillis()); }catch (InterruptedException e){ e.printStackTrace(); } } } public static void tenDown () throws InterruptedException { int num = 10 ; while (true ){ Thread.sleep(1000 ); System.out.println(num--); if (num<=0 ){ break ; } } } }
1 2 3 4 5 14 :54 :58 14 :54 :59 14 :55 :00 14 :55 :01 14 :55 :02
线程礼让
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行态,转为就绪状态
让cpu重新调度,礼让不一定成功!看cpu心情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package state;public class TestYield { public static void main (String[] args) { MyYield myYield = new MyYield (); new Thread (myYield,"a" ).start(); new Thread (myYield,"b" ).start(); } } class MyYield implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName()+"线程开始执行" ); Thread.yield (); System.out.println(Thread.currentThread().getName()+"线程停止执行" ); } }
礼让成功
1 2 3 4 b线程开始执行 a线程开始执行 b线程停止执行 a线程停止执行
礼让失败
例子不好没试出来
线程强制执行_join 相当于插队
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package state;public class TestJoin implements Runnable { @Override public void run () { for (int i = 0 ; i < 100 ; i++) { System.out.println("线程vip来了" +i); } } public static void main (String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin (); Thread thread = new Thread (testJoin); thread.start(); thread.sleep(1000 ); for (int i = 0 ; i < 1000 ; i++) { if (i==200 ){ thread.join(); } System.out.println("main" +i); } } }
线程状态观测
Thread.State
NEW //尚未启动的线程处于此状态
RUNNABLE //在Java虚拟机中执行的线程处于此状态
BLOCKED //被阻塞等待监视器锁定的线程处于此状态
WAITING //正在等待另一个线程执行特定动作的线程处于此状态
TIMED_WAITING //正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
TERMINATED //已退出的线程处于此状态
线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按学号优先级决定应该调度哪个线程来执行。
线程优先级用数字表示,范围从1~10,在Thread中定义了几个常量来表示优先级
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5; //默认优先级,不设置的话就是默认优先级
线程优先级高不一定先执行,但是给它的资源就会多一些
使用以下方式来改变或获取优先级
getPriority() //获取优先级
setPriority(int xxx) //改变优先级
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
守护线程:后台记录操作日志,监控内存,垃圾回收等待
通过 setDaemon(true) 方法来设置守护线程,参数为true是守护线程,默认是false用户线程
线程同步
并发:同一个对象被多个线程同时操作
在现实生活中,我们遇到“同一个对象被多个线程同时操作”这种问题,比如食堂打饭,解决办法就是排队
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程在使用。
队列和锁:在线程访问对象时,加入锁机制 synchronized, 当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题
一个线程持有锁会导致其他所有需要此锁的线程挂起
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换,和调度延时,引起性能问题
如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题
三大不安全案例 买票案例 线程不安全,票数出现负数
每个线程都在自己的工作内存交互,内存控制不当会造成数据不一致
买票时,每个线程都把票数拿到自己的内存 ,当票数只剩一张时,所以它们看到的都是1,然后都去拿这一张票,当第一个线程拿到这张票时,票数修改为0,第二个线程再去拿时,票数就变成了-1.
银行取钱案例 线程不安全的集合 以ArrayList 为例
我们加入了10000个元素,实际加入集合的只有7761个,原因是当这些线程在同一瞬间把集合元素添加到集合的同一位置,就覆盖掉了前面的元素,所以集合中的元素数量才会少
如何解决不安全的问题?