多线程相关知识整理

进程

先来了解下什么是进程
进程:指正在运行的程序,例如:QQ,微信,浏览器等等
进程有前台进程,后台进程
在这里插入图片描述
在这里插入图片描述
一个进程可以有一条线程,也可以有多条线程

线程

线程:具有完成独立任务的一条执行路径

public class Thread extends Object implements Runnable

线程是程序中的执行线程。Java虚拟机允许应用程序并发的运行多个执行线程

每个线程都有一个优先级,高优先级线程的执行高于低优先级线程。每个线程都可以或者不可以标记一个守护程序,当某个线程中运行的代码创建了一个新的Thread对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护线程

当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
1、调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
2、非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常

创建新执行线程有两种方法:
一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。

class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }
 
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }
 

然后,下列代码会创建并启动一个线程:

PrimeThread p = new PrimeThread(143);
p.start();

另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。

 class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }
 
         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

然后,下列代码会创建并启动一个线程:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。

多线程

开启多线程的优点:
1、执行某些耗时任务
2、希望某些程序看起来像是同时执行
3、希望完成某个特点的子任务
4、防止线程阻塞
在这里插入图片描述
开启多线程是提高了效率吗?
答案是否定的,这反而会降低执行效率,但是提高了cpu的使用率,合理的利用了cpu的使用率

Java中开启了几个线程:
至少有两个:1、主线程 2、垃圾回收线程

多线程的概述:
具有完成特定功能的执行路径,是cpu最先执行单位
cpu在某个时间刻度上只能够执行一条线程的一条原子性语句

int a = 1;原子性语句
a ++;不是
a.将a的值读取出来
b.将a的值+1
c.重新将新值赋值给a

cpu的执行原理:
1、在真实环境下,CPU能够同时执行多个程序,本质只是在同一个时间刻度上执行一条线程的一条原子性语句,只不过CPU切换执行速度非常快,我们无法察觉以为是同时执行
2、并发和并行
并发:在同一个时间段同时执行
并行:在同一个时间刻度上同时执行
3、同步和异步
同步:并发情况下会出现同步问题
异步:能够同一个时间段能够处理多个任务,例如ajax请求

多线程和多进程的好处:
1、多线程提高了进程的使用率,从而提高CPU的使用率
2、多进程提高了CPU的使用率

线程开启的五种方式

方式一:

继承Thread类
1、自定义类MyThread继承Thread类
2、MyThread类里面重写run()方法
3、创建线程对象
4、启动线程
注意:a.启动线程使用的是start()方法而不是run()方法
b.线程不能多次启动

编写三个线程
1.拷贝文件 子线程 使用继承Thread的方式
2.输出1-100 子线程 使用实现 Runnable的方式
3.打印九九乘法表 主线程实现
代码实现如下

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class ThreadDemo {
	public static void main(String[] args) {
		// 创建线程对象。
		CopyFileThread cft = new CopyFileThread(new File("ThreadDemo.java"), new File("thread.txt"));
		// 启动线程。
		// cft.run();
		// cft.start();
		
		PrintNumberThread pnt = new PrintNumberThread();
		Thread t = new Thread(pnt);
		
		cft.start();
		t.start();
		
		//主线程执行
		for (int i = 0; i < 100; i++) {
			System.out.println("main:" + i);
		}
	}
}

class PrintNumberThread implements Runnable {

	// 重写run()方法
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println("实现Runnable方式开启子线程: " + i);
		}
	}
	
}

// 自定义类CopyFileThread 继承Thread类。
class CopyFileThread extends Thread {
	
	private File srcFile;
	private File descFile;
	
	public CopyFileThread() {
		super();
	}
	
	public CopyFileThread(File srcFile, File descFile) {
		super();
		this.srcFile = srcFile;
		this.descFile = descFile;
	}

	// 重写run方法
	@Override
	public void run() {
		// 需要写什么? --> 主方法写什么这里就写什么,这里就是和方法的写法很像,用来书写特定任务的代码
		/*
		 * 1.任务参数 (通过线程封装外界传入) File srcFile, File descFile
		 * 2.任务结果 无
		 * 3.具体的任务 拷贝文件
		 */
		copyFile(srcFile, descFile);
		//子线程执行
		for (int i = 0; i < 100; i++) {
			System.out.println("子线程: " + i);
		}
	}

	public void copyFile(File srcFile, File descFile) {
		try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
				BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(descFile))) {
			int len = 0;
			byte[] bys = new byte[1024];
			while ((len = bis.read(bys)) != -1) {
				bos.write(bys, 0, len);
				bos.flush();
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public File getSrcFile() {
		return srcFile;
	}

	public void setSrcFile(File srcFile) {
		this.srcFile = srcFile;
	}

	public File getDescFile() {
		return descFile;
	}

	public void setDescFile(File descFile) {
		this.descFile = descFile;
	}

}
方式二:

实现Runnable接口
1、自定义类MyRunnable实现Runnable接口
2、重写run()方法
3、创建MyRunnable类的对象
4、创建Thread类的对象,并把步骤3的对象作为构造参数传递
5、启动线程

实现接口方式的好处:
可以避免由于Java单继承带来的局限性。适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		
		// 创建MyRunnable类的对象
		MyRunnable mr = new MyRunnable();
		// 创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
		Thread t = new Thread(mr);
		// 启动线程
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println("主线程: " + i);
		}
	}
}

class MyRunnable implements Runnable {

	// 重写run()方法
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("子线程: " + i);
		}
	}
	
}
方式三:

实现Callable接口
FutureTask

public class FutureTask<V> implements RunnableFuture<V> { }
public interface RunnableFuture<V> extends Runnable, Future<V> { 
		void run();
 }

public interface Future<V> { 
	V get() throws InterruptedException, ExecutionException;
}

 @FunctionalInterface
	public interface Callable<V> {
	    V call() throws Exception;
}

可取消的异步计算。利用开始和取消计算的方法、查询计算是否完成的方法和获取计算结果的方法,此类提供了对 Future 的基本实现。仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。

可使用 FutureTask 包装 Callable 或 Runnable 对象。因为 FutureTask 实现了 Runnable,所以可将 FutureTask 提交给 Executor 执行。

除了作为一个独立的类外,此类还提供了 protected 功能,这在创建自定义任务类时可能很有用。

举例代码如下

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
public class ThreadDemo04 {
	public static void main(String[] args) {
	//创建FutureTask对象
		FutureTask<Integer> task = new FutureTask<>(new MyCallable(1,100));
		Thread t = new Thread(task);
		t.start();
		
		for (int i = 0; i < 1000; i++) {
			System.out.println("main: " + i);
		}
		
		try {
			Integer value = task.get();
			System.out.println(value);
		} catch (InterruptedException | ExecutionException e) {
			System.out.println("子线程抛出异常给主线程: " + e);
//			e.printStackTrace();
		}
		
		System.out.println("over");
	}
}

/*
 * 计算m~n的和
 */
class MyCallable implements Callable<Integer> {
	
	private Integer m;
	private Integer n;
	
	public MyCallable() {
		super();
	}

	public MyCallable(Integer m, Integer n) {
		super();
		this.m = m;
		this.n = n;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = m; i <= n; i++) {
			System.out.println("Callable: " + i);
			sum += i;
		}
		throw new NullPointerException();
//		return sum;
	}

	public Integer getM() {
		return m;
	}

	public void setM(Integer m) {
		this.m = m;
	}

	public Integer getN() {
		return n;
	}

	public void setN(Integer n) {
		this.n = n;
	}
	
}

实现Runnable和实现Callable接口的区别:
1、有返回值
2、可以声明异常
这里的返回值和异常抛出都是给到线程的去启动者

方式四:

使用匿名内部类
举例代码如下

public class ThreadDemo05 {
	public static void main(String[] args) {
		new Thread();
		
		new Thread().start();
		
		// 方式一继承Thread开启线程
		new Thread() {}.start();
		
		new Thread() {
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("方式一继承Thread开启线程:" + i);
				}
			}
		}.start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("方式二实现Runnable开启线程:" + i);
				}
			}
		}).start();
		
		// 如果一个线程既继承了Thread,同时实现了Runnable接口,那么继承Thread优先
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("方式二实现Runnable开启线程---:" + i);
				}
			}
		}) {
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("方式一继承Thread开启线程---:" + i);
				}
			}
		}.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println("main:" + i);
		}
	}
}

方式五:

使用Lamda表达式

Lambda表达式是JDK1.8之后引入
本质就是方便匿名内部类的书写

函数式接口
只有一个抽象方法的接口就是函数式接口
例如 Runnable

Lambda表达式的语法
主要由三部分组成:
1.形参列表: 形式参数允许省略参数类型
2.箭头 ->
3.方法体: 由大括号包裹,当方法体中只有一条语句,{}可以省略
当一个方法有返回值的时候,如果只是返回一条语句,那么return和{}都可以省略,这个表达式结果自动作为返回值的结果返回

举例代码如下

public class ThreadDemo06 {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				
			}
		}).start(); 
		
		new Thread( () -> System.out.println("HelloWorld") ).start();
		
		new Thread( () -> {
			for (int i = 0; i < 100; i++) {
				System.out.println("实现Runnable方式: " + i);
			}
		}).start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println("main: " + i);
		}
	}
	
	
}

@FunctionalInterface
interface Inter {
	void show(int a, int b);
}

Lambda表达式

语法格式:

1.形参列表: 形式参数允许省略参数类型
2.箭头 ->
3.方法体: 由大括号包裹,当方法体中只有一条语句,{}可以省略
当一个方法有返回值的时候,如果只是返回一条语句,那么return和{}都可以省略,这个表达式结果自动作为返回值的结果返回

举例代码如下

public class LambdaDemo {
	public static void main(String[] args) {
		new Test().dailup( () -> System.out.println("helloWorld") );
		
		new Test().play( (name) -> System.out.println(name) );
		
		new Test().calc( (a,b) ->  a + b );
	}
}

class Test{
	public void dailup(IDailup dailup) {
		dailup.dailup();
	}
	
	public void play(IPlay play) {
		play.play("Dvd播放器");
	}
	
	public void calc(ICalculate calculate) {
		int addResult = calculate.calc(20, 30);
		System.out.println(addResult);
	}
}

interface IDailup{
	void dailup();
}

interface IPlay{
	void play(String name);
}

interface ICalculate{
	int calc(int a, int b);
}

Lambda表达式在集合中的应用

public class LambdaDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		list.add("张三");
		list.add("张丰");
		list.add("张春晓");

		System.out.println("---------forEach---------");
		list.forEach( (t) -> System.out.println(t) );
		System.out.println("---------forEachRemaining---------");
		//通过迭代器
		Iterator<String> it = list.iterator();
		it.forEachRemaining((t) -> System.out.println(t));
		
	}
}

设置和获取线程的名称

通过构造方法

Thread(String name)  分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
通过线程的成员方法
public final String getName()
public final void setName(String name)
通过静态方法
public static Thread currentThread()
可以获取任意方法所在的线程名称
可以获取主线程的线程名称:Thread.currentThread().getName();
long getId()  返回该线程的标识符。

举例代码如下

public class ThreadDemo01 {
	public static void main(String[] args) {
		
		MyRunnable mr = new MyRunnable();
		Thread t1 = new Thread(mr, "老孙");
		Thread t2 = new Thread(mr, "老邓");
		Thread t3 = new Thread(mr, "老王");
		
		System.out.println(t1.getId());
		System.out.println(t2.getId());
		System.out.println(t3.getId());
		t1.start();
		t2.start();
		
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}

class MyThread extends Thread {
	
	public MyThread() {
		super();
	}
	
	public MyThread(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

Java对线程的调度

Java使用的是抢占式调度模型
抢占式调度模型
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。
设置和获取线程的优先级

public final int getPriority()
public final void setPriority(int newPriority)

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		PriorityThread pt = new PriorityThread();
		Thread t1 = new Thread(pt, "刘备");
		Thread t2 = new Thread(pt, "关羽");
		Thread t3 = new Thread(pt, "张飞");
		
	//优先级范围1-10	
		t1.setPriority(Thread.MAX_PRIORITY);//10
		t2.setPriority(Thread.NORM_PRIORITY);//5
		t3.setPriority(Thread.MIN_PRIORITY);//1
		
		t1.start();
		t2.start();
		t3.start();
		
	}
}

class PriorityThread implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}

设置线程休眠

构造方法

public static void sleep(long millis)

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		Thread t = new Thread(new SleepThread(), "时钟线程");
		t.start();
	}
}

// 利用线程休眠模拟时钟
class SleepThread implements Runnable {

	@Override
	public void run() {
		while (true) {
			String dateStr = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
			System.out.println(Thread.currentThread().getName() + "的当前时间为: " + dateStr);
			
			// 休眠1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}

中断线程

构造方法

public final void stop()
public void interrupt()

stop和interrupt的区别
stop:结束线程的生命
interrupt: 会给线程抛出一个中断异常,对应的线程可以处理或者结束

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		Thread t = new Thread(new StopThread(), "马云");
		t.start();
		
		try {
			Thread.sleep(3000);
			t.interrupt();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

class StopThread implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "睡眠起始时间:" + new Date());
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			System.out.println("有人打断我了");
//			e.printStackTrace();
			System.exit(0);
		}
		
		System.out.println(Thread.currentThread().getName() + "睡眠起床时间:" + new Date());
	}
	
}

后台线程

构造方法

public final void setDaemon(boolean on)

一般来说,JVM(JAVA虚拟机)中一般会包括两种线程,分别是用户线程和后台线程。
所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,
并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束的时候,也就是用户线程都结束的时候,程序也就终止了。同时,会杀死进程中的所有的后台线程。
反过来说,只要有任何非后台线程还在运行,程序就不会结束。比如执行main()的就是一个非后台线程。
基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了。

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		DaemonThread t1 = new DaemonThread();
		DaemonThread t2 = new DaemonThread();
		DaemonThread t3 = new DaemonThread();
		
		t1.setName("武器大师");
		t2.setName("小法师");
		t3.setName("光辉女郎");
		
		t1.setDaemon(true);
		t2.setDaemon(true);
		t3.setDaemon(true);
		
		t1.start();
		t2.start();
		t3.start();
		
		Thread.currentThread().setName("大水晶");
		for (int i = 1; i <= 10; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
		
	}
}

class DaemonThread extends Thread {
	
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

加入线程

构造方法

public final void join()
优先让线程执行完毕

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		JoinThread t1 = new JoinThread();
		JoinThread t2 = new JoinThread();
		JoinThread t3 = new JoinThread();
		
		t1.setName("老张");
		t2.setName("老李");
		t3.setName("老王");
		
		//必须要将join方法放在需要先执行完的线程后面
		t1.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
		try {
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t3.start();
		
	}
}

class JoinThread extends Thread {
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(getName() + ":" + i);
		}
	}
}

线程礼让

public static void yield()
		让出CPU的执行权一小会
		这里让出了CPU的执行权,并不是说把执行权交给了其他线程
		而是释放了自己的执行权,自己还可以重新和其他线程抢夺执行权

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		YieldThread t1 = new YieldThread();
		YieldThread t2 = new YieldThread();
		YieldThread t3 = new YieldThread();
		
		t1.setName("华妃");
		t2.setName("杨贵妃");
		t3.setName("武则天");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

class YieldThread extends Thread {
	
	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(getName() + ":" + i);
			Thread.yield();
		}
	}
}

线程同步

产生线程安全的因素:
1.必须存在多线程环境
2.至少有两条语句操作了共享数据
3.如果多个线程中有一个线程对共享数据进行了写操作

综合来说:
在多线程环境下,至少有两条以上的原子性语句操作了共享数据,
并且这个操作是写操作,肯定会出现线程安全问题

如何来解决线程安全问题?
	1.同步代码块
	2.同步方法
	3.Lock锁

1.同步代码块
格式:
synchronized(锁对象){需要同步的代码;}
注意:
a.锁对象是任意对象
b.不同线程共享同一把锁
c.同步方法的锁对象是 this
d.如果方法是静态方法,锁对象是字节码文件对象
同步的好处
解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,
降低程序的运行效率。如果出现了同步嵌套,就容易产生死锁问题
2.同步方法
格式:
public synchronized 返回值 方法名(参数列表) {
//需要同步的代码块
}
3.Lock锁 (常用)

举例代码如下

public class ThreadDemo01 {
	public static void main(String[] args) {
		SellTicketThread stt = new SellTicketThread();
		
		Thread t1 = new Thread(stt);
		Thread t2 = new Thread(stt);
		Thread t3 = new Thread(stt);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}


class SellTicketThread implements Runnable {

	private int tickets = 100;
	Lock lock = new ReentrantLock();

	@Override
	public void run() {
		while (true) {	
			lock.lock();
			if (tickets > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets --) + "张票");
				
			}
			lock.unlock();
		}
	}
	public synchronized void sellTickets() {
		if (tickets > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets --) + "张票");
			
		}
	}
	
}

class MyLock {
	public static final Object LOCK = new Object();
}

线程的生命周期

生命周期:从创建到销毁的全过程
线程的生命周期:线程从开始到销毁的全过程

状态:
新建状态(初始状态):创建线程对象的时候 Thread t = new Thread(); state = NEW
就绪状态(可运行状态):该线程具备执行的资格,但是还没有执行的权利,处于线程队列等待获取CPU的执行权
运行状态:该状态下线程即具有执行资格,同时也具备执行权,线程被CPU分配到时间片,表示正在执行

阻塞状态
由于某些操作使得当前线程处于阻塞状态,这个时候有可能线程由于其他的操作又回到了就绪状态等待获取CPU的执行权
等待阻塞
同步阻塞
其他阻塞

死亡状态:stop方法或者run方法执行结束,线程对象称为垃圾对象,等待垃圾回收器在空闲的时候回收
在这里插入图片描述
一个线程如果发生了阻塞,可能是以下几种情况:
1、等待阻塞:运行中的线程调用了wait()方法,JVM会把该线程放入等待池
2、同步阻塞:运行中的线程获取到同步锁对象的时候,JVM会把线程放入池锁中
3、其他阻塞:运行的线程执行了sleep或者join等方法,或者当前线程有IO请求的时候,JVM会把该线程设置阻塞状态(BLOCK),当sleep时间到,join线程结束,IO请求结束,线程回到就绪状态

死锁

死锁的概念:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。
另一个原因是默认的锁申请操作是阻塞的。
Java中如何避免死锁

既然我们知道了产生死锁可能性的原因,那么就可以在编码时进行规避。Java是面向对象的编程语言,程序的最小单元是对象,对象封装了数据和操作,所以Java中的锁一般也是以对象为单位的,对象的内置锁保护对象中的数据的并发访问。所以如果我们能够避免在对象的同步方法中调用其它对象的同步方法,那么就可以避免死锁产生的可能性。
代码演示

public class ThreadDemo {
	public static void main(String[] args) {
		DieLock t1 = new DieLock(true);
		DieLock t2 = new DieLock(false);
		
		t1.start();
		t2.start();
	}
}

class DieLock extends Thread {
	
	// 定义flag标志位用于切换线程执行
	private boolean flag;
	
	public DieLock() {
		super();
	}
	
	public DieLock(boolean flag) {
		super();
		this.flag = flag;
	}

	@Override
	public void run() {
		if (flag) {
			synchronized (MyLock.LOCKA) {
				System.out.println("if语句中的LockA锁");
				synchronized (MyLock.LOCKB) {
					System.out.println("if语句中的LockB锁");
				}
			}
		} else {
			synchronized (MyLock.LOCKB) {
				System.out.println("else语句中的LockB锁");
				synchronized (MyLock.LOCKA) {
					System.out.println("else语句中的LockA锁");
				}
			}
		}
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
} 

class MyLock {
	public static final Object LOCKA = new Object(); //锁对象
	public static final Object LOCKB = new Object();
}

结果:
在这里插入图片描述
池的组成:
初始容量
最大容量
增量
最小空闲数
最大空闲数
等待时间
在这里插入图片描述
线程池:
当程序中要创建大量生存期很短的线程时,应该考虑使用线程池,程序启动一个新线程占用资源大,使用线程池可以很好的提高性能,线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

Executors 工厂类来产生线程池。
	构造方法
	public static ExecutorService newCachedThreadPool()
	public static ExecutorService newFixedThreadPool(int nThreads)
	public static ExecutorService newSingleThreadExecutor()

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建线程池
		ExecutorService pool = Executors.newFixedThreadPool(3);
		// 将线程放入到池中
		pool.submit(new MyRunnable());
		Future<Integer> future = pool.submit(new MyCallable());
		pool.submit(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println(Thread.currentThread().getName() + "线程池匿名内部类实现Runnable方式开启线程:" + i);
				}
			}
		});
		System.out.println("1~100的和为:" + future.get());
		
		// 结束线程池
		pool.shutdown();
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "线程池实现Runnable方式开启线程:" + i);
		}
	}
	
}

class MyCallable implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {
		
		int sum = 0;
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + "线程池实现Callable方式开启线程:" + i);
			sum += i;
		}
		return sum;
	}
	
}

线程组

为什么需要对线程分组? 方便对一批线程进行管理 本质可以理解为 Thread threads[];
	Thread(ThreadGroup group, Runnable target) 
                     分配新的 Thread 对象。 
	Thread(ThreadGroup group, Runnable target, String name) 
	          分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。 
	Thread(ThreadGroup group, Runnable target, String name, long stackSize) 
	          分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。 
	Thread(ThreadGroup group, String name) 
	          分配新的 Thread 对象。

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		
		// 创建小组
		ThreadGroup tg1 = new ThreadGroup("天天向上组");
		ThreadGroup tg2 = new ThreadGroup("快乐大本营组");
		
		MyRunnable mr = new MyRunnable();
		
		Thread t1 = new Thread(tg1, mr, "何炅");
		Thread t2 = new Thread(tg1, mr, "谢娜");
		Thread t3 = new Thread(tg1, mr, "李维嘉");
		
		Thread t4 = new Thread(tg2, mr, "汪涵");
		Thread t5 = new Thread(tg2, mr, "大张伟");
		Thread t6 = new Thread(tg2, mr, "田源");
		
//		Thread[] threads = new Thread[3];
//		int enumerate = tg1.enumerate(threads);
//		System.out.println(Arrays.toString(threads));
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
		t6.start();
		
		System.out.println(tg1.activeCount());
		tg1.setDaemon(true);
		tg1.stop();
		System.out.println(tg1.getName());
		
		// 获取主线程所在的组?
		String mainThreadGroupName = Thread.currentThread().getThreadGroup().getName();
		System.out.println(mainThreadGroupName);
		
	}
}

class MyGroupRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}

线程通信

线程通信: 线程和线程之间实现数据交互
1.通过构造方法 A -> B A在启动之前传递给B
2.通过实现Callable接口的方式 A -> B B在执行结束之后将数据回传给A
3.利用接口回调方式传递数据
4.利用同步锁串行实现数据传递
5.利用等待唤醒机制
接口回调
1.创建一个回调接口[电话号码],接口中书写方法 A->B
方法的形参就是 B线程使用形参传递给A线程
方法的返回值就是 A线程使用返回值传递给B线程
2.A线程将这个回调接口通过构造方法传递给B线程
3.B线程再使用这个接口,即接口回调

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		AThread a = new AThread();
		a.setName("A");
		a.start();
		
	}
}

class AThread extends Thread {
	@Override
	public void run() {
		
		BThread b = new BThread();
		b.setName("B");
		b.setCb(new ICallBack() {
			
			@Override
			public int fun(int a, int b, String message) {
				int sum = a + b;
				System.out.println("收到了来自于B线程的消息: " + message);
				return sum;
			}
		});
		b.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "线程正在执行!");
		}
		
	}
}

class BThread extends Thread {
	
	private ICallBack cb;
	
	public BThread() {}
	//A线程将这个回调接口通过构造方法传递给B线程
	public BThread(String name, ICallBack cb) {
		super(name);
		this.cb = cb;
	}
	
	public void setCb(ICallBack cb) {
		this.cb = cb;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "线程正在执行!");
		}
		// 这里你想要传递什么数据就通过实参,实参你可以自己任意的确定 B->A
		int result = cb.fun(10, 20, "我是线程B带过来两个整数数据");
		// 这里的result是 A线程【调用者】传递给B线程 A->B
		System.out.println("收到了来自于A线程的数据: " + result);
	}
	
}

// 回调接口
interface ICallBack {
	/**
	 * 回调接口
	 * @param a
	 * 		是B线程 传递 A线程的数据
	 * @param b
	 * 		是B线程 传递 A线程的数据
	 * @param message
	 * 		是B线程 传递 A线程的数据
	 * @return
	 * 		是A线程 传递 B线程的数据
	 */
	int fun(int a, int b, String message);
}

"共享资源"式的通信
多个线程需要访问同一个共享变量,哪个线程获取到了锁对象就可以执行。

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		MyObject object = new MyObject();

		// 线程A与线程B 持有的是同一个对象:object
		ThreadA a = new ThreadA(object);
		ThreadB b = new ThreadB(object);
		ThreadC c = new ThreadC(object);
		
		a.start();
		b.start();
		c.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(object.getData());
	}
}

// 锁对象
class MyObject {
	
	private String data = "";

	public synchronized void methodA(String message) {
		System.out.println(message);
		data += message;
	}

	public synchronized void methodB(String message) {
		
		System.out.println(message);
		data += message;
	}

	public synchronized void methodC(String message) {
		System.out.println(message);
		data += message;
	}
	
	public void setData(String data) {
		this.data = data;
	}
	
	public String getData() {
		return data;
	}
}

class ThreadA extends Thread {

	private MyObject object;

	public ThreadA() {
		super();
	}

	public ThreadA(MyObject object) {
		super();
		this.object = object;
	}

	@Override
	public void run() {
		object.methodA("A");
	}
}

class ThreadB extends Thread {

	private MyObject object;

	public ThreadB() {
		super();
	}

	public ThreadB(MyObject object) {
		super();
		this.object = object;
	}

	@Override
	public void run() {
		object.methodB("B");
	}
}

class ThreadC extends Thread {

	private MyObject object;

	public ThreadC() {
	}

	public ThreadC(MyObject object) {
		this.object = object;
	}

	@Override
	public void run() {
		object.methodC("C");
	}
	
}

生产者消费者模型

比如买玩具:

生产者
   先判断是否有玩具
			有,就等待消费者消费  wait()
   		没有,就生产
   			生产完毕之后通知消费者消费  nofity

 消费者
 		先判断是否有玩具
		有,就消费
				消费完毕之后,就通知生产者生产
			没有,就等待生产者生产

该模型是否存在线程安全问题 --> 存在

等待唤醒机制依赖的方法

wait: 让当前线程处理等待(阻塞)状态,会将当前线程放入到等待池中,并且会释放锁对象
void wait(long timeout)
void wait(long timeout, int nanos)
notify: 唤醒在同一把锁上的处于等待的单个线程
nofityAll: 唤醒在同一把锁上处于等待的所有线程
举例代码如下

测试类

public class ThreadDemo {
	public static void main(String[] args) {
		Toy t = new Toy();
		
		SetThread st = new SetThread(t);
		GetThread gt = new GetThread(t);
		
		st.start();
		gt.start();
	}
}

玩具类

public class Toy {
	private String name; // 玩具名称
	private int num; // 玩具数量
	private boolean flag; // 是否有玩具, 有表示true,没有表示false

	public Toy() {
		super();
	}

	public Toy(String name, int num, boolean flag) {
		super();
		this.name = name;
		this.num = num;
		this.flag = flag;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}
	//创建判断是否有玩具的构造方法
	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}

	@Override
	public String toString() {
		return "Toy [name=" + name + ", num=" + num + ", flag=" + flag + "]";
	}

}

生产者类

/*
 * 生产者
 * 		先判断是否有玩具
 * 			有,就等待消费者消费  wait
 * 			没有,就生产
 * 				生产完毕之后通知消费者消费  nofity
 */
public class SetThread extends Thread {
	private Toy t;
	private int i;

	public SetThread() {
		super();
	}

	public SetThread(Toy t) {
		super();
		this.t = t;
	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (t) {
				// 先判断是否有玩具
				if (t.isFlag()) {
					// 就等待消费者消费
					try {
						t.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				// 没有,就生产
				if (i % 2 == 0) {
					t.setName("Hello Kitty");
					t.setNum(10);
				} else {
					t.setName("King");
					t.setNum(5);
				}
				i++;
				
				// 设置标志位,表示目前已经有数据了
				t.setFlag(true);
				
				// 生产完毕之后通知消费者消费  nofity
				t.notify();
			}
		}
		
	}

	public Toy getT() {
		return t;
	}

	public void setT(Toy t) {
		this.t = t;
	}
	
}

消费者类

/*
 * 消费者线程
 * 
 * 消费者
 * 		先判断是否有玩具
 * 			有,就消费
 * 				消费完毕之后,就通知生产者生产
 * 			没有,就等待生产者生产
 * 
 * 1.为什么等待唤醒机制中 wait方法不设计在Thread中,而设计在Object中?
 */
public class GetThread extends Thread {
	private Toy t;

	public GetThread() {
	}

	public GetThread(Toy t) {
		this.setT(t);
	}
	
	@Override
	public void run() {
		
		while (true) {
			synchronized (t) {
				// 先判断是否有玩具
				if (!t.isFlag()) {
					// 没有,就等待生产者生产
					try {
						t.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				// 有,就消费
				String toyName = t.getName();
				int toyNum = t.getNum();
				System.out.println(toyName + "|" + toyNum);
				// 消费一次数量减一
				t.setNum(--toyNum);
				
				// 消费完毕之后,就通知生产者生产
				if (toyNum <= 0) {
					// 设置标志位,说明没有玩具了
					t.setFlag(false);
					// 消费完毕之后,就通知生产者生产
					t.notify();
				}
			}
		}
		
	}

	public Toy getT() {
		return t;
	}

	public void setT(Toy t) {
		this.t = t;
	}
	
	
}

深入剖析volatile关键字

volatile关键字的两层语义

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1、保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2、禁止进行指令重排序。

举例代码如下

public class ThreadDemo {
	public static void main(String[] args) {
		try {
			RunThread thread = new RunThread();
			thread.start();
			Thread.sleep(1000);
			thread.setRunning(false);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class RunThread extends Thread {

	private volatile boolean isRunning = true;

	public boolean isRunning() {
		return isRunning;
	}

	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}

	@Override
	public void run() {
		System.out.println("进入到run方法中了");
		while (isRunning == true) {
		}
		System.out.println("线程执行完成了");
	}
}

在这里插入图片描述
在这里插入图片描述
volatile关键字的非原子性

所谓原子性,就是某系列的操作步骤要么全部执行,要么都不执行。
比如,变量的自增操作 i++,分三个步骤:
i = 10;
1.从内存中读取出变量 i 的值
2.将 i 的值加1
3.将 加1 后的值写回内存

1.从内存中读取出变量 i 的值 10
2.将 i 的值加1
3.将 加1 后的值写回内存

A线程执行到第二步, i变成11, B线程抢到了执行权,B读取内存中的数据还是10,执行第二步i=11
最后结果是11,预期结果是12,线程不安全
这说明 i++ 并不是一个原子操作。因为,它分成了三步,
有可能当某个线程执行到了第2步时被中断了,那么就意味着只执行了其中的两个步骤,没有全部执行。

以下程序期望的结果应该是: 100*100=10000,但是,实际上count并没有达到10000
volatile修饰的变量并不保证对它的操作(自增)具有原子性

1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,
换句话说,volatile变量在各个线程中是一致的
2、禁止指令的重排序优化; 指令重排序 , 这里间接可以保证线程安全
代码1
1:int a = 1;
2:int b = 2;
3:boolean flag = false;

------->
3:boolean flag = false;
2:int b = 2;
1:int a = 1;
代码1中,由于每条语句都是独立的,在java虚拟机内部有一个优化,java虚拟机会判断多条语句,如果相互之间是独立的,那么会自动调整语句顺序,以便有助于加快语句执行的效率,使用volatile关键字就不会使java虚拟机重新对语句排序
如何保证线程安全? volatile + synchronized

synchronized和volatile的区别
1.volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
2.volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
3.synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区
从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

举例代码如下

class MyThread extends Thread {
//	public volatile static int count;
	static AtomicInteger count = new AtomicInteger(0);

	private static void addCount() {
		for (int i = 0; i < 100; i++) {
//          count++;
			count.incrementAndGet();
		}
		System.out.println("count=" + count.get());
	}

	@Override
	public void run() {
		addCount();
	}
}

public class ThreadDemo02 {
	public static void main(String[] args) {
		MyThread[] mythreadArray = new MyThread[100];
		for (int i = 0; i < 100; i++) {
			mythreadArray[i] = new MyThread();
		}

		for (int i = 0; i < 100; i++) {
			mythreadArray[i].start();
		}
	}
}

在这里插入图片描述

本地线程ThreadLocal

针对每一个线程都提供对应的副本数据
这样操作每次只操作自己副本数据,保证了线程的安全,同时也提高了效率
但是操作的不是同一份数据

主要知识点
1.线程的启动方式
2.线程的生命周期
3.线程池
4.线程同步
5.线程通信
6.高并发

举例代码如下

import java.util.HashMap;

public class ThreadLocalDemo {
	public static void main(String[] args) {
//		MyLocalThread<User> tl = new MyLocalThread<User>();
		ThreadLocal<User> tl = new ThreadLocal<User>();
		tl.set(new User("隔壁老王", 18));

		new Thread(new Runnable() {

			@Override
			public void run() {
				User user = new User("隔壁老李", 25);
				tl.set(user);

				user.setName("hello kitty");
				System.out.println(tl.get());
			}
		}).start();

		try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println(tl.get());
	}
}

class MyLocalThread<T> {
	private HashMap<Thread, T> hm;

	public MyLocalThread() {
		hm = new HashMap<>();
	}

	// 添加数据到map中
	public void set(T t) {
		hm.put(Thread.currentThread(), t);
	}

	// 通过线程对象获取数据
	public T get() {
		return hm.get(Thread.currentThread());
	}

}

class User {
	private String name;
	private int age;

	public User() {
		super();
	}

	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}

}

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值