Live2d Test Env

多线程深入理解

一:线程概述

        1,线程和进程

                  进程:正在运行的程序,是系统进行资源分配和调用的独立单位。 每一个进程都有它自己的内存空间和系统资源。

                  线程:进程的执行单元,执行路径。

        2,多线程和多进程的意义

                 多进程:提高CPU的使用率
                 多线程: 提高应用程序的使用率

         3,线程的生命周期

 

 

二:实现多线程

       实现方式一;编写一个类继承Thread类,,重写run()方法

public class MyThread extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 200; x++) {
			System.out.println(x);
		}
	}
}
public class MyThreadDemo {
	public static void main(String[] args) {
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();
		my1.start();
		my2.start();
	}

  实现方式二:编写一个类实现Runnable接口;重写run()方法;创建MyRunnable对象;创建Thread类对象,将MyRunnable对象作为形参进行传递

public class RunnableDemo {
	public static void main(String[] args) {
		MyRunnable mr1 = new MyRunnable();
		Thread t1 = new Thread (mr1);
		Thread t2 = new Thread (mr1);
		Thread t1 = new Thread (mr1,"线程一");
		Thread t2 = new Thread (mr1,"线程二");
		t1.start();
		t2.start();
	}
 
}
class MyRunnable implements Runnable{
	public void run(){
		System.out.println(Thread.currentThread().getName()+":"+"yangjinbiao");
	}
}

         实现方式三: 实现Callable 接口和使用FutureTask接收返回值

          Callable方式依赖线程池,线程的的submit方法可以接收一个Callable接口的实现对象,同时放回一个FutureTask,如果Callable的call方法出错,将会抛出一个异常。

首先查看API

Interface Callable<V>

发现这个接口有个泛型,在查看接口内的方法:

V call() 计算一个结果,如果不能这样做,就会抛出一个异常。 

发现接口方法的返回值有个泛型值,可以得出该接口的泛型是call()的放回值。在通过API查看FutureTask类。

Class FutureTask<V>

这个类泛型也是接收Callable接口的call()方法的放回值,通过查看FutureTask类的方法。

V get() 等待计算完成,然后检索其结果。  
protected void set(V v) 将此未来的结果设置为给定值,除非此未来已被设置或已被取消。 
protected void setException(Throwable t) 
导致这个未来报告一个ExecutionException与给定的可抛弃的原因,除非这个未来已经被设置或被取消。 

第一个方法表示获取call()方法的放回值存贮在FutureTask的值,下面是实现代码。

public class CallableDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建线程池对象
		ExecutorService pool = Executors.newFixedThreadPool(2);

		// 可以执行Runnable对象或者Callable对象代表的线程
		Future<Integer> f1 = pool.submit(new MyCallable(100));
		Future<Integer> f2 = pool.submit(new MyCallable(200));

		// V get()
		Integer i1 = f1.get();
		Integer i2 = f2.get();

		System.out.println(i1);
		System.out.println(i2);

		// 结束
		pool.shutdown();
	}
}
public class MyCallable implements Callable<Integer> {

	private int number;

	public MyCallable(int number) {
		this.number = number;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int x = 1; x <= number; x++) {
			sum += x;
		}
		return sum;
	}

}

三:与线程相关的注意事项

        1,run和start的区别:

                run():仅仅是封装被线程执行的代码,直接调用是普通方法

                start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

        2,为何需要重写run方法

                 不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread                 类中的run()用来包含那些被线程执行的代码。

       3,多次启动线程会怎么样?

                出现异常:在第二次调用start()方法的时候,线程可能处于终止或者其它(非NEW)状态,但是不论如何,都是不可以再次启动的。

四:与线程相关的方法

        1,线程名的获取和设置

           设置名称: public final String setName()或者构造方法来设置线程的名称。

          获取名称: public final String getName():获取线程的名称。public static Thread currentThread():返回当前正在执行的线程对象

         2,线程优先级:线程优先级默认为5,范围为1至10;

              通过: public final int getPriority():获取优先级

               通过:public final void setPriority(int newPriority):设置优先级

五:线程控制

        1,线程休眠:  public static void sleep(long millis)

public class ThreadSleepDemo {
	public static void main(String[] args) {
		ThreadSleep ts1 = new ThreadSleep();
		ThreadSleep ts2 = new ThreadSleep();
		ThreadSleep ts3 = new ThreadSleep();
		ts1.setName("林青霞");
		ts2.setName("林志玲");
		ts3.setName("林志颖");
		ts1.start();
		ts2.start();
		ts3.start();
	}
}
public class ThreadSleep extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x + ",日期:" + new Date());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

       2,加入线程:public final void join():等待该线程终止。

public class ThreadJoinDemo {
	public static void main(String[] args) {
		ThreadJoin tj1 = new ThreadJoin();
		ThreadJoin tj2 = new ThreadJoin();
		ThreadJoin tj3 = new ThreadJoin();
		tj1.setName("李渊");
		tj2.setName("李世民");
		tj3.setName("李元霸");
		tj1.start();
		try {
			tj1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		tj2.start();
		tj3.start();
	}}
public class ThreadJoin extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
		}}}

3,礼让线程:public static void yield():暂停当前正在执行的线程对象,并执行其他线程。  让多个线程的执行更和谐,但是不能靠它保证一人一次。

public class ThreadYieldDemo {
	public static void main(String[] args) {
		ThreadYield ty1 = new ThreadYield();
		ThreadYield ty2 = new ThreadYield();
		ty1.setName("林青霞");
		ty2.setName("刘意");
		ty1.start();
		ty2.start();
	}}
public class ThreadYield extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
			Thread.yield();
		}}}

4, 守护线程: public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 
         

public class ThreadDaemonDemo {
	public static void main(String[] args) {
		ThreadDaemon td1 = new ThreadDaemon();
		ThreadDaemon td2 = new ThreadDaemon();
		td1.setName("关羽");
		td2.setName("张飞");
		// 设置收获线程
		td1.setDaemon(true);
		td2.setDaemon(true);
		td1.start();
		td2.start();
		Thread.currentThread().setName("刘备");
		for (int x = 0; x < 5; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}}}
public class ThreadDaemon extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
		}}}

5,线程中断: 
          public final void stop():让线程停止,过时了,但是还可以使用。
          public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。

public class ThreadStopDemo {
	public static void main(String[] args) {
		ThreadStop ts = new ThreadStop();
		ts.start();	
		try {
			Thread.sleep(3000);
			// ts.stop();
			ts.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}}
public class ThreadStop extends Thread {
	@Override
	public void run() {
		System.out.println("开始执行:" + new Date());
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// e.printStackTrace();
			System.out.println("线程被终止了");
		}
		System.out.println("结束执行:" + new Date());
	}
}

六:线程组

线程组是用于对功能相同的进行管理,可以对线程组内的全部线程进行统一管理。实现方式如下所示,只要是通过线程的构造方法实现的

查看API

java.lang.ThreadGroup 

构造方法

ThreadGroup(String name) 构造一个新的线程组。  
ThreadGroup(ThreadGroup parent, String name) 创建一个新的线程组。 

主要方法:

void destroy() 销毁此线程组及其所有子组。 
int getMaxPriority() 返回此线程组的最大优先级。  
String getName() 返回此线程组的名称。  
void setMaxPriority(int pri) 设置组的最大优先级。  

代码代码如下:

public class ThreadGroupDemo {
	public static void main(String[] args) {
		method2();
	}
	private static void method2() {
		// ThreadGroup(String name)
		ThreadGroup tg = new ThreadGroup("这是一个新的组");
		MyRunnable my = new MyRunnable();
		// Thread(ThreadGroup group, Runnable target, String name)
		Thread t1 = new Thread(tg, my, "林青霞");
		Thread t2 = new Thread(tg, my, "刘意");
		System.out.println(t1.getThreadGroup().getName());
		System.out.println(t2.getThreadGroup().getName());
		//通过组名称设置后台线程,表示该组的线程都是后台线程
		tg.setDaemon(true);
	}
 
}

七:线程池

    1,线程池概述

            A,为什么使用线程池:因为每次启动一个新的线程,系统都会给它开辟一个新的资源空间,会耗费资源,特别是在需要开辟大量的线程的时候,更是如此,所以使用线程池。

            B:线程池:系统会开辟一个空间,专门用于存在被执行之后的线程,等到下次需要调用的时候,无需重新创建线程就可以从线程池内建对象拿出来使用。

 2,实现

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

public static ExecutorService newCachedThreadPool() :创建具有缓存功能的线程

public static ExecutorService newFixedThreadPool(int nThreads):创建存贮多个线程的线程池

public static ExecutorService newSingleThreadExecutor():创建存贮单个线程的线程池

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法

Future<?> submit(Runnable task) <T>

Future<T> submit(Callable<T> task)

实现代码:

public class ExecutorsDemo {
	public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(2);
 
		// 可以执行Runnable对象或者Callable对象代表的线程
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		//结束线程池
		pool.shutdown();
	}
}
 
public class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}
 
}

3,实现原理(来源:疯狂哈丘 

就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。
 

八,线程安全

       1,为什么会导致线程安全问题

             多线程环境,共享数据,对共享数据进行操作。

       2,解决线程安全问题

           A:  同步代码块

                      synchronized(对象){需要同步的代码;}

           注意:可以是任意锁,但是必须使用的是同一个对象。同步方法锁对象为;this;静态同步方法锁对象:类的字节码文件对象。

         B:Lock锁(接口):Lock void lock():加锁; void unlock() :释放锁;ReentrantLock(子实现类);

              如果被加锁的代码块出现异常,就不会在执行 unlock(),就无法释放锁,这时候需要将释放锁的操作早finally中,保证出问题还能将锁释放。

public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();
		// 创建三个窗口
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");
		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}}
public class SellTicket implements Runnable {
	// 定义票
	private int tickets = 100;
	// 定义锁对象
	private Lock lock = new ReentrantLock();
	public void run() {
		while (true) {
			try {
				// 加锁
				lock.lock();
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
					+ "正在出售第" + (tickets--) + "张票");
				}
			} finally {
				// 释放锁
				lock.unlock();
			}}}}

          3,死锁问题: 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象,例如同步代码块的嵌套

public class DieLockDemo {
	public static void main(String[] args) {
		DieLock dl1 = new DieLock(true);
		DieLock dl2 = new DieLock(false);

		dl1.start();
		dl2.start();
	}
}
public class DieLock extends Thread {

	private boolean flag;

	public DieLock(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		if (flag) {
			synchronized (MyLock.objA) {
				System.out.println("if objA");
				synchronized (MyLock.objB) {
					System.out.println("if objB");
				}
			}
		} else {
			synchronized (MyLock.objB) {
				System.out.println("else objB");
				synchronized (MyLock.objA) {
					System.out.println("else objA");
				}
			}
		}
	}
}
public class MyLock {
	// 创建两把锁对象
	public static final Object objA = new Object();
	public static final Object objB = new Object();
}

这时候,会出现相互等待的问题。

        解决方案:生产消费者模式之等待唤醒机制

生产方有资源,就通知并等待消费者来消费;消费者需要消费,就通知生产者生产。

 共享的资源:

共同资源
public class Student {
	private String name;
	private int age;
	private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
 
	public synchronized void set(String name, int age) {
		// 如果有数据,就等待
		if (this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
 
		// 设置数据
		this.name = name;
		this.age = age;
 
		// 修改标记
		this.flag = true;
		this.notify();
	}
 
	public synchronized void get() {
		// 如果没有数据,就等待
		if (!this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
 
		// 获取数据
		System.out.println(this.name + "---" + this.age);
 
		// 修改标记
		this.flag = false;
		this.notify();
	}
}

生产者:

public class SetThread implements Runnable {
 
	private Student s;
	private int x = 0;
 
	public SetThread(Student s) {
		this.s = s;
	}
	public void run() {
		while (true) {
			if (x % 2 == 0) {
				s.set("林青霞", 27);
			} else {
				s.set("刘意", 30);
			}
			x++;
		}}}

消费者:

 
public class GetThread implements Runnable {
	private Student s;
 
	public GetThread(Student s) {
		this.s = s;
	}
 
	@Override
	public void run() {
		while (true) {
			s.get();
		}
	}

测试:

 
public class StudentDemo {
	public static void main(String[] args) {
		//创建资源
		Student s = new Student();
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);
		//线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);
		//启动线程
		t1.start();
		t2.start();
	}
}

 

 

 

 

posted @ 2020-06-04 16:33  waywardcode  阅读(52)  评论(0)    收藏  举报