zoukankan      html  css  js  c++  java
  • Java-20 线程

    1.多线程概述

    • 进程:一个程序运行,程序在内存中分配的那片空间。

    • 线程:进程中一个执行单元执行路径

      进程中至少有一个线程,如果进程中有多个线程,就是多线程的程序。

    • 并行与并发:

      并行:某一时间点,有多个程序同时执行,多核CPU运行
      并发:某一时间段,有多个程序同时执行,并不是真正意义的同时执行。 为多线程。
      
    • 并发真的是同时执行吗?

      不是,而是时间间隔很短,造成同时执行感觉。
      
    • 多线程优势?

      提高了用户体验,提高了程序的运行效率,提高CPU使用率。
      

    2.开启线程两种方式

    • 开启线程

      /*
       * 1.继承Thread
       * 2.重写run方法
       * 3.创建子类的对象
       * 4.调用start方法
       * */
      public class ThreadDemo1 {
      	public static void main(String[] args) {
              // 创建子类的对象
      		Demo d1 = new Demo("Jack");
      		Demo d2 = new Demo("Tom");
              // 设置线程名字
      		d1.setName("d1");
      		// 获取d1执行线程名字
      		System.out.println(d1.getName());
      		// 获取当前线程名字
      		System.out.println(Thread.currentThread().getName());
              
              // 调用start方法
      		d1.start();
      		d2.start();
      	}
      }
      // 继承Thread
      class Demo extends Thread{
      	String nickName;
      	public Demo(String nickName) {
      		this.nickName = nickName;
      	}
          // 重写run方法
      	public void run() {
      		for(int i=0;i<30;i++) {
      			System.out.println(nickName + "---" + i);
      		}
      	}
      }
      // 整个运行过程有三个线程运行,主线程开启d1和d2线程
      
    • run方法与start方法区别

      start:开启新的线程,会自动调用run方法在新的线程中执行
      run:没有开启新的线程,只是普通方法
      
    • 开启新线程第二种方式

      声明实现Runnable接口的类,该类然后实现run方法,然后可以分配该类的实例,在创建Thread时做为一个参数来传递并启动,采用这种风格的同一个例子
      
      /*
       * 实现多线程第二种方式:
       * 1.实现Runnable
       * 2.重写run方法
       * 3.创建Runnable子类的对象
       * 4.创建Thread类的对象,把第三步的对象传到构造方法中
       * 5.使用Thread子类对象,调用start方法
       * */
      public class ThreadDemo2 {
      	public static void main(String[] args) {
      		Demo5 d = new Demo5();// 只有Thread或子类线程对象才是线程对象。它只是线程任务度夏宁。
      		Thread th = new Thread(d); // th才是线程对象
      		Thread th2 = new Thread(d);
      		th.start();
      		th2.start();
      	}
      }
      
      class Demo5 implements Runnable{
      	public void run() {
      		for (int i=0;i<20;i++) {
      			System.out.println(Thread.currentThread().getName() + "---" + i);
      		}
      	}
      }
      
    • 两种实现方式,区别是?

      第一种方式有局限性的,因为Java是单继承,如果一个类已经有一个继承,它就不能再继承Thread类,就无法实现多线程。而第二种实现通过接口方式实现更合理,并且第二种方式更加符合面向对象特点:高内聚低耦合,把线程对象和线程任务分离开了。
      

    3.线程中方法

    1.sleep方法使用

    public Static void sleep(long millis)
    静态方法 进入阻塞状态,时间结束后进入可执行
    
    • 示例:
    Thread.sleep(3000);
    

    sleep方法让谁阻塞,取决于他在哪个线程中。

    2.join方法使用

    public final void join()
    被谁调用,让哪个线程先执行,执行完毕后,再执行所在线程
    
    • 示例
    
    public class JoinDemo1 {
    	public static void main(String[] args) {
    		Sum s = new Sum();
    		s.start();
    		// join:用谁调用,就让那个线程先执行,执行完毕后,再执行他所在线程(将他所在线程阻塞)。
    		try {
    			s.join();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    		System.out.println(Sum.sum);
    	}
    }
    
    class Sum extends Thread{
    	static int sum = 0;
    	public void run() {
    		for(int i=0;i<=1000;i++) {
    			sum += i;
    		}
    	}
    }
    

    3.yield方法

    public static void yield()
    让其他线程先执行,不一定生效,因为让谁执行是CPU决定的
    

    4.stop方法

    停止一个线程
    

    5.interrupt 方法

    打断线程的阻塞状态,进入可执行状态,会抛出异常
    
    • 示例:打断子线程阻塞
    public class interruptDemo {
    	public static void main(String[] args) {
    		Demo3 d = new Demo3();
    		d.start();
    		// 将d执行线程阻塞状态打断。
    		d.interrupt();
    		System.out.println("over");
    	}
    }
    
    
    class Demo3 extends Thread{
    	public void run() {
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		for (int i=0;i<10;i++) {
    			System.out.println(i);
    		}
    	}
    }
    
    • 打断主线程阻塞
    public class InterruptDemo2 {
    	public static void main(String[] args) {
    		// 主线程传给Demo4
    		Demo4 d = new Demo4(Thread.currentThread());
    		d.start();
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}	
    		System.out.println("over");
    	}
    }
    
    class Demo4 extends Thread{
    	Thread th;
    	public Demo4(Thread th) {
    		this.th = th;
    	}
    	// 用于打断主线程
    	public void run() {
    		th.interrupt();
    	}
    }
    
    • 练习:创建两个线程,一个线程负责打印大写字母表,一个线程负责打印小写字母表
    public class Threadlianxi1 {
    	public static void main(String[] args) {
    		Thread th1 = new Thread(new Task1());
    		Thread th2 = new Thread(new Task2());
    		th1.start();
    		th2.start();
    	}
    }
    
    
    class Task1 implements Runnable{
    	public void run() {
    		for(char i='a';i<='z';i++) {
    			System.out.println(i);
    		}
    	}
    }
    class Task2 implements Runnable{
    	public void run() {
    		for(char i='A';i<='Z';i++) {
    			System.out.println(i);
    		}
    	}
    }
    

    4.线程生命的周期

    主线程执行时候在栈空间,开辟空间给子线程,而他们的之间栈是独立的,但他们堆空间数据是共享的
    

    5.线程安全的问题

    • 买票示例:
    public class SellTicketsDemo {
    	public static void main(String[] args) {
    		Tickets t = new Tickets();
    		Thread th1 = new Thread(t);
    		Thread th2 = new Thread(t);
    		Thread th3 = new Thread(t);
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    
    class Tickets implements Runnable{
    	static int tickets = 100;
    	public void run() {
    		while (true) {
    			if (tickets> 0) {
    				try {
    					Thread.sleep(30);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    			} else {
    				break;
    			}
    		}
    	}
    }
    
    
    • 在执行代码时候,会发现多个线程会卖出同一张票。这样会产生线程安全问题。

    • 线程安全产生原因:1.具备多线程。2.操作共享数据。3.操作共享数据的代码有多条。

    • 通过加锁:让每一时刻只能有一个线程操作数据。方式有三种

    1.同步代码块

    synchronized(锁对象){
    	容易产生线程安全问题的代码
    }
    
    // 锁对象:可以是任意对象,但是必须保证多个线程使用是同一个对象。
    
    • 示例:
      • 关键点:锁对象选择
    public class SellTicketsDemo {
    	public static void main(String[] args) {
    		Tickets t = new Tickets();
    		Thread th1 = new Thread(t);
    		Thread th2 = new Thread(t);
    		Thread th3 = new Thread(t);
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    
    class Tickets implements Runnable{
    	static int tickets = 100;
        // 如果o方法run方法里,则无法实现线程安全,原因是执行run方法,实现3个o对象,对于这三个线程来说o对象不是共有的同一个对象。
    	Object o = new Object();
    	public void run() {
    		while (true) {
    			synchronized (o) {// o所在的类被new几次
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    			}
    		}
    	}
    }
    
    • 锁对象选取错误示例:
      • Tickets2被new了3次导致,没有锁住。
    public class SellTicketsDemo2 {
    	public static void main(String[] args) {
    		Tickets2 th1 = new Tickets2();
    		Tickets2 th2 = new Tickets2();
    		Tickets2 th3 = new Tickets2();
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    class Tickets2 extends Thread{
    	static int tickets = 100;
    	Object o = new Object();
    	public void run() {
    		while (true) {
    			synchronized (o) {
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    			}
    		}
    	}
    }
    
    • 方式1:解决方式通过:构造方法解决:
    public class SellTicketsDemo2 {
    	public static void main(String[] args) {
    		Object o = new Object();
    		Tickets2 th1 = new Tickets2(o);
    		Tickets2 th2 = new Tickets2(o);
    		Tickets2 th3 = new Tickets2(o);
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    class Tickets2 extends Thread{
    	static int tickets = 100;
    	Object o;
        // 构造方法
    	public Tickets2(Object o) {
    		this.o=o;
    	}
    
    	public void run() {
    		while (true) {
    			synchronized (o) {
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    			}
    		}
    	}
    }
    
    • 方式2:使用静态方式:
    Static Object o = new Object();
    
    • 方式3:使用字符串的常量
    synchronized ("abc")
    
    • 方式4:使用Class对象
    synchronized (SellTicketsDemo2.class)
    // 在jvm中,每一个类的Class总共只有一个
    

    2.同步方法

    • 把synchronized放到方法的修饰符中,锁的是整个方法。
    public class SellTicketsDemo3 {
    	public static void main(String[] args) {
    		Tickets3 th1 = new Tickets3();
    		new Thread(th1).start();
    		new Thread(th1).start();
    		new Thread(th1).start();
    	}
    }
    
    class Tickets3 implements Runnable{
    	static int tickets = 100;
    	// 默认锁对象:this
    	public synchronized void run() {
    		while (true) {
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    		}
    	}
    }
    

    上述代码虽然解决了线程安全问题,但是编程了单线程程序,原因synchronized锁的范围太大,第一个进来线程,执行完整个while循环。导致在使用同步方法时候也需要注意这个问题。

    • 但可以通过同步方法解决懒汉式单例模式:
    package com.xjk;
    // 懒汉式:存在问题,存在线程安全问题
    public class Singleton2 {
    	private static Singleton2 s;
    	// 构造方法私有化,为了不让别人随便new
    	private Singleton2() {
    	}
    	// 通过synchronized 解决线程安全问题
    	public synchronized static Singleton2 getInstance() {
    		if (s == null) {
    			s = new Singleton2();
    		} 
    		return s;
    	}
    }
    // 当启100个线程,当一个线程执行到s=new Singleton2();此时刚要new Singleton2,cpu切到第二个线程,因为s此时还是等于null,第二个线程也执行new Singleton2() 导出单例模式创建多个对象。此时通过同步方法添加synchronized可以有效解决此问题。
    
    • 同样StringBuffer是线程安全的内部有synchronized,效率相对StringBuilder低,StringBuilder是线程不安全的。

    3.Lock

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SellTicketsDemo4 {
    	public static void main(String[] args) {
    		Tickets4 th1 = new Tickets4();
    		new Thread(th1).start();
    		new Thread(th1).start();
    		new Thread(th1).start();
    	}
    }
    
    class Tickets4 implements Runnable{
    	static int tickets = 100;
    	// 也要保证该锁对象对于多个线程是同一个
    	Lock lock = new ReentrantLock();
    	public synchronized void run() {
    		while (true) {
    				lock.lock();// 加锁
    				try {
    					if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    				} finally {
    					lock.unlock();//解锁
    				}
    		}
    	}
    }
    
    • 释放锁的代码放到finally代码块中,否则容易造成程序阻塞。

    6.死锁

    • 是指两个或两个以上的线程在执行的过程中,因争夺资源产生一种互相等待现象。

    7.线程池用法

    • 线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

    • 可以根据系统的承受能力,调整线程池中工作线程数目,放置因为消耗过多的内存,而把服务器累死(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

    • 线程池的创建:

      public static ExecutorService newCachedThreadPool()
      	创建一个具有缓存功能的线程池
      public static ExecutorService new FixedThreadPool(int nThreads)
      	创建一个可重用的,具有固定线程数的线程池
      public static ExecutorService newSingleThreadExecutor()
      	创建一个只有单线程的线程池,相当于上个方法的参数是1
      
    • 示例:

      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class ThreadPoolDemo {
      	public static void main(String[] args) {
              // 1
      		ExecutorService pool = Executors.newCachedThreadPool();
      		pool.execute(new Runnable() {
      			public void run() {
      				System.out.println("hello world");
      			}
      		});
      		// 关闭线程池
      		pool.shutdown();
              
              // 2
              pool = Executors.newFixedThreadPool(20);
              
      	}
      }
      // 缓存线程池,如果没有任务会等待60s就关闭
      
  • 相关阅读:
    java之day4补充
    java之day4
    JAVA之day3对象
    JAVA之DAY3
    JAVA之DAY2
    element-ui表格添加复选框及根据列表中的数据判断是否可选
    h5手机端上传多张图片(界面上的展示图片,删除图片)
    模态框-开启关闭事件
    Vue-粒子特效(vue-particles)
    网页常用代码片段-sessionStorage存储JSON
  • 原文地址:https://www.cnblogs.com/xujunkai/p/13875512.html
Copyright © 2011-2022 走看看