多线程java (附代码)

多线程

在这里插入图片描述
在这里插入图片描述

单核CPU,以单个线程完成多个任务会比多个线程完成的要快,因为单核CPU要是有多个线程的话CPU要切换

在这里插入图片描述

何时需要多线程

  • 程序需要同时执行两个或多个任务
  • 程序需要实现一些需要等待的人物,如用户输入,文件读写操作、搜索等
  • 需要一些后台运行的程序

多线程的创建

1.创建一个继承于Thread类的子类
2.重写Thread的run方法,将此线程执行的操作声明在run方法中
3.创建Thread类的子类的对象
4.通过此对象调用start

多线程是存在交互性的

package ThreadT;
//1. 创建Thread的子类
class MyThread extends Thread{
	// 2.重写run
	public void run(){
		for(int i = 0;i < 100; i++){
			if(i % 2 == 0){
				System.out.println(i);
			}
		}
	}
}
public class ThreadTest {
	public static void main(String[] args) {
		// 3. 创建Thread子类的对象
		MyThread t1 = new MyThread();
	
		// 4.通过此对象调用start
		t1.start();
		for(int i = 0;i < 100; i++){
			if(i % 2 == 0){
				System.out.println(i + "+++++++++++++++");
			}
		}
	
	}
}

i 和 i********** 会交互

start两个作用,1.启用当前线程。2.使用当前线程的run

不能让已经start的线程去start

线程的常用方法

1.start() 启动当前线程,调用当前的run
2.run() 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3. currentThread 静态方法,返回执行当前代码的线程
4. getName:获取当前线程的名字
5. setName: 设置当前线程的名字
6. yield : 释放当前cpu的执行权(当前下一刻也能抢过来)
7. join:在线程A中调用线程B的join方法,线程A进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞
8. stop 强行结束线程周期,不建议使用
9. sleep(long millitime) 休息一会 ,单位是毫秒,在指定的时间之内,当前线程是堵塞状态,都会报异常 try - catch 一下 倒计时啥的用一下
10.isAlive() : 判断当前线程是否还存活

package ThreadT;

class HelloThread extends Thread{
	public void run(){
		for(int i = 0;i < 100;i++){
			if(i % 2 == 0){
			
//				try {
//					sleep(1000);
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		
			if(i % 20 == 0){
				this.yield(); // 释放当前CPU执行权
			}
		}	
	}

	public HelloThread(String name){
		super(name);  // 也能这样给线程取名字
	}
}
public class ThreadMethodTest {
	public static void main(String[] args) {
		HelloThread h1 = new HelloThread("线程一");
		// h1.setName("线程一");
		h1.start();
	
		// 给主线程做个命名
		Thread.currentThread().setName("主线程");
		for(int i = 0;i < 100;i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		
			if (i == 20){
				try {
					h1.join(); // h1过来执行完
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println(h1.isAlive());
	}
}

线程的优先值设置

高优先级的线程抢占CPU

线程的优先级等级

  • MAX_PRIORITY : 10
  • MIN_PRIORITY : 1
  • NORM_PRIORITY : 5 默认的优先级

getPriotity() 可以返回线程优先级
setPriority(int newPriority) 改变线程的优先级

说明:

线程的创建时回继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

创建多线程有两种方式

// 1.创建一个实现了Runnable接口的类
// 2.实现类去实现Runnable的抽象方法 run()
// 3.创建实现类的对象
// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
// 5.通过Thread类的对象调用start()

package ThreadT;
// 创建线程的方式2 实现Runnable 接口

//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
	// 2.实现类去实现Runnable的抽象方法 run()
	@Override
	public void run() {
		for (int i = 0;i < 100;i++){
			if (i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}


}
public class ThreadTest1 {
	public static void main(String[] args) {
		// 3.创建实现类的对象
		MThread mThread = new MThread();
		// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
		Thread t1 = new Thread(mThread);
		t1.start();
	
	
		// 再启动一个线程,遍历100以内的偶数,可以不用给钱new MThread
		Thread t2 = new Thread(mThread);
		t2.start(); 
	}

}

线程的生命周期

在这里插入图片描述
在这里插入图片描述

线程的安全问题

在这里插入图片描述

问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也操作共享数据
解决:当一个线程A在操作共享数据时,其他线程都不能参与进来,直到线程A完成操作,其他线程才可以开始操作,这样即使线程A出现了阻塞,也不能被改变

Java中通过同步机制,来解决线程的安全问题

方式一 : 同步代码块

synchranized(同步监视器){
	// 需要被同步的代码
}

说明:

  • 操作共享数据的代码,即位需要被同步的代码 --> 不能包含代码多了,也不能包含代码少了。
  • 共享数据:多个线程共同操作的变量
  • 同步监视器:俗称,锁。任何一个类的对象,都可以来充当锁。要求多个线程必须要用同一把锁。在创建类的时候就 Object obj = new Object(); 就造一个对象然后用Thread 去弄就不用在Object前加static 如果要造多个类的对象,就得 static Object。(接口的话一般就造一个继承Thread的对象)

类也是个对象,且类只会加载一次

同步的方式:安全了,但是变慢了。操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

在继承Thread类创建多线程的方式中,慎用this来充当同步监视器,建议使用当前类来充当同步监视器

方式二:同步方法
如果操作共享数据的代码,正好完整的声明在一个方法中,我们不妨将此方法声明同步的。

public synchronized void ...()  // 同步监视器:this

用接口创建这样是没问题,但是如果继承的话,

public static synchronized void show() // 同步监视器:当前类

1.关于同步方法的总结:同步方法仍然需要涉及到同步监视器,只是不需要我们显式的声明
2.非静态的同步方法,同步监视器是:this
静态的同步方法:同步监视器是当前类本身

使用同步机制将单例模式中的懒汉式改为线程安全的(之前提过)

package ThreadT;

// 使用同步机制将单例模式中的懒汉式改为线程安全的
public class BankTest {

}

class Bank{
	private Bank(){}

	private static Bank instance = null;
	// 若有多个线程可能会一起进去,有线程安全问题
	// 方式一:效率稍差
	public static Bank getInstance(){
//		synchronized (Bank.class){
//		if (instance == null){
//			instance = new Bank();
//			
//		}
//		return instance;
//		}
//	}
	// 方式二:效率高,后面的线程没必要进行同步代码块了
		if(instance == null){
			synchronized (Bank.class){
				if (instance == null){
					instance = new Bank();
		
				}
			}
		}
		return instance;
	}
}

死锁

不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己所需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处理阻塞状态,无法继续

解决方法

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步

我们使用同步时,要避免死锁

package ThreadT;

public class ThreadsisuoTest {
	public static void main(String[] args) {
		StringBuffer s1 = new StringBuffer();
		StringBuffer s2 = new StringBuffer();
	
		new Thread(){
			public void run(){
				synchronized(s1){
					s1.append("a");
					s2.append("1");
				
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized(s2){
						s1.append("b");
						s2.append("2");
					
						System.out.println(s1);
						System.out.println(s2);
					}
				}
			}
		}.start();
	
		new Thread(new Runnable(){
			public void run(){
				synchronized(s2){
					s1.append("c");
					s2.append("3");
				
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					synchronized(s1){
						s1.append("d");
						s2.append("4");
					
						System.out.println(s1);
						System.out.println(s2);
					}
				}	
			}
		}).start();
	}
}

第一个线程进入的时候拿着s1的钥匙,sleep了一下,第二个线程就有更大的概率会启动(第一个线程不sleep第二个线程也可能会启动,就是让更大的可能让两个线程都停留在拿了第一个锁的位置),这时候第一个线程手上的钥匙是s1 ,第二个线程手上的钥匙是s2,但第一个线程想要出去又需要钥匙s2,第二个线程也需要钥匙s1,他两就会僵持不动

Lock(锁)

在这里插入图片描述

Lock 和 synchronized 的异同

  • 相同点:都是用来解除线程安全问题

  • 不同点:synchronized 机制在执行完相应的同步代码后,自动的释放同步监视器
    Lock 需要手动的去启动同步(Lock()),同时结束同步也需要手动的实现(unlock())

    package ThreadT;

    import java.util.concurrent.locks.ReentrantLock;

    // Lock锁 – JKD5.0新增
    public class LockTest {
    public static void main(String[] args) {
    Windows w = new Windows();

      	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();
      }
    

    }

    class Windows implements Runnable{
    private int ticket = 100;
    // 1.实例化一个Lock
    private ReentrantLock lock = new ReentrantLock();

      @Override
      public void run() {
      	while(true){
      		try{
      			// 2.调用lock
      			lock.lock();
      	
      		if(ticket > 0){
      			System.out.println(Thread.currentThread().getName() + ":, 售票,票号为 " + ticket);
      			ticket --;
      		}else{
      			break;
      		}
      		}finally{
      			//3.调用解锁方法
      			lock.unlock();
      		}
      	
      		}
      	
      	}
      
      }
    

建议解决线程安全问题时,最好用Lock 再同步代码块,其实都差不多

sleep 和 wait 方法的异同

相同点:一旦执行到sleep和wait,都可以使得当前的进程进入阻塞状态
不同点:1.两个方法声明的位置不同,Thread 类中声明sleep,Object类中声明wait
2.调用的要求不同:sleep()可以在任何需要的常见下调用,wait必须在使用同步代码块或同步方法中调用
3.关于是否释放同步监视器:如果两个方法都使用在同步方法块或者同步方法中,sleep方法不会释放锁,wait()会释放同步监视器(锁)

JDK5.0新增的创建线程的方式

新增方式一:实现Callable接口

与使用Runnable相比,Callable功能更强大

  • 相比run方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

在这里插入图片描述

package ThreadT;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

// 实现Callable接口 --- JDK 5.0新增

// 1.创建一个实现Callable接口的类
class NumThread implements Callable{

	@Override
	// 2.实现call方法,将此线程需要实现的操作声明在此方法中,并且可以有返回值
	public Object call() throws Exception {
		int sum = 0;
		for (int i = 1;i <= 100;i++){
			if(i % 2 == 0){
				System.out.println(i);
				sum += i;
			}
		}
		return sum;
	}

}
public class ThreadNew {
	public static void main(String[] args) {
		// 3.创建Callable接口实现类的对象
		NumThread numThread = new NumThread();
		// 4.将Callable实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
		FutureTask futureTask = new FutureTask(numThread);
		// 5.将FutureTask的对象作为参数传递到Thread的构造器中,创建Thread对象,最后start
		new Thread(futureTask).start();
		try {
			// 6.需要的话得到返回值
			// get方法返回值即为futureTask构造器参数Callable重写的call方法的返回值
			Object sum = futureTask.get();
			System.out.println("总和为" + sum);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
}

如何理解Callable 比 Runnable 好

  • call() 有返回值
  • call() 可以抛出异常,被外面的操作捕获,获得异常的信息
  • Callable 支持泛型

线程池

在这里插入图片描述
在这里插入图片描述

 package ThreadT;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class xianchengchi {
	public static void main(String[] args) {
		// 1.提供指定线程数量的线程池
		ExecutorService service = Executors.newFixedThreadPool(10);
		// 2.执行指定的线程操作,需要提供Runnable
		service.execute(new NumberThread()); //适合Runnable
		service.execute(new NumberThread1()); //适合Runnable
	
		// 3.关闭线程池
		service.shutdown();
		// service.submit(task); //适合Callable
	
	}

}


class NumberThread implements Runnable{
	public void run(){
		for (int i = 0;i < 100;i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}

class NumberThread1 implements Runnable{
	public void run(){
		for (int i = 0;i < 100;i++){
			if(i % 2 != 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}

猜你喜欢

转载自blog.csdn.net/abc1234564546/article/details/127798396