一个进程Process可以有多个线程Thread。
进程是执行程序的一次执行过程,是一个动态概念,是系统资源分配的单位。
线程就是独立的执行路径。程序运行时,即使没有自己创建线程,后台也存在线程,如主线程、GC线程。
1.线程的三种创建方式
线程的三种创建方式:继承Thread类、实现Runnable接口、实现Callable接口。
1.继承Thread类
public class TestThread1 extends Thread{ @Override public void run() { //run方法线程 for (int i = 0; i < 21; i++) { System.out.println(i); } } //主线程 public static void main(String[] args) { TestThread1 testThread1 = new TestThread1(); testThread1.start();//调用start方法开启线程 for (int i = 0; i < 21; i++) { System.out.println("main"+i); } } }
结果:交替进行
package com.lesson; 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("https://i1.hdslb.com/bfs/archive/28f5df1e2f2aa0c5b49ad67e34fd312f4bb7e75a.jpg@412w_232h_1c.jpg","1.jpg"); TestThread2 t2=new TestThread2("https://i1.hdslb.com/bfs/archive/92c9d97eb3d9914d97149bb3bf2c2dea3682855c.jpg@412w_232h_1c.jpg","2.jpg"); TestThread2 t3=new TestThread2("https://i0.hdslb.com/bfs/archive/4a00d3fb294cb63fc414f6819e277f6548c4134a.jpg@412w_232h_1c.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异常"); } } }
结果:同时下载
2.实现Runnable接口(推荐使用)
package com.lesson; //Runnable接口 public class TestThread3 implements Runnable{ @Override public void run() { for (int i = 0; i < 21; i++) { System.out.println(i); } } //主线程 public static void main(String[] args) { //创建Runnable接口的实现类对象 TestThread3 testThread3 = new TestThread3(); //创建线程对象,通过线程对象来开启我们的线程 Thread thread=new Thread(testThread3); thread.start(); for (int i = 0; i < 21; i++) { System.out.println("main"+i); } } } 避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。
package com.lesson; //多个线程同时操作同一个对象,如多人买火车票 public class TestThread4 implements Runnable{ private int ticketNum = 10; @Override public void run() { while (true){ //Thread.currentThread().getName()获取当前线程的名字 if (ticketNum<=0){ break; } //模拟延时 try { Thread.sleep(200); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"买走了第"+ticketNum--+"票"); } } public static void main(String[] args) { TestThread4 ticket = new TestThread4(); new Thread(ticket,"one").start(); new Thread(ticket,"two").start(); new Thread(ticket,"three").start(); } }
结果:
出现多人买走同一张票的情况,线程不安全,数据紊乱,即并发问题。
龟兔赛跑:
package com.lesson; //龟兔赛跑:同时开始比赛,兔子比龟快,但是兔子开始睡觉,乌龟赢得比赛 public class Race implements Runnable{ private static String winner;//static保证只有一个winner @Override public void run() { for (int i=1;i<100;i++){ if (Thread.currentThread().getName().equals("兔子")&&i==10){ //跑一半时兔子休息 try { Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } } boolean flag=raceOver(i); if (flag){ break; } System.out.println(Thread.currentThread().getName()+"跑了"+i+"步"); } } //判断是否完成比赛 private boolean raceOver(int step){ if (winner!=null){//已存在胜利者 return true; }else{ if (step>=20){//判断胜者 winner=Thread.currentThread().getName(); System.out.println("赢者:"+winner); return true; } } return false; } public static void main(String[] args) { Race race=new Race(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); } }
3.实现Callable接口
优点:可以返回值
缺点:实现方式复杂
package com.lesson; 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() { WebDownLoader2 webDownLoader=new WebDownLoader2(); webDownLoader.downLoader(url,name); System.out.println(name); return true; } public static void main(String[] args) throws ExecutionException,InterruptedException { TestCallable t1=new TestCallable("https://i1.hdslb.com/bfs/archive/28f5df1e2f2aa0c5b49ad67e34fd312f4bb7e75a.jpg@412w_232h_1c.jpg","1.jpg"); TestCallable t2=new TestCallable("https://i1.hdslb.com/bfs/archive/92c9d97eb3d9914d97149bb3bf2c2dea3682855c.jpg@412w_232h_1c.jpg","2.jpg"); TestCallable t3=new TestCallable("https://i0.hdslb.com/bfs/archive/4a00d3fb294cb63fc414f6819e277f6548c4134a.jpg@412w_232h_1c.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(); //关闭服务 ser.shutdown(); } } class WebDownLoader2{ 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异常"); } } }
2. Lambda 表达式
new Thread (()->System.out.println("a")).start();
Lambda表达式:避免匿名内部类定义过多,使代码整洁。
函数式接口(Functional Interface):任何接口,如果只包含唯一一个抽象方法,它就是一个函数式接口。
package com.lesson; //推导lambda表达式 public class TestLambda1 { public static void main(String[] args) { iLike like=new Like();//接口回调 like.lambda(); } } //1.定义一个函数式接口 interface iLike{ void lambda(); } //2.实现类 class Like implements iLike{ @Override public void lambda() { System.out.println("i like lambda"); } }
使用静态内部类进行优化:
public class TestLambda1 { //3.静态内部类 static class Like2 implements iLike{ @Override public void lambda() { System.out.println("i like lambda"); } } public static void main(String[] args) { iLike like=new Like();//接口回调 like=new Like2(); like.lambda(); } } //1.定义一个函数式接口 interface iLike{ void lambda(); }
使用局部内部类进行优化:
public class TestLambda1 { public static void main(String[] args) { iLike like=new Like();//接口回调 //4.局部内部类 class Like3 implements iLike{ @Override public void lambda() { System.out.println("i like lambda3"); } } like=new Like3(); like.lambda(); } } //1.定义一个函数式接口 interface iLike{ void lambda(); }
使用匿名内部类进行优化:
public class TestLambda1 { public static void main(String[] args) { iLike like=new Like();//接口回调 //5.匿名内部类 like=new iLike() { @Override public void lambda() { System.out.println("i like lambda4"); } }; like.lambda(); } } //1.定义一个函数式接口 interface iLike{ void lambda(); }
使用Lambda表达式:
public class TestLambda1 { public static void main(String[] args) { iLike like=new Like();//接口回调 //6.lambda表达式 like=()->{ System.out.println("i like lambda5"); }; like.lambda(); } } //1.定义一个函数式接口 interface iLike{ void lambda(); }
带参数的Lambda表达式:
package com.lesson; public class TestLambda2 { public static void main(String[] args) { /*完整版 iLove love=(String a)->{ System.out.println("i love "+a); };*/ //只有一行代码时,小括号与花括号都可以省去 //多个参数也可以去掉参数类型,但必须同时删去 iLove love=(a)->{ System.out.println("i love "+a); }; love.love("myself"); } } //接口 interface iLove{ void love(String a); }
3.静态代理模式
真实对象和代理对象都需要调用同一个接口,代理对象需要代理真实角色。
package com.lesson; //静态代理 public class StaticProxy { public static void main(String[] args) { new Company(new marriedPerson()).HappyMarry(); //new Thread(()-> System.out.println("Lambda")).start(); //Thread代理Runnable接口 } } interface Marry{ void HappyMarry(); } //真实角色(结婚者) class marriedPerson implements Marry{ @Override public void HappyMarry() { System.out.println("恭喜结婚"); } } //代理角色(婚庆公司) class Company implements Marry{ //真实目标角色 private Marry target; public Company(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("收拾结束"); } }
优点:代理对象可以实现比真实对象更多的方法,真实对象只需专注自己的事情。
4.线程状态
1.停止线程
package com.lesson; //建议线程正常停止->利用标志位。 //不要使用stop()或destroy()等过时或JDK不建议使用的方法 public class TestStop implements Runnable{ private boolean flag=true;//标志位 @Override public void run() { int i=0; while (flag){ System.out.println("run"+i++); } } public void stop(){ this.flag=false; } public static void main(String[] args) { TestStop testStop=new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 1001; i++) { System.out.println("main"+i); if (i==900){ testStop.stop();//切换标志位,使线程停止 System.out.println("stop"); } } } }
结果:
主线程跑完,线程停止。
2.线程休眠
sleep
package com.lesson; //模拟倒计时 public class TestSleep { 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; } } } }
3.线程礼让
yield
让当前正在执行的线程暂停(即转为就绪状态),但不阻塞。
让CPU重新调度,礼让不一定成功。
package com.lesson; public class TestYield { public static void main(String[] args) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield();//礼让 System.out.println(Thread.currentThread().getName()+"线程结束执行"); },"a").start(); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"线程开始执行"); System.out.println(Thread.currentThread().getName()+"线程结束执行"); },"b").start(); } }
结果:
4.线程强制执行
Join合并线程,其他线程处于阻塞状态,待此线程执行完成后,再执行其他线程。
package com.lesson; //类似插队 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(); for (int i = 0; i < 1000; i++) { if (i==200){ thread.join(); } System.out.println("main"+i); } } }
5.线程状态观测
Thread.State:
-
NEW 尚未启动
-
RUNNABLE 在Java虚拟机中执行
-
BLOCKED 被阻塞等待监视器锁定
-
WAITING 正在等待另一个线程执行特定动作
-
TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间
-
TERMINATED 已退出
package com.lesson; //线程状态观测 public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(()->{ for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("//"); } }); //观测状态 Thread.State state = thread.getState(); System.out.println(state); //观察启动后 thread.start(); state=thread.getState(); System.out.println(state); while (state!=Thread.State.TERMINATED){ //线程未终止 Thread.sleep(100); state=thread.getState(); System.out.println(state); } } }
结果:
线程只能启动一次。
5.线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,并按照优先级觉得应该调度哪个线程来执行。
优先级范围1-10:
-
Thread.MIN_PRIORITY=1;
-
Thread.MAX_PRIORITY=10;
-
Thread.NORM_PRIORITY=5;
更改方式:getPriority().setPriority(int x)
package com.lesson; //测试线程优先级 public class TestPriority extends Thread{ public static void main(String[] args) { //主线程默认优先级 System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); MyPriority myPriority=new MyPriority(); Thread t1=new Thread(myPriority); Thread t2=new Thread(myPriority); Thread t3=new Thread(myPriority); Thread t4=new Thread(myPriority); Thread t5=new Thread(myPriority); //先设置优先级,后启动 t1.start();//默认5 t2.setPriority(1); t2.start(); t3.setPriority(4); t3.start(); t4.setPriority(Thread.MAX_PRIORITY);//10 t4.start(); t5.setPriority(3); t5.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); } }
结果:
优先级只是意味着获得调度的概率低,不代表不被调用,最终还是看CPU的调度。
6.守护线程
线程分为用户线程和守护(daemon)线程。
虚拟机必须确保用户线程执行完毕,如main,不用等待守护线程执行完毕,如gc垃圾回收。
package com.lesson; public class TestDaemon { public static void main(String[] args) { God god = new God(); Person person = new Person(); Thread thread=new Thread(god); thread.setDaemon(true);//默认false表示用户线程 thread.start(); new Thread(person).start(); } } class Person implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("live"); } System.out.println("=========goodbye========"); } } class God implements Runnable{ @Override public void run() { System.out.println("God"); } }
7.线程同步
并发问题:多个线程操作同一个对象。
线程同步:等待机制->多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。sleep不会释放锁。
存在问题:一个线程持有锁会导致其他所有需要此锁的线程挂起;在多线程竞争时,加锁、释放锁会导致较多的上下文切换和调度延时,引起性能问题;如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置。
同步块:synchronized(obj){
}
锁的对象必须是变化的量。
package com.lesson; import java.util.concurrent.CopyOnWriteArrayList; //测试JUC安全类型的集合 public class TestJUC { public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(3000); System.out.println(list.size()); } }
8.死锁
产生死锁的四个必要条件:
-
互斥条件:一个资源每次只能被一个进程使用。
-
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:进程已获得的资源在未使用完之前,不能强行剥夺。
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
9.Lock
ReentrantLock类(可重入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义。可以手动开启和关闭锁。
package com.lesson; import java.util.concurrent.locks.ReentrantLock; public class TestLock { public static void main(String[] args) { TestLock2 testLock2=new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); } } class TestLock2 implements Runnable{ int ticket =10; //定义Lock锁 private final ReentrantLock lock=new ReentrantLock(); @Override public void run() { while (true){ lock.lock();//加锁 try { if (ticket>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ticket--); }else { break; } }finally { //解锁 lock.unlock(); } } } }
10.线程协作
生产者消费者问题。->管程法、信号灯法。
1.管程法
package com.lesson; //生产者消费者问题->利用缓冲区解决:管程法 public class TestPC { public static void main(String[] args) { SynContainer container=new SynContainer(); new Producer(container).start(); new Consumer(container).start(); } } //生产者 class Producer extends Thread{ SynContainer container; public Producer(SynContainer container){ this.container=container; } //生产 @Override public void run() { for (int i = 1; i < 100; i++) { try { container.put(new Production(i)); } catch (InterruptedException e) { e.printStackTrace(); } } } } //消费者 class Consumer extends Thread{ SynContainer container; public Consumer(SynContainer container){ this.container=container; } //消费 @Override public void run() { for (int i = 1; i < 100; i++) { try { container.get(); } catch (InterruptedException e) { e.printStackTrace(); } } } } //产品 class Production { int id;//产品编号 public Production(int id) { this.id = id; } } //缓冲区 class SynContainer{ Production[] productions=new Production[10]; int count=0; //生产者放入产品 public synchronized void put(Production production) throws InterruptedException { //当容器满时,等待 使用while可以解决多个生产者或消费者的问题 while (count==productions.length){ //通知消费者取出产品 this.wait(); } //如果容器未满,放入产品 productions[count]=production; count++; //将输出语句放入put()方法,以免线程切换了,但仍打印出生产了第11个产品 System.out.println("生产了第"+count+"个产品"); //放入产品后,通知消费者取出产品 this.notifyAll(); } //消费者取出产品 public synchronized Production get() throws InterruptedException { //当容器空时,等待 while (count==0){ //通知生产者放入产品 this.wait(); } //如果容器不为空,取出产品 count--; Production production=productions[count]; System.out.println("消费了第"+(count+1)+"个产品"); //取出产品后,通知生产者放入产品 this.notifyAll(); return production; } }
2.信号灯法
package com.lesson; //生产者消费者问题->信号灯法 public class TestPC2 { public static void main(String[] args) { TV tv=new TV(); new Actor(tv).start(); new Audience(tv).start(); } } //生产者->演员 class Actor extends Thread{ TV tv; public Actor(TV tv){ this.tv=tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i%2==0){ try { this.tv.act("节目A"); } catch (InterruptedException e) { e.printStackTrace(); } }else { try { this.tv.act("节目B"); } catch (InterruptedException e) { e.printStackTrace(); } } } } } //消费者->观众 class Audience extends Thread{ TV tv; public Audience(TV tv){ this.tv=tv; } @Override public void run() { for (int i = 0; i < 20; i++) { try { tv.view(); } catch (InterruptedException e) { e.printStackTrace(); } } } } //产品->节目 class TV{ //true 演员录制节目,观众等待;false 观众观看节目,演员等待 String program;//表演的节目 boolean flag=true; //录制节目 public synchronized void act(String program) throws InterruptedException { while (!flag){ this.wait(); } System.out.println("演员录制"+program); //录制完成,通知观众观看 this.notifyAll(); this.program=program; this.flag=!this.flag; } //观看节目 public synchronized void view() throws InterruptedException { while (flag){ this.wait(); } System.out.println("观众观看"+program); //观看完毕,通知演员录制 this.notifyAll(); this.flag=!this.flag; } }
11.线程池
经常创建、销毁、使用量特别大的资源,如并发情况下的线程,对性能影响很大。
提前创建好 多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。
优点:提高响应速度,降低资源消耗,便于线程管理。
Callable接口实现线程池见上,可以有返回值。
Runnable接口实现线程池(没有返回值):
package com.lesson; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //Runnable接口创建线程池 public class TestPool { public static void main(String[] args) { //1.创建线程池 ExecutorService service= Executors.newFixedThreadPool(10);//线程池大小 service.execute(new MyThread()); service.execute(new MyThread()); //2.关闭服务 service.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
结果: