java多线程
创建线程的两种方式
继承Thread类
1创建一个继承于Thread类的子类
2.重写Thread类的run方法() --> 将此线程执行的操作声明在run()方法中
3.创建Thread类的子类的对象
4.通过此对象调用start()
案例:多窗口售票(暂不考虑线程安全)
1 //1.创建一个继承于Thread类的子类 2 class Window extends Thread{ 3 4 private static int ticket = 100; 5 //2.重写Thread类的run方法() 6 @Override 7 public void run() { 8 while (true){ 9 if(ticket > 0){ 10 System.out.println(getName() + " : 卖票,票号为: " + ticket); 11 ticket--; 12 }else { 13 break; 14 } 15 } 16 } 17 } 18 19 public class WindowTest { 20 public static void main(String[] args) { 21 //3.创建Thread类的子类的对象 22 Window t1 = new Window(); 23 Window t2 = new Window(); 24 Window t3 = new Window(); 25 26 t1.setName("窗口1"); 27 t2.setName("窗口2"); 28 t3.setName("窗口3"); 29 //4.通过此对象调用start():启动当前线程;调用当前线程的run()方法 30 t1.start(); 31 t2.start(); 32 t3.start(); 33 } 34 }
实现Runnable接口
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法: run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
案例:多窗口售票(暂不考虑线程安全)
1 //1.创建一个实现了Runnable接口的类 2 class Window1 implements Runnable{ 3 private int ticket = 100; 4 //2.实现类去实现Runnable中的抽象方法: run() 5 @Override 6 public void run() { 7 while (true){ 8 if(ticket > 0){ 9 System.out.println(Thread.currentThread().getName() + " : 卖票,票号为: " + ticket); 10 ticket--; 11 }else { 12 break; 13 } 14 } 15 } 16 } 17 18 public class WindowTest1 { 19 public static void main(String[] args) { 20 //3.创建实现类的对象 21 Window1 w = new Window1(); 22 //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 23 Thread t1 = new Thread(w); 24 Thread t2 = new Thread(w); 25 Thread t3 = new Thread(w); 26 27 t1.setName("窗口1"); 28 t2.setName("窗口2"); 29 t3.setName("窗口3"); 30 //5.通过Thread类的对象调用start():启动线程;调用当前线程的run()-->调用了Runnable类型的target的run() 31 t1.start(); 32 t2.start(); 33 t3.start(); 34 } 35 }
两种方法的比较
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1.实现的方法没有类的单继承的局限性
* 2.实现的方式更适合来处理多个线程有共享数据的情况
* 联系:public class Thread implements Runnable
* 相同点:两种方法都需要重写run(),将线程要执行的逻辑声明在run()中
线程的生命周期
线程同步
问题:在卖票过程中,出现了重票,错票---->出现了线程安全问题。
原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
解决方法:当一个线程A在操作ticket的时候,其他线程不能参与进来。直到线程A操作完ticket时,其他线程才可以参与进来。即使线程A出现了阻塞也不能被改变,举个例子,当你上厕所上到一半时,睡着了,其他人也要等你醒来把厕所上完出来,才可以进去上厕所。
在Java中,我们通过同步机制,来解决线程安全问题。
方式一:同步代码块
1 synchronized(同步监视器){ 2 //需要被同步的代码 3 }
说明:1.操作共享数据的代码,即为需要被同步的代码---->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器, 俗称:锁。任何一个类的对象,都可以充当锁,但必须是唯一的。
要求:多个线程必须用同一把锁
补充:在实现Runnable接口创建多线程的方法中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器。可以考虑当前类来充当同步监视器。
同步代码块处理实现Runnable的线程安全问题
1 class Window1 implements Runnable{ 2 private int ticket = 100; 3 Object obj = new Object(); 4 5 @Override 6 public void run() { 7 while (true){ 8 synchronized (obj){ 9 if(ticket > 0){ 10 System.out.println(Thread.currentThread().getName() + " : 卖票,票号为: " + ticket); 11 ticket--; 12 }else { 13 break; 14 } 15 } 16 } 17 } 18 } 19 20 public class WindowTest1 { 21 public static void main(String[] args) { 22 Window1 w = new Window1(); 23 24 Thread t1 = new Thread(w); 25 Thread t2 = new Thread(w); 26 Thread t3 = new Thread(w); 27 28 t1.setName("窗口1"); 29 t2.setName("窗口2"); 30 t3.setName("窗口3"); 31 32 t1.start(); 33 t2.start(); 34 t3.start(); 35 } 36 }
同步代码块处理继承Thread类的线程安全问题。
1 class Window extends Thread{ 2 3 private static int ticket = 100; 4 //继承方式注意加static,将obj更换为Window.class类对象也可以 5 private static Object obj = new Object(); 6 7 @Override 8 public void run() { 9 while (true){ 10 //synchronized (Window.class) 11 synchronized (obj){ 12 if(ticket > 0){ 13 System.out.println(getName() + " : 卖票,票号为: " + ticket); 14 ticket--; 15 }else { 16 break; 17 } 18 } 19 } 20 } 21 } 22 23 public class WindowTest { 24 public static void main(String[] args) { 25 Window t1 = new Window(); 26 Window t2 = new Window(); 27 Window t3 = new Window(); 28 29 t1.setName("窗口1"); 30 t2.setName("窗口2"); 31 t3.setName("窗口3"); 32 33 t1.start(); 34 t2.start(); 35 t3.start(); 36 } 37 }
方法二:同步方法
同步方法处理实现Runnable的线程安全问题
class Window1 implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ show(); } } private synchronized void show(){//同步监视器:this if(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " : 卖票,票号为: " + ticket); ticket--; } } } public class WindowTest1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
使用同步方法处理继承Thread类的线程安全问题
1 class Window extends Thread{ 2 3 private static int ticket = 100; 4 5 @Override 6 public void run() { 7 while(true){ 8 show(); 9 } 10 } 11 private static synchronized void show(){ 12 // private synchronized void show(){//同步监视器:t1,t2,t3,此种解决方式是错误的 13 if(ticket > 0){ 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 System.out.println(Thread.currentThread().getName() + " : 卖票,票号为: " + ticket); 20 ticket--; 21 } 22 } 23 } 24 25 public class WindowTest { 26 public static void main(String[] args) { 27 Window t1 = new Window(); 28 Window t2 = new Window(); 29 Window t3 = new Window(); 30 31 t1.setName("窗口1"); 32 t2.setName("窗口2"); 33 t3.setName("窗口3"); 34 35 t1.start(); 36 t2.start(); 37 t3.start(); 38 } 39 }
关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只是不需要我们显视的的声明;
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
同步的方式,解决了线程的安全问题。操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
创建线程的方法三,四
jdk5.0新增两种创建线程的方式
实现Callable接口
与使用Runnable相比,Callable功能更加强大
1.call()相比run()方法,可以有返回值
2.call()方法可以抛出异常,被外面的操作捕获,获取异常的信息。
3.支持泛型的返回值
4.需要借助FutureTask类,比如获取返回结果
1 import java.util.concurrent.Callable; 2 import java.util.concurrent.ExecutionException; 3 import java.util.concurrent.FutureTask; 4 5 //1.创建一个实现Callable的实现类 6 class NumThread implements Callable{ 7 //2.实现call方法,将此线程需要执行的操作声明在call()中 8 @Override 9 public Object call() throws Exception { 10 int sum = 0; 11 for(int i = 1; i <= 100; i++){ 12 if(i % 2 == 0){ 13 System.out.println(i); 14 sum += i; 15 } 16 } 17 return sum; 18 } 19 } 20 21 public class ThreadNew { 22 public static void main(String[] args) { 23 //3.创建Callable接口实现类的对象 24 NumThread numThread = new NumThread(); 25 //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象 26 FutureTask futureTask = new FutureTask(numThread); 27 //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并start()调用。 28 new Thread(futureTask).start(); 29 30 try { 31 //6.获取Callable中call方法的返回值。 32 //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值 33 Object sum = futureTask.get(); 34 System.out.println("总和为:" + sum); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } catch (ExecutionException e) { 38 e.printStackTrace(); 39 } 40 } 41 }
使用线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。
提高响应速度(减少了创建新线程的时间),降低资源笑话(重复利用线程池中线程,不需要每次都创建)
便于线程管理:
corePoolSize: 核心池的大小
maximumPoolSize: 最大线程池
keepAliveTime: 线程没有任务时最多保持多长时间后会终止
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 import java.util.concurrent.ThreadPoolExecutor; 4 5 class NumberThread implements Runnable{ 6 7 @Override 8 public void run() { 9 for(int i = 0; i <= 100;i++){ 10 if(i % 2 == 0){ 11 System.out.println(Thread.currentThread().getName() + ": "+ i); 12 } 13 } 14 } 15 } 16 17 class NumberThread1 implements Runnable{ 18 19 @Override 20 public void run() { 21 for(int i = 0; i <= 100;i++){ 22 if(i % 2 != 0){ 23 System.out.println(Thread.currentThread().getName() + ": "+ i); 24 } 25 } 26 } 27 } 28 public class ThreadPool { 29 30 public static void main(String[] args) { 31 //1.提供指定线程数量的线程池 32 ExecutorService service = Executors.newFixedThreadPool(10); 33 ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; 34 //设置线程池属性 35 // System.out.println(service.getClass()); 36 // service1.setCorePoolSize(15); 37 // service1.setKeepAliveTime(); 38 39 //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 40 service.execute(new NumberThread());//适合适用于Runnable; 41 service.execute(new NumberThread1()); 42 // service.submit(Callable callable);//适合使用于Callable; 43 //3.关闭连接池 44 service.shutdown(); 45 } 46 }
本文来自博客园,作者:锦此,转载请注明原文链接:https://www.cnblogs.com/jinci2022/p/16514557.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律