JAVA编程之多线程 朱雀 2022-05-28 11:29 192阅读 0赞 **一、线程相关概念** 程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。 进程:是程序的一次执行过成程,或是正在运行的一个程序。动态过程:有它自身的差生,存在和消亡的过程。 程序是静态的,进程是动态的。 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径 若一个程序可同一时间执行多个线程,就是支持多线程 每个java程序有一个隐含的线程就是main Java 提供了三种创建线程的方法: * 通过实现 Runnable 接口; * 通过继承 Thread 类本身; * 通过 Callable 和 Future 创建线程。 实现Runnable接口比继承Thread类所具有的优势: 1):适合多个相同的程序代码的线程去处理同一个资源 2):可以避免java中的单继承的限制 3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立 4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类 **二、线程的优先级** 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。 Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN\_PRIORITY ) - 10 (Thread.MAX\_PRIORITY )。 默认情况下,每一个线程都会分配一个优先级 NORM\_PRIORITY(5)。 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。 **实现Runnable接口创建线程:** package com.cnpc.Thread; //创建一个线程方法一:实现Runnable接口 //实现的方式优于继承的方式:避免了java单继承的局限性 // 如果多个线程要操作同一份数据,更适合使用实现的方式 class RunnableDemo implements Runnable\{ private Thread thread; private String threadName; RunnableDemo(String name) \{ this.threadName=name; System.out.println("正在创建线程: "+threadName); \} public void run() \{ System.out.println("正在运行:"+threadName); try \{ for(int i=4;i>0;i--) \{ System.out.println("当前线程:"+threadName+", "+i); System.out.println("当前线程是否活动"+thread.isAlive()); //让线程睡眠一会儿 Thread.sleep(1000); \} \} catch (InterruptedException e) \{ System.out.println("线程: "+threadName+" 中断。。。"); \} System.out.println("线程:"+threadName+" 退出"); \} public void start() \{ System.out.println("正在开始 "+threadName); if(thread==null) \{ thread=new Thread(this,threadName); thread.start(); \} \} \} //Thread.yield(),暂停当前正在执行的线程对象,并执行其他对象。 //Thread.currentThread(),返回当前正在执行的线程的引用 public class RunnableThread \{ public static void main(String\[\] args) \{ RunnableDemo R1=new RunnableDemo("线程-1"); R1.start(); RunnableDemo R2=new RunnableDemo("线程-2"); R2.start(); \} \} **继承Thread方法创建线程:** package com.cnpc.Thread; //创建线程方法二:集成Thread类 //Thread.currentThread().getName() //Thread.currentThread().yield() class ThreadDemo extends Thread\{ private Thread thread; private String threadName; ThreadDemo(String Name) \{ threadName=Name; System.out.println("创建线程名为:"+threadName); \} public void run() \{ System.out.println("正在运行:"+threadName); try \{ for(int i=4;i>0;i--) \{ System.out.println("当前线程:"+threadName+", "+i); //让线程睡眠一会儿 Thread.sleep(1000); \} \} catch (InterruptedException e) \{ System.out.println("线程: "+threadName+" 中断。。。"); \} System.out.println("线程:"+threadName+" 退出"); \} public void start () \{ System.out.println("开始 " + threadName ); if (thread == null) \{ thread = new Thread (this, threadName); thread.start (); \} \} \} public class ExtendThreadThread \{ public static void main(String\[\] args) \{ ThreadDemo T1 = new ThreadDemo( "Thread-1"); T1.start(); ThreadDemo T2 = new ThreadDemo( "Thread-2"); T2.start(); \} \} **Thread的常用方法:** 1.start():启动线程并执行相应的run()方法 2.run():子线程要执行的代码放入run() 3.currentThread():静态的,调取当前的现场 4.getName():获取此线程的名字 5.setName():设置此线程的名字 6.yield():调用此方法的线程释放当前cpu的执行权 7.join():在A线程调用B线程的join方法,表示当执行到此,A线程停止执行,B线程参与进来,直至B线程执行完毕再执行A线程 8.isAlive():判断当前线程是否活动 9.sleep(long l)显式的使当前线程睡眠 10.线程通信:wait(),notify(),notifyAll() **三、线程调度策略** 1.时间片 2.抢占式:高优先级的优先使用cpu 使用多线程的优点 只使用单个线程完成多个任务,肯定比用多个线程完成的时间更短,为何仍需多线程呢 优点:1.提高应用程序的响应,对图形化界面更有意义,可增强用户体验。 2.提高计算机系统CPU的利用率 3.改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。 守护线程:用来服务用户线程的,通过在start()前调用thread.setDeamen().j将用户线程设置成守护线程,守护线程最典型的例子是java虚拟机后台的垃圾回收机制。 **四、线程的生命周期** * 新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。 * 就绪状态: 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。 * 运行状态: 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 * 阻塞状态: 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种: * 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。 * 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。 * 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。 * 死亡状态: 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。 start() 获得cpu执行权 正常执行完run()/Error未处理 新建 ----------> 就绪 <------------->运行 ---------------->死亡 失去CPU执行权 yield() ![70][] **五、多线程安全问题范例** //模拟火车站售票,开启三个窗口售票,总票数为100张 //以继承Thread类来创建线程 //此程序会出现错票和重票问题 //实现Runnable接口可以使用this关键字作为锁,但继承Thread类实现同步得将锁设置为静态的static对象 /\*//模拟火车站售票,开启三个窗口售票,总票数为100张 //以实现Runnable的方式创建Thread //此程序会出现错票和重票问题 //1.线程安全问题存在的原因 // 由于一个线程在操作共享数据的过程中,未执行完的情况下,另外的线程参与进来,导致共享数据存在安全问题。 //2.如何解决线程安全问题 // 必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作 //3.java如何实现解决线程安全问题 \* 1.同步代码块 \* synchronize(同步监视器)\{ \* //需要被同步的代码块(既为操作共享数据的代码) \* \} \* 共享数据:多个线程共同操作的变量 \* 同步监视器:由任何一个类的对象来充当, 哪个线程获取此监视器,谁就执行大括号里被同步的代码,俗称:锁(多线程必须使用同一个锁) \* 实现Runnable接口可以使用this关键字作为锁,但继承Thread类实现同步得将锁设置为静态的static对象 \* 2.同步方法 方法加synchronize关键字 \*/ **实现方法实现** class WindowImp implements Runnable \{ //100张票是三个窗口共享 ,如果不设置成static变量 static int ticket=100; Object object=new Object(); public void run() \{ while(true) \{ // synchronized (object) \{ synchronized (this) \{ //后面的主程序中wi只创建了一个,所以可以作为一把锁 \} if(ticket >0) \{ try \{ Thread.currentThread().sleep(100);; \} catch (InterruptedException e) \{ // TODO Auto-generated catch block e.printStackTrace(); \} System.out.println(Thread.currentThread().getName()+"售票,票号为: "+ticket--); \}else \{ break; \} \} \} \} public class ImpTicketing \{ public static void main(String\[\] args) \{ WindowImp wi =new WindowImp(); Thread thread1=new Thread(wi); Thread thread2=new Thread(wi); Thread thread3=new Thread(wi); thread1.start(); thread2.start(); thread3.start(); \} \} **继承方法来实现** package com.cnpc.Thread; //模拟火车站售票,开启三个窗口售票,总票数为100张 //以继承Thread类来创建线程 //此程序会出现错票和重票问题 //实现Runnable接口可以使用this关键字作为锁,但继承Thread类实现同步得将锁设置为静态的static对象 class Window extends Thread \{ static int ticket=100; static Object object=new Object(); public void run() \{ while(true) \{ // synchronized (this) \{ // //继承Thread类实现同步得将锁设置为静态的static对象,这里不能使用this // \} synchronized (object) \{ if(ticket >0) \{ System.out.println(Thread.currentThread().getName()+"售票,票号为: "+ticket--); \}else \{ break; \} \} \} \} \} public class ExtendsTicketing \{ public static void main(String\[\] args) \{ Window w1=new Window(); Window w2=new Window(); Window w3=new Window(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); \} \} **释放锁的操作** 1.当前线程的同步方法、同步代码块执行结束 2.当前线程在同步代码块,同步方法中遇到break、return终止了该代码块、该方法的继续执行 3.当前线程中出现了ERROR或Exception,导致异常结束 4.当前线程在同步代码块、同步方法中执行了线程对象的wait方法,当前线程暂停,并释放锁。 不会释放锁的操作 1.执行当前线程的Thread.sleep()、Thread.suspend()方法不会释放当前锁,只是睡眠。 package com.cnpc.Thread; /\* \* 银行有一个账户,有两个储户分别向同一个账户存3000,每次存1000,每次存完打印余额 \*/ class Account\{ static double balance; public Account() \{\} public synchronized void deposit(double amount)\{ balance=balance+amount; try \{ Thread.currentThread().sleep(100); \} catch (InterruptedException e) \{ // TODO Auto-generated catch block e.printStackTrace(); \} System.out.println(Thread.currentThread().getName()+":"+balance); \} \} class Customer extends Thread\{ Account account; public Customer(Account account) \{ this.account=account; \} public void run() \{ for(int i=0;i<3;i++) \{ account.deposit(1000); \} \} \} public class BankThread \{ public static void main(String\[\] args) \{ // TODO Auto-generated method stub Account account=new Account(); Customer c1=new Customer(account); Customer c2=new Customer(account); c1.setName("储户1"); c2.setName("储户2"); c1.start(); c2.start(); \} \} ![70 1][] **死锁:** 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁 解决: 专门的算法原则 尽量减少同步资源的定义 下面是死锁的范例 package com.cnpc.Thread; public class DeadLock \{ static StringBuffer sb1=new StringBuffer(); static StringBuffer sb2=new StringBuffer(); public static void main(String\[\] args) \{ new Thread() \{ public void run() \{ synchronized (sb1) \{ try \{ Thread.currentThread().sleep(10); \} catch (InterruptedException e) \{ // TODO Auto-generated catch block e.printStackTrace(); \} sb1.append("A"); synchronized (sb2) \{ sb2.append("B"); System.out.println(sb1); System.out.println(sb2); \} \} \} \}.start(); new Thread() \{ public void run() \{ synchronized (sb2) \{ try \{ Thread.currentThread().sleep(10); \} catch (InterruptedException e) \{ // TODO Auto-generated catch block e.printStackTrace(); \} sb2.append("C"); synchronized (sb1) \{ sb1.append("D"); System.out.println(sb1); System.out.println(sb2); \} \} \} \}.start(); \} \} **线程通信:** java.lang.Object提供的方法,只有在synchronize方法或synchronize代码块中才可使用 wait() :令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问 notify() :唤醒正在排队等待同步资源的线程中优先级最高者等待结束 notifyAll() :唤醒正在等待资源的所有线程结束等待 **生产者消费者问题** /\* \* 生产者与消费者问题: \* producer将产品交给clerk,而consumer从clerk处取走产品 \* 店员一次只能持有固定数量的产品比如(20),如果producer试图生产更多的产品,clerk会叫producer停一下, \* 如果店中有空位放产品了再通知producer继续生产,如果店中没有产品了,clerk会通知consumer等一下 \*/ //产品数量是共享数据,所以设计到同步,而且存在生产者和消费者的通信 class Clerk\{ int product; public synchronized void addProduct() \{ //生产产品 if(product>=20) \{ try \{ wait(); \} catch (InterruptedException e) \{ // TODO Auto-generated catch block e.printStackTrace(); \} \} else \{ product++; System.out.println(Thread.currentThread().getName()+":生产了第 "+product+"个产品"); notifyAll(); \} \} public synchronized void consumeProduct() \{ //消费产品 if(product<=0) \{ try \{ wait(); \} catch (InterruptedException e) \{ e.printStackTrace(); \} \} else \{ System.out.println(Thread.currentThread().getName()+":消费了第 "+product+"个产品"); product--; notifyAll(); \} \} \} class Producer implements Runnable\{ Clerk clerk; public Producer(Clerk clerk) \{ this.clerk=clerk; \} public void run() \{ System.out.println("生产者开始生产产品"); while(true) \{ try \{ Thread.currentThread().sleep(10); \} catch (InterruptedException e) \{ // TODO Auto-generated catch block e.printStackTrace(); \} clerk.addProduct(); \} \} \} class Consumer implements Runnable\{ Clerk clerk; public Consumer(Clerk clerk) \{ this.clerk=clerk; \} public void run() \{ System.out.println("消费者消费产品"); try \{ Thread.currentThread().sleep(10); \} catch (InterruptedException e) \{ // TODO Auto-generated catch block e.printStackTrace(); \} while(true) \{ clerk.consumeProduct(); \} \} \} public class ProduceConsumeThread \{ public static void main(String\[\] args) \{ Clerk clerk=new Clerk(); Producer producer1=new Producer(clerk); Producer producer2=new Producer(clerk); Consumer consumer =new Consumer(clerk); Thread t1=new Thread(producer1); Thread t2=new Thread(producer2); Thread t3=new Thread(consumer); t1.setName("生产者1"); t2.setName("生产者2"); t3.setName("消费者"); t1.start(); t2.start(); t3.start(); \} \} ![70 2][] **单例模式线程问题:** package com.cnpc.Thread; /\*懒汉式的单例线程安全问题 \* \*/ class Singleton\{ private static Singleton instance=null; private Singleton() \{ \} public static Singleton getInstance() \{ if(instance==null) \{ //使用同步锁效率较低,此判断可提高效率 synchronized (Singleton.class) \{ if(instance==null) \{ instance=new Singleton(); \} \} \} return instance; \} \} public class SingletonThread \{ public static void main(String\[\] args) \{ Singleton s1=Singleton.getInstance(); Singleton s2=Singleton.getInstance(); System.out.println(s1==s2); \} \} [70]: /images/20220528/38b9f7d58b7148a9a9927be99df8424d.png [70 1]: /images/20220528/f4eb4c9cbae9429398f7c98c06e1a30c.png [70 2]: /images/20220528/3f239f2922dd46f1afa45402a57f94fb.png
还没有评论,来说两句吧...