java 多线程介绍

多线程
  • 什么是线程
    • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作(cpu不断切换任务)当然多核cpu是不一样的
多线程并行和并发的区别
  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
    比如:
    JVM的启动是多线程的吗
    JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
多线程程序实现的两种方式
  • 继承Thread
    • 定义类继承Thread
    • 重写run方法
    • 把新线程要做的事写在run方法中
    • 创建线程对象
    • 开启新线程, 内部会自动执行run方法
			public class Demo2_Thread {
				public static void main(String[] args) {
					MyThread mt = new MyThread();							
					mt.start();												
					for(int i = 0; i < 1000; i++) {
						System.out.println("b");
					}
				}
			}
			class MyThread extends Thread {									
				public void run() {											
					for(int i = 0; i < 1000; i++) {							
						System.out.println("a");
					}
				}
			}

  • 实现Runnable(方法二)
    • 定义类实现Runnable接口
    • 实现run方法
    • 把新线程要做的事写在run方法中
    • 创建自定义的Runnable的子类对象
    • 创建Thread对象, 传入Runnable
    • 调用start()开启新线程, 内部会自动调用Runnable的run()方法
			public class Demo3_Runnable {
				public static void main(String[] args) {
					MyRunnable mr = new MyRunnable();						
					Thread t = new Thread(mr);								
					t.start();												
					for(int i = 0; i < 1000; i++) {
						System.out.println("b");
					}
				}
			}
			class MyRunnable implements Runnable {							
				@Override
				public void run() {											
					for(int i = 0; i < 1000; i++) {							
						System.out.println("a");
					}
				}	
			}

Runnable 底层也是依赖 Thread的

两种方式的区别
  • 继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法

  • 实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法

  • 继承Thread

    • 好处是:可以直接使用Thread类中的方法,代码简单
    • 弊端是:如果已经有了父类,就不能用这种方法
  • 实现Runnable接口

    • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    • 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
匿名内部类实现线程的两种方式

继承Thread类

		new Thread() {													
			public void run() {											
				for(int i = 0; i < 1000; i++) {							
					System.out.println("a");
				}
			}
		}.start();

实现Runnable接口

		new Thread(new Runnable(){										
			public void run() {											
				for(int i = 0; i < 1000; i++) {							
					System.out.println("b");
				}
			}
		}).start(); 
获取名字和设置名字
  • 获取名字
    • 通过getName()方法获取线程对象的名字
  • 设置名字
    • 通过构造函数可以传入String类型的名字
		new Thread("xxx") {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "***");
					}
				}
			}.start();
			
			new Thread("yyy") {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "++++");
					}
				}
			}.start(); 

通过setName(String)方法可以设置线程对象的名字

	
			Thread t1 = new Thread() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "*****");
					}
				}
			};
			
			Thread t2 = new Thread() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(this.getName() + "+++++");
					}
				}
			};
			t1.setName("xixi");
			t2.setName("哈哈");
			
			t1.start();
			t2.start();

获取当前线程的对象

Thread.currentThread(), 主线程也可以获取

	new Thread(new Runnable() {
			public void run() {
				for(int i = 0; i < 1000; i++) {
					System.out.println(Thread.currentThread().getName() + "*****");
				}
			}
		}).start();
			
			new Thread(new Runnable() {
				public void run() {
					for(int i = 0; i < 1000; i++) {
						System.out.println(Thread.currentThread().getName() + "++++");
					}
				}
			}).start();
			Thread.currentThread().setName("我是主线程");					
			System.out.println(Thread.currentThread().getName());	
休眠线程
  • Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
守护线程
  • setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
		Thread t1 = new Thread() {
				public void run() {
					for(int i = 0; i < 50; i++) {
						System.out.println(getName() + "****");
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			};
			
			Thread t2 = new Thread() {
				public void run() {
					for(int i = 0; i < 5; i++) {
						System.out.println(getName() + "++++");
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			};
			
			t1.setDaemon(true);						//将t1设置为守护线程
			
			t1.start();
			t2.start();
加入线程
  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续
			final Thread t1 = new Thread() {
				public void run() {
					for(int i = 0; i < 50; i++) {
						System.out.println(getName() + "*****");
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			};
			
			Thread t2 = new Thread() {
				public void run() {
					for(int i = 0; i < 50; i++) {
						if(i == 2) {
							try {					
								t1.join(30);					
								Thread.sleep(10);
							} catch (InterruptedException e) {
								
								e.printStackTrace();
							}
						}
						System.out.println(getName() + "+++++");
					}
				}
			};
			t1.start();
			t2.start();
礼让线程和设置线程的优先级
  • yield()让出cpu
  • setPriority()设置线程的优先级
同步代码块
  • 什么情况下需要同步
    • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
    • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
  • 同步代码块
    • 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
    • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
public static void main(String[] args) {
		final Printer p = new Printer();
		
		new Thread() {
			public void run() {
				while(true) {
					p.print1();
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					p.print2();
				}
			}
		}.start();
	}

}

	class Printer {
				Demo d = new Demo();
				public static void print1() {
					synchronized(d){				
		//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
						System.out.print("A");
						System.out.print("B");
						System.out.print("C");
						System.out.print("D");
						System.out.print("\r\n");
					}
				}
	
				public static void print2() {	
					synchronized(d){	
						System.out.print("1");
						System.out.print("2");
						System.out.print("3");
						System.out.print("4");
						System.out.print("\r\n");
					}
				}
			}
同步方法
  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
	class Printer {
			public static void print1() {
				synchronized(Printer.class){			
			//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
						System.out.print("A");
						System.out.print("B");
						System.out.print("C");
						System.out.print("D");
					System.out.print("\r\n");
				}
			}
			/*
			 * 非静态同步函数的锁是:this
			 * 静态的同步函数的锁是:字节码对象
			 */
			public static synchronized void print2() {	
					System.out.print("1");
					System.out.print("2");
					System.out.print("3");
					System.out.print("4");
				System.out.print("\r\n");
			}
		}

单例设计模式
  • 单例设计模式:保证类在内存中只有一个对象。
  • 如何保证类在内存中只有一个对象呢?
    • (1)控制类的创建,不让其他类来创建本类的对象。private
    • (2)在本类中定义一个本类的对象。Singleton s;
    • (3)提供公共的访问方式。 public static Singleton getInstance(){return s}
  • 单例写法两种:
    • (1)饿汉式 开发用这种方式。
	class Singleton {
				//1,私有构造函数
				private Singleton(){}
				//2,创建本类对象
				private static Singleton s = new Singleton();
				//3,对外提供公共的访问方法
				public static Singleton getInstance() {
					return s;
				}
				
				public static void print() {
					System.out.println("11111111111");
				}
			}
  • (2)懒汉式 面试写这种方式。多线程的问题?
		//懒汉式,单例的延迟加载模式
			class Singleton {
				//1,私有构造函数
				private Singleton(){}
				//2,声明一个本类的引用
				private static Singleton s;
				//3,对外提供公共的访问方法
				public static Singleton getInstance() {
					if(s == null)
						//线程1,线程2 线程不安全
						s = new Singleton();
					return s;
				}
			}

饿汉和懒汉的区别:
1.饿汉空间换时间,懒汉相反
2.懒汉线程不安全

  • (3)第三种格式
			class Singleton {
				private Singleton() {}
			
				public static final Singleton s = new Singleton();
				//final是最终的意思,被final修饰的变量不可以被更改
			}

Runtime类是一个单例类

扫描二维码关注公众号,回复: 4517050 查看本文章
Timer

它是java的定时器,可以定时执行一个任务,或者间隔一段时间重复执行一个任务
schedule()方法

			public class DTimer {
				public static void main(String[] args) throws InterruptedException {
					Timer t = new Timer();
					t.schedule(new MyTimerTask(), new Date(118,12,7,10,54,20),5000);
					//第一个参数 是一个继承TimerTask类的类 对象 继承要重写run()方法 ,也就是要多线程执行内容
					//第二个参数是Date对象
					//第三个参数是间隔时间
				}
			}
			class MyTimerTask extends TimerTask {
				public void run() {
					System.out.println("该敲代码了");
				}
				
			}
等待线程和唤醒线程

对象.wait()让一个线程等待
对象.notify()任意唤醒一个线程
对象.notify()唤醒所有等待线程

注意点:

  • 1,在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法
  • 2,为什么wait方法和notify方法定义在Object这类中?
    因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中
  • 3,sleep方法和wait方法的区别?
    a,sleep方法必须传入参数,参数就是时间,时间到了自动醒来
    wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
    b,sleep方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡
    wait方法在同步函数或者同步代码块中,释放锁
多个线程的执行问题(互斥锁运用)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Demo3_ReentrantLock {
	public static void main(String[] args) {
		final Printer3 p = new Printer3();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print1();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print2();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print3();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
	}

}

class Printer3 {
	private ReentrantLock r = new ReentrantLock();
	private Condition c1 = r.newCondition();
	private Condition c2 = r.newCondition();
	private Condition c3 = r.newCondition();
	
	private int flag = 1;
	public void print1() throws InterruptedException {							
		r.lock();								//获取锁
			if(flag != 1) {
				c1.await();
			}
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\r\n");
			flag = 2;
			//this.notify();						//随机唤醒单个等待的线程
			c2.signal();
		r.unlock();								//释放锁
	}
	
	public void print2() throws InterruptedException {
		r.lock();
			if(flag != 2) {
				c2.await();
			}
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
			flag = 3;
			//this.notify();
			c3.signal();
		r.unlock();
	}
	
	public void print3() throws InterruptedException {
		r.lock();
			if(flag != 3) {
				c3.await();
			}
			System.out.print("i");
			System.out.print("t");
			System.out.print("h");
			System.out.print("e");
			System.out.print("i");
			System.out.print("m");
			System.out.print("a");
			System.out.print("\r\n");
			flag = 1;
			c1.signal();
		r.unlock();
	}
}


java的多线程分组

Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制
而默认情况下 都是main组

	public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
 	public final String getName()//通过线程组对象获取他组的名字

如何设置线程组

我们也可以给线程设置分组
		1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
		2,创建线程对象
		3,Thread(ThreadGroup?group, Runnable?target, String?name) 
		4,设置整组的优先级或者守护线程
线程的五种状态

新建(初始),就绪(可运行),运行,阻塞,死亡(结束)
在这里插入图片描述

线程池 java jdk5开始
  • 线程池概述
    • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • 内置线程池的使用概述
    • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
      • public static ExecutorService newFixedThreadPool(int nThreads)
      • public static ExecutorService newSingleThreadExecutor()
      • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
      • Future<?> submit(Runnable task)
      • Future submit(Callable task)
	* 使用步骤:
		* 创建线程池对象
		* 创建Runnable实例
		* 提交Runnable实例
		* 关闭线程池

猜你喜欢

转载自blog.csdn.net/qq_40435621/article/details/84879891