滴水穿石-09多线程
1:多线程概述
私以为多线程就是一个进程中同时又多个线程执行,就是“多线程”。从代码的角度理解就是一个程序中有多个方法同时运行。(可能不是很准确)
2:多线程实现方案
创建新执行线程有两种方法。一种方法是将类声明为 Thread
的子类。该子类应重写 Thread
类的 run
方法。接下来可以分配并启动该子类的实例。
创建线程的另一种方法是声明实现 Runnable
接口的类。该类然后实现 run
方法。然后可以分配该类的实例,在创建 Thread
时作为一个参数来传递并启动。
2.1 继承Thread
类
1:创建MyThread继承Thread类 2:重写run()方法 3:创建对象 4:启动线程
MyThread 1.0
package d9; public class MyThreadDemo { public static void main(String[] args) { //创建线程对象 MyThread my = new MyThread(); MyThread my2 = new MyThread(); //my.run(); //my.run(); //run() 方法仅仅是封装该线程执行的代码,直接调用是管道方法 //start() 方法首先启动了线程,然后再由jvm去掉用该线程的run方法 my.start(); my2.start(); } }
2.2 继承Runnable接口
1:自定义一个类MyRunnable实现Runnable接口 2:重写run()方法 3:创建MyRunnable类的对象 4:创建Threadle的对象,并把3创建的对象以参数形式传递
package d9; public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
package d9; public class MyRunnableDemo { public static void main(String[] args) { MyRunnable my = new MyRunnable(); Thread t1 = new Thread(my); Thread t2 = new Thread(my,"李四"); t1.setName("张三"); t1.start(); t2.start(); } }
2.3 Callable,会在线程池部分讲解
3:设置和获取线程名称
package d9; public class MyThread extends Thread { public MyThread() {} public MyThread(String name) { super(name); } @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(getName()+" : "+ i); } } }
package d9; public class MyThreadDemo { public static void main(String[] args) { //创建线程对象 MyThread my = new MyThread("逍遥"); MyThread my2 = new MyThread(); //my.run(); //my.run(); //run() 方法仅仅是封装该线程执行的代码,直接调用是管道方法 //start() 方法首先启动了线程,然后再由jvm去掉用该线程的run方法 my2.setName("小天狼");//给线程命名 my.start(); my2.start(); //输出当前线程的方法名臣 System.out.println(Thread.currentThread().getName()); } }
4:线程调度和线程控制
4.1线程调度
package d9; public class ThreadPriority extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i <100; i++) { System.out.println(getName()+": "+i); } } }
package d9; public class ThreadPriorityDemo { public static void main(String[] args) { //创建线程对象 MyThread my = new MyThread("逍遥"); MyThread my2 = new MyThread(); my2.setName("小天狼");//给线程命名 my.start(); my2.start(); //线程优先级的范围1-10默认为5 my2.setPriority(1); } }
4.2线程控制
4.2.1 线程休眠
package d9; import java.util.Date; public class ThreadSleep extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(getName()+":"+i+"当前时间"+new Date()); } } }
package d9; public class ThreadSleepDemo { public static void main(String[] args) { // TODO Auto-generated method stub ThreadSleep s1 = new ThreadSleep(); ThreadSleep s2 = new ThreadSleep(); ThreadSleep s3 = new ThreadSleep(); s1.setName("张三"); s2.setName("李四"); s3.setName("王五"); s1.start(); s2.start(); s3.start(); } }
4.2.2 线程加入
package d9; public class ThreadJoin extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(getName()+" : "+ i); } } }
package d9; public class ThreadJoinDemo { public static void main(String[] args) { // TODO Auto-generated method stub ThreadJoin s1 = new ThreadJoin(); ThreadJoin s2 = new ThreadJoin(); ThreadJoin s3 = new ThreadJoin(); s1.setName("张三"); s2.setName("李四"); s3.setName("王五"); s1.start(); try { s1.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } s2.start(); s3.start(); } }
4.2.3 线程礼让(并不能完全实现)
package d9; public class ThreadYield extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(getName()+" : "+ i); Thread.yield(); } } }
package d9; public class ThreadYieldDemo { public static void main(String[] args) { //创建线程对象 ThreadYield my = new ThreadYield(); ThreadYield my2 = new ThreadYield(); my.setName("逍遥");; my2.setName("小天狼");//给线程命名 my.start(); my2.start(); } }
4.2.4 后台线程(守护线程)
package d9; public class ThreadDaemon extends Thread { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(getName()+" : "+ i); } } }
package d9; public class ThreadDaemonDemo { public static void main(String[] args) { //创建线程对象 ThreadDaemon my = new ThreadDaemon(); ThreadDaemon my2 = new ThreadDaemon(); my.setName("逍遥");; my2.setName("小天狼"); my.setDaemon(true); my2.setDaemon(true); my.start(); my2.start(); Thread.currentThread().setName("杨小可"); for(int x=0;x<5;x++) { System.out.println(Thread.currentThread().getName()); } } }
4.2.5 中断线程
package d9; public class ThreadInterruptDemo { public static void main(String[] args) { //创建线程对象 ThreadInterrupt my = new ThreadInterrupt(); my.setName("逍遥");; my.start(); //超过3秒如果不醒来,就停止 try { Thread.sleep(3000); my.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
package d9; import java.util.Date; public class ThreadInterrupt extends Thread { @Override public void run() { System.out.println("开始执行: "+new Date()); try { Thread.sleep(10000); } catch (InterruptedException e) { System.out.println("线程被终止了 "); } System.out.println("结束执行: "+new Date()); } }
5 线程的生命周期
5.1 简单生命周期
新建:创建线程对象
就绪:有执行资格,没有执行权
运行:有执行资格,有执行权
阻塞:由于一些操作让线程保护欲了该状态,没有执行资格,没有执行权
而另一些操作却可以把它激活,激活后处于就绪状态
死亡:线程对象变成垃圾,等待被回收
5.2
常见的几种情况
A:新建—就绪—运行—结束
B:新建—就绪—运行—就绪—运行--结束
C:新建—就绪—运行—其他阻塞--就绪—运行—结束
D:新建—就绪—运行—同步阻塞--就绪—运行—结束
E:新建—就绪—运行—等待阻塞--同步阻塞--就绪—运行--结束
6 练习题
6.1 模拟电影票售票系统
6.1.1 版本1.0
package d9; public class SellTicket implements Runnable { //定义一个资源 100张票 int ticketNumber = 100; @Override public void run() { // TODO Auto-generated method stub while (true) { if (ticketNumber>0) { System.out.println(Thread.currentThread().getName()+"正在出售第"+ticketNumber+"张票"); ticketNumber--; }else { break; } } } }
package d9; public class SellTicketDemo { public static void main(String[] args) { // TODO Auto-generated method stub SellTicket st = new SellTicket(); Thread t1 = new Thread(st,"窗口1"); Thread t2 = new Thread(st,"窗口2"); Thread t3 = new Thread(st,"窗口3"); t1.start(); t2.start(); t3.start(); } }
6.1.2版本2.0
由1.0可以查看出有重复票卖出.这时候应该用static定义一个全局变量
6.1.3 同步代码块
线程安全分析:由于该线程是多线程的,而且有共享资源(100张票).并且有多条语句操作共享数据(三个窗口).所以会存在安全隐患
同步代码块:
synchronized(对象){
需要同步的代码;
}
同步方法:即把同步关键字加在方法上,此时的锁对象是this
静态方法的锁对象是累的字节码文件,即SellTicket.class
6.1.4 锁
6.1.5 死锁:两个锁资源由于相互调用而出现相互等待的现象
1 package d9; 2 3 public class MyLock { 4 //第一步,创建两把锁对象 5 public static final Object objA = new Object(); 6 public static final Object objB = new Object(); 7 }
1 package d9; 2 3 public class DieLock extends Thread { 4 private boolean flag; 5 public DieLock (boolean flag) { 6 this.flag = flag; 7 } 8 public void run() { 9 if (flag) { 10 synchronized (MyLock.objA) { 11 System.out.println("if objA"); 12 synchronized(MyLock.objB) { 13 System.out.println("if objB"); 14 } 15 } 16 }else { 17 synchronized (MyLock.objB) { 18 System.out.println("else objB"); 19 synchronized(MyLock.objA) { 20 System.out.println("else objA"); 21 } 22 } 23 } 24 } 25 }
package d9; public class DieLockDemo { public static void main(String[] args) { // TODO Auto-generated method stub DieLock di1 = new DieLock(true); DieLock di2 = new DieLock(false); di1.start(); di2.start(); } }
7 生产者消费者模式
1:资源类(Student) 2:生产者(SetThread) 3:消费者(GetThread) 4:测试类
7.1 Version1.0
package StudentLX; public class Student { //创建一个资源类,Student String name; int age; }
package StudentLX; public class SetThread implements Runnable { private Student s; public SetThread(Student s) { this.s = s; } @Override public void run() { // TODO Auto-generated method stub s.name = "逍遥"; s.age = 18; } }
package StudentLX; public class GetThread implements Runnable { private Student s ; public GetThread (Student s) { this.s =s; } public void run() { System.out.println("姓名:"+s.name+"年龄: "+s.age); } }
package StudentLX; public class StudentDemo { public static void main(String[] args) { //创建资源 Student s = new Student(); //设置和获取类 GetThread gt= new GetThread(s); SetThread st= new SetThread(s); //创建线程 Thread t1 = new Thread(gt); Thread t2 = new Thread(st); //启动线程 t1.start(); t2.start(); } }
多线程问题是会存在安全隐患的.进一步修改SetThread和GetThread方法
7.2 Version2.0
package StudentLX; public class SetThread implements Runnable { private Student s; public SetThread(Student s) { this.s = s; } @Override public void run() { // TODO Auto-generated method stub int i =1; while (true) { if (i%2==0) { s.name = "逍遥"; s.age = 18; }else { s.name = "小天狼"; s.age = 22; } i++; } } }
package StudentLX; public class GetThread implements Runnable { private Student s ; public GetThread (Student s) { this.s =s; } public void run() { while (true) { System.out.println("姓名: "+s.name+";---: "+s.age); } } }
原因分析以及解决方法
A:多线程环境;B:共享同一资源;C共用同一代码.
解决方法线程同步:A:不同种类的线程都要加锁(GetThread和SetThread);B不同种类的线程加同一把锁
package StudentLX; public class SetThread implements Runnable { private Student s; public SetThread(Student s) { this.s = s; } @Override public void run() { // TODO Auto-generated method stub int i = 1; while (true) { synchronized (s) { if (i % 2 == 0) { s.name = "逍遥"; s.age = 18; } else { s.name = "小天狼"; s.age = 22; } i++; } } } }
package StudentLX; public class GetThread implements Runnable { private Student s ; public GetThread (Student s) { this.s =s; } public void run() { while (true) { synchronized (s) { System.out.println("姓名: "+s.name+";---: "+s.age); } } } }
7.3 Version3.0 等待唤醒机制
思路:
A:生产者 先看是否有数据,有就等待;没有就生产,生产完之后通知消费者进行消费
B:消费者 先看是否有数据,有就消费;没有就等待,通知生产者生产数据
等待唤醒
Object类中提供了三个方法
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
package StudentLX; public class Student { //创建一个资源类,Student boolean flag;//定义一个标志,用于判断是否有数据 String name; int age; }
package StudentLX; public class SetThread implements Runnable { private Student s; public SetThread(Student s) { this.s = s; } @Override public void run() { // TODO Auto-generated method stub int i = 1; while (true) { synchronized (s) { //生产者:如果有就等待 ;如果没有就生产; if (s.flag) { try { s.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (i % 2 == 0) { s.name = "逍遥"; s.age = 18; } else { s.name = "小天狼"; s.age = 22; } i++; s.flag=true; //生产完成,通知消费者(唤醒线程) s.notify(); } } } }
package StudentLX; public class GetThread implements Runnable { private Student s ; public GetThread (Student s) { this.s =s; } public void run() { while (true) { synchronized (s) { //消费者,如果有就消费;没有就等待 if (!s.flag) { try { s.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("姓名: "+s.name+";---: "+s.age); //修改标记 s.flag=false; s.notify(); } } } }
package StudentLX; public class StudentDemo { public static void main(String[] args) { //创建资源 Student s = new Student(); //设置和获取类 GetThread gt= new GetThread(s); SetThread st= new SetThread(s); //创建线程 Thread t1 = new Thread(gt); Thread t2 = new Thread(st); //启动线程 t1.start(); t2.start(); } }
7.4 version4.0 Student变量私有化
package StudentLX; public class Student { // 第一步,修改为私有成员 private boolean flag;// private String name; private int age; //第二步,创建SetStudent方法,并设置为同步方法 public synchronized void setStudent(String name,int age) { //如果存在,等待 if (this.flag) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //创建 this.name = name; this.age = age; //修改标签 this.flag=true; //唤醒 this.notify(); } public synchronized void getStudent() { //如果不存在,等待 if (!this.flag) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //使用 System.out.println(this.name+"---"+this.age); //修改标签 this.flag=false; //唤醒 this.notify(); } }
package StudentLX; public class SetThread implements Runnable { private Student s; public SetThread(Student s) { this.s = s; } @Override public void run() { // TODO Auto-generated method stub int i = 1; while (true) { if (i % 2 == 0) { s.setStudent("逍遥", 18); } else { s.setStudent("小天狼", 22); } i++; } } }
package StudentLX; public class GetThread implements Runnable { private Student s ; public GetThread (Student s) { this.s =s; } public void run() { while (true) { s.getStudent(); } } }
package StudentLX; public class StudentDemo { public static void main(String[] args) { //创建资源 Student s = new Student(); //设置和获取类 GetThread gt= new GetThread(s); SetThread st= new SetThread(s); //创建线程 Thread t1 = new Thread(gt); Thread t2 = new Thread(st); //启动线程 t1.start(); t2.start(); } }
8 线程组:多个线程组合到一起
package d9; public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
package d9; public class ThreadGroupDemo { public static void main(String[] args) { // 获取线程所属线程组的名称 method1(); // 设置线程所属的线程组 method2(); } private static void method2() { // 创建一个线程组 ThreadGroup tg = new ThreadGroup("新的线程组"); MyRunnable my = new MyRunnable(); // 创建线程对象,并分组 Thread t1 = new Thread(tg, my, "逍遥new"); Thread t2 = new Thread(tg, my, "小天狼new"); // 输出线程名称 System.out.println(t1.getThreadGroup().getName() ); System.out.println(t2.getThreadGroup().getName() ); // 通过线程组控制该组内的所有线程,例如:设置优先级和后台线程 tg.setMaxPriority(4); } private static void method1() { MyRunnable my = new MyRunnable(); Thread t1 = new Thread(my, "逍遥"); Thread t2 = new Thread(my, "小天狼"); // 获取线程的所属线程组 ThreadGroup gt1 = t1.getThreadGroup(); ThreadGroup gt2 = t2.getThreadGroup(); // 通过ThreadGroup中的getName()方法获取线程组名称 String name1 = gt1.getName(); String name2 = gt2.getName(); System.out.println(name1); System.out.println(name2); // 获取main线程的线程组名称 System.out.println(Thread.currentThread().getThreadGroup().getName()); } }
9 线程池
9.1 通过实现Runnable接口
A:创建一个线程池对象,控制要创建几个线程对象 public static ExecutorService newFixedThreadPoll(int nThreads) B:这种线程池的线程可以执行 可以执行Runnable对象或者Callable对象代表的线程 做一个类实现Runnable接口 C:调用如下方法 Future<?> submit(Runnable task) <T> Future<T> subbmit(Callable<T> task)
package d9; public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }
package d9; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class ExecutorsDemo { public static void main(String[] args) { // 创建线程池 ExecutorService tpool = Executors.newFixedThreadPool(2); //创建线程对象,并放入到线程池中 tpool.submit(new MyRunnable()); tpool.submit(new MyRunnable()); //结束线程池 tpool.shutdown(); } }
9.2 通过实现Callable接口
package d9; import java.util.concurrent.Callable; public class MyCallable implements Callable { @Override public Object call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } return null; } }
package d9; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CallableDemo { public static void main(String[] args) { //创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); pool.submit(new MyCallable()); pool.submit(new MyCallable()); } }
9.3 通过实现Callable接口实现具有返回值的线程池(求和)
package d9; import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { private int startNumber; private int endNumber; public MyCallable(int startNumber,int endNumber) { this.startNumber = startNumber; this.endNumber = endNumber; } @Override public Integer call() throws Exception { int sum=0; for (int i = startNumber; i <= endNumber; i++) { sum+=i; } return sum; } }
package d9; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { //创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); Future<Integer> sum1= pool.submit(new MyCallable(1,100)); Future<Integer> sum2= pool.submit(new MyCallable(1,50)); int f1 = sum1.get(); System.out.println(f1); System.out.println(sum2.get()); } }
10 匿名内部类实现多线程
package d9; public class ThreadAnonymous { public static void main(String[] args) { // 01 继承Thread类 实现多继承 new Thread() { public void run() { for (int i = 0; i < 100; i++) { System.out.println("Hello" + i); } }; }.start(); // 02 通过实现runable接口来实现 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("World" + i); } } }) { }.start(); // 03 混合实现,其实只走方法内部的 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("java" + i); } } }) { public void run() { for (int i = 0; i < 100; i++) { System.out.println("!!" + i); } }; }.start(); } }
11 定时线程
定时器:可以在指定的时间做某件事情,还可以重复做某件事情
依赖Timer(定时)和TimerTask(任务)这两个类
import java.util.Timer; import java.util.TimerTask; public class TimeDemo { public static void main(String[] args) { //创建定时器对象 Timer t = new Timer(); //执行任务 t.schedule(new MyTask(), 3000); } } class MyTask extends TimerTask{ @Override public void run() { // TODO Auto-generated method stub System.out.println("beng,爆炸了!!!"); } }
import java.util.Timer; import java.util.TimerTask; public class TimeDemo { public static void main(String[] args) { //创建定时器对象 Timer t = new Timer(); //执行任务+结束任务 t.schedule(new MyTask(t), 3000); } } class MyTask extends TimerTask{ private Timer t; public MyTask() {} public MyTask(Timer t) { this.t =t; } @Override public void run() { // TODO Auto-generated method stub System.out.println("beng,爆炸了!!!"); t.cancel(); } }
import java.util.Timer; import java.util.TimerTask; public class TimeDemo { public static void main(String[] args) { //创建定时器对象 Timer t = new Timer(); //执行任务+结束任务 //3秒后执行爆炸任务,如果不成功,每个2秒后再继续炸 t.schedule(new MyTask(), 3000,2000); } } class MyTask extends TimerTask{ @Override public void run() { // TODO Auto-generated method stub System.out.println("beng,爆炸了!!!"); } }
练习:定时删除指定文件夹
package d9; import java.io.File; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Iterator; import java.util.Timer; import java.util.TimerTask; public class FolderDelete { public static void main(String[] args) throws ParseException { //创建定时器对象 Timer t = new Timer(); //执行任务 String stTime = "2018-3-30 07:21:06"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse(stTime); t.schedule(new DeleteFolder(), d,100000); } } class DeleteFolder extends TimerTask{ @Override public void run() { File srcFolder = new File("D:\\aaa"); deleteFolder(srcFolder); System.out.println("beng,爆炸了!!!"); } private void deleteFolder(File srcFolder) { // TODO Auto-generated method stub File [] fileArray = srcFolder.listFiles(); if (fileArray != null) { for (File file : fileArray) { if (file.isDirectory()) { deleteFolder(file); }else { file.delete(); } } srcFolder.delete(); } } }