多线程

1 多线程的引入

1 为什么要开启多线程?

1.当执行某个耗时任务的时候,需要开启多线程
2.希望多个任务看起来像是同时执行
3.防止线程阻塞
4.完成某个特定的任务

2 进程和线程

进程: 正在执行的程序
线程: 具有完成特定任务的一条执行路径,是CPU执行的最小单位(数据传输的基本单位是字节)
注意: CPU在某个时间刻度上只能够执行一条原子性语句 字节最小是bit位
原子性语句: 不可再分割的语句
int a = 1;是原子性语句:
a++;不是原子性语句
System.out.println(a);

3 CPU执行原理

1.真实环境下,除了多核处理器,本质都是CPU在某一个时间点上执行了一条线程
2.CPU看起来像是同时执行,但是本质上只执行了一天线程的一条原子性语句
3.在Java的线程程序中,采用的是抢占式调度模型
4.并发和并行的区别
并发:在同一个时间段同时执行
并行:在同一个时间刻度上同时执行,物理上同时执行
5.同步和异步的却别
同步:本质就是串行执行,并发情况下会出现同步问题
异步:能够在处理一个任务的同时还能够处理其他事情

多进程的好处
提高了进程的使用率,从而间接提高了CPU的使用率

多线程的好处
提高了CPU的使用率

一个进程可以包含多个线程

一个Java进程至少包含了几个线程?

2个线程
1.主线程
2.垃圾回收线程

public class ThreadDemo01 {
	public static void main(String[] args) {
		visitNet();
		visitDataBase();
		updateUserInterface();
		new Scanner(System.in).nextInt();
		System.out.println("daljdal");
	}
	
	public static void visitNet() {
		System.out.println("访问网络");
	}
	
	public static void visitDataBase() {
		System.out.println("访问数据库");
	}
	
	public static void updateUserInterface() {
		System.out.println("更新界面");
	}
}


2 线程的创建(开启多线程的方式)

1 方式一:继承Thread类

自定义类MyThread继承Thread类。

MyThread类里面重写run()方法。
创建线程对象。
启动线程。

注意:

1、启动线程使用的是start()方法而不是run()方法
2、线程能不能多次启动

代码演示使用方式一 开启线程
需求: 开启两个线程,一个线程拷贝文件,一个线程计算m~n的和

public class ThreadDemo02 {
	public static void main(String[] args) {
		// 创建线程对象。
		CopyFileThread cft = new CopyFileThread(new File("D:\\Java_0221\\02_JavaSE\\day21\\视频\\01.进程和线程.mp4"),
				new File("test.mp4"));
		// 启动线程。
		cft.start();
		
		CalculateThread ct = new CalculateThread(1, 100);
		ct.start();
		
		// 主线程的任务 输出1~100的数字
		for (int i = 1; i <= 100; i++) {
			System.out.println("main:" + i);
		}
		
	}
}

// 自定义CalculateThread继承Thread
class CalculateThread extends Thread {
	
	private int m;
	private int n;
	
	public CalculateThread() {
		super();
	}
	
	public CalculateThread(int m, int n) {
		super();
		this.m = m;
		this.n = n;
	}

	@Override
	public void run() {
		int sum = add(m,n);
		System.out.println(m + "~" + n + "的和为:" + sum);
	}

	public int add(int m, int n) {
		int sum = 0;
		for (int i = m; i <= n; i++) {
			sum += i;
		}
		return sum;
	}
}

// 自定义类CopyFileThread继承Thread类。
class CopyFileThread extends Thread {
	
	private File srcFile;
	private File descFile;
	
	// 线程的数据封装: 通过构造方法
	public CopyFileThread() {}
	
	public CopyFileThread(File srcFile, File descFile) {
		this.srcFile = srcFile;
		this.descFile = descFile;
	}
	
	// MyThread类里面重写run()方法。
	@Override
	public void run() {
		// 以后大家把这里理解为一个新的main方法,这里就是线程的核心任务代码
		// 向之前写方法一样书写这个任务
		/*
		 * 任务结果: 没有结果
		 * 任务参数: File srcFile, File descFile
		 * 功能: 文件拷贝
		 */
		System.out.println("视频文件拷贝开始!!!");
		copyFile(srcFile, descFile);
		System.out.println("视频文件拷贝结束!!!");
	}

	public void copyFile(File srcFile, File descFile) {
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		try {
			bis = new BufferedInputStream(new FileInputStream(srcFile));
			bos = new BufferedOutputStream(new FileOutputStream(descFile));
			byte[] bys = new byte[1024];
			int len = 0;
			while ((len = bis.read(bys)) != -1) {
				bos.write(bys, 0, len);
				bos.flush();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (bis != null) {
				try {
					bis.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			if (bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
}

2 方式二:实现Runnable接口方式开启线程

好处:

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

继承Thread也是有优点的: 能够直接在子线程访问Thread类的方式

开发中建议使用方式二开启线程

自定义类MyRunnable实现Runnable接口

重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
启动线程

输出1~100
public class ThreadDemo03 {
	public static void main(String[] args) {
		PrintLetterThread plt = new PrintLetterThread('A', 'Z');
		MyRunnable mr = new MyRunnable(100);
		// Thread(Runnable target) 
		Thread t1 = new Thread(mr);
		Thread t2 = new Thread(mr);
		Thread t3 = new Thread(mr);
		Thread t4 = new Thread(plt);
		// 启动线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class MyRunnable implements Runnable {

	private int num;
	
	public MyRunnable() {
		super();
	}
	
	public MyRunnable(int num) {
		super();
		this.num = num;
	}

	@Override
	public void run() {
		for (int i = 1; i <= 100 ; i++) {
			/*
			 * Thread.currentThread().getName()
			 * 获取当前正在执行的线程的名称
			 */
			System.out.println(Thread.currentThread().getName() + ":" + i);
			num ++;
		}
		System.out.println("num:" + num);
	}
	
}

// 2.输出m~n(子线程)
class PrintLetterThread implements Runnable {
	
	private Character startLetter;
	private Character endLetter;
	
	public PrintLetterThread() {
		super();
	}
	
	public PrintLetterThread(Character startLetter, Character endLetter) {
		super();
		this.startLetter = startLetter;
		this.endLetter = endLetter;
	}

	@Override
	public void run() {
		for (int i = startLetter; i <= endLetter; i++) {
			System.out.println((char)i);
		}
	}

	public Character getStartLetter() {
		return startLetter;
	}

	public void setStartLetter(Character startLetter) {
		this.startLetter = startLetter;
	}

	public Character getEndLetter() {
		return endLetter;
	}

	public void setEndLetter(Character endLetter) {
		this.endLetter = endLetter;
	}
}

3方式三: 实现Callable方式开启线程

本质还是方式二,需要依赖一个类FutureTask

public class FutureTask implements RunnableFuture { }

public interface RunnableFuture extends Runnable, Future { void run(); }

public interface Callable { V call() throws Exception; }

方式三开启线程的优点:

1.call方法有返回值,run方法没有返回值,可以返回结果给主线程
2.call方法可以抛出异常告知主线程
缺点: 1.会导致主线程在计算完成之前阻塞

实现Runnable和实现Callable接口的区别

1.有返回值
2.可以声明异常
这里的返回值和异常抛出都是给到线程的启动者

public class ThreadDemo04 {
	public static void main(String[] args) {
		// Callable
		FutureTask<Integer> calculateTask = new FutureTask<>(new CalculateCallable(1,100));
		Thread t = new Thread(calculateTask);
		t.start();
		try {
			Integer result = calculateTask.get();
			System.out.println(result);
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
		
		for (int i = 0; i < 1000; i++) {
			System.out.println("main:" + i);
		}
	}
}

class CalculateCallable implements Callable<Integer> {
	
	private int m;
	private int n;
	
	public CalculateCallable() {
		super();
	}

	public CalculateCallable(int m, int n) {
		super();
		this.m = m;
		this.n = n;
	}

	@Override
	public Integer call() throws Exception {
		// 这里和之前编写run方法一模一样
		// 这里有返回值和异常传递给主线程: 线程通信
		int sum = 0;
		for (int i = m; i <= n; i++) {
			System.out.println("Zi:" + i);
			sum += i;
			if (i == 50) {
				throw new NullPointerException("哈哈null!!!");
			}
		}
		return sum;
	}
	
}

4 方式四:匿名内部类开启线程

本质是一个继承或者实现了某个类或者接口的子类匿名对象 在Android使用频繁
如果一个线程既继承了Thread,同时实现了Runnable接口,那么继承Thread优先
注意: 如果同时使用两种方式开启线程,继承Thread方式优先!!!~

    public class ThreadDemo05 {
	public static void main(String[] args) {
		// 匿名内部类方式一开启线程
//		new Thread().start();
//		
//		new Thread() {
//			
//		}.start();
//		
//		new Thread() {
//			public void run() {
//				for(int i = 0; i < 100; i++) {
//					System.out.println(getName() + ":" + i);
//				}
//			};
//		}.start();
//		
//		// 匿名内部类方式二开启线程
//		new Thread(new Runnable() {
//			
//			@Override
//			public void run() {
//				for(int i = 0; i < 100; i++) {
//					System.out.println(Thread.currentThread().getName() + ":" + i);
//				}
//			}
//		}).start();
		
		// 面试题
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i = 0; i < 100; i++) {
					System.out.println("实现Runnable接口方式开启线程:" + i);
				}
			}
		}) {
			public void run() {
				for(int i = 0; i < 100; i++) {
					System.out.println("继承Thread方式开启线程:" + i);
				}
			}
		}.start();
	}
}

5 方式五:Lambda表达式方式开启线程

函数式接口: 只有一个抽象方法的接口称为函数式接口
Lambda表达式是JDK1.8之后引入
本质就是方便匿名内部类的书写

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

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

Runnable

public class ThreadDemo06 {
	public static void main(String[] args) {
		new Thread( ()->{
			for (int i = 0; i < 100; i++) {
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		} ).start();
	}
}

6 方式六:线程池开启线程

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

4 设置和获取线程的名称

1 通过构造方法

Thread(String name) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。

2 通过线程的成员方法

public final String getName()
public final void setName(String name)

3 通过静态方法

public static Thread currentThread()
可以获取任意方法所在的线程名称
可以获取主线程的线程名称:Thread.currentThread().getName();

long getId() 返回该线程的标识符。

public class ThreadDemo {
   public static void main(String[] args) {
//		Thread t1 = new MyThread();
//		Thread t2 = new MyThread();
//		Thread t3 = new MyThread();
   	
   	// 方式二: 通过构造方法
//		Thread t1 = new MyThread("隔壁老邓1");
//		Thread t2 = new MyThread("隔壁老邓2");
//		Thread t3 = new MyThread("隔壁老邓3");
   	Thread t1 = new MyThread();
   	Thread t2 = new MyThread();
   	Thread t3 = new MyThread();
   	
   	// 方式一: 通过setName方法
//		t1.setName("隔壁老邓");
//		t2.setName("隔壁老王");
//		t3.setName("隔壁老李");
   	
   	t1.start();
   	t2.start();
   	t3.start();
//		Thread mainThread = Thread.currentThread();
//		String name = mainThread.getName();
   	Thread.currentThread().setName("主线程");
   	System.out.println(Thread.currentThread().getName());
   	
   	System.out.println(t1.getId());
   	System.out.println(t2.getId());
   	System.out.println(t3.getId());
   	
   }
}

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

5 线程的优先级

Java是如何对线程进行调度的?
Java使用的是抢占式调度模型

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

设置和获取线程的优先级

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

public class ThreadDemo {
	public static void main(String[] args) {
		Thread t1 = new MyThread();
		Thread t2 = new MyThread();
		Thread t3 = new MyThread();
		
		t1.setName("皇贵妃");
		t2.setName("杨贵妃");
		t3.setName("绿贵妃");
		
		System.out.println(t1.getPriority());
		System.out.println(t2.getPriority());
		System.out.println(t3.getPriority());
		
		t1.setPriority(1);
		t2.setPriority(5);
		t3.setPriority(10);
		
		t1.start();
		t2.start();
		t3.start();
	}
}

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

6 线程休眠 sleep()

public static void sleep(long millis)

public class ThreadDemo03 {
	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();
			}
		}
	}
	
}

7 线程中断

public final void stop()
public void interrupt()

需求: 子线程休眠10S
主线程在子线程休眠到第5秒的时候打断子线程

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

public class ThreadDemo {
	public static void main(String[] args) {
		Thread t = new Thread(new MyRunnable(), "正在睡眠中...请稍后|");
		t.start();
		
		try {
			Thread.sleep(5000);
//			t.stop();
			t.interrupt();
		} catch (InterruptedException e) {
			System.out.println("主线程catch");
			e.printStackTrace();
		}
		
		System.out.println("Over");
	}
}

class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "子线程睡眠起始时间: " + new Date());
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			System.out.println("子线程catch");
			System.exit(0);
			e.printStackTrace();
		}
		
		System.out.println(Thread.currentThread().getName() + "子线程睡眠结束时间: " + new Date());
	}
}

8 后台线程

一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程,
分别是用户线程和后台线程。

所谓后台线程(daemon)线程指的是:

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

public final void setDaemon(boolean on)
public class ThreadDemo {
   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, "猴王");
   	
   	Thread.currentThread().setName("大水晶");
   	
   	t1.setDaemon(true);
   	t2.setDaemon(true);
   	t3.setDaemon(true);
   	
   	t1.start();
   	t2.start();
   	t3.start();
   	
   	for (int i = 0; i < 10; i++) {
   		System.out.println(Thread.currentThread().getName() + "|" + i);
   	}
   	
   }
}

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

9 线程加入 join()

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("张飞");
		
		
		t1.start();
		try {
			// 优先让t1线程执行完毕
			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 = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

10 线程礼让 yield()

public static void yield()

注意: 表示让出CPU的执行权一小会,这一小会多久不确定
让出了CPU的执行权并不是直接让给了其他线程
而是释放了执行权,自己还是可以参数执行权的争夺

t1 t2 t3 同时执行
Thread.yield() 让出t1线程的执行权
t1 t2 t3 继续抢
有可能又被t1抢到

public class ThreadDemo {
	public static void main(String[] args) {
		YieldThread t1 = new YieldThread();
		YieldThread t2 = new YieldThread();
		
		t1.setName("孔融的哥哥");
		t2.setName("孔融");
		
		t1.start();
		t2.start();
	}
}

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

11 线程安全问题

线程安全问题: 在多线程环境下,存在多条线程使用多条原子性语句对共享数据做了写操作

产生线程安全的因素:[**********]

1.必须存在多线程环境
2.至少有两条语句操作了共享数据
3.如果多个线程中有一个线程对共享数据进行了写操作
综合来说:
在多线程环境下,至少有两条以上的原子性语句操作了共享数据,
并且这个操作是写操作,肯定会出现线程安全问题

解决办法:

1.同步代码块
2.同步方法
3.Lock锁

解决办法方式一:同步代码块

格式:
synchronized(对象){需要同步的代码;}
同步的好处
解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,
降低程序的运行效率。如果出现了同步嵌套,就容易产生死锁问题

注意:
a.锁对象是任意对象
b.不同线程共享同一把锁
c.同步方法的锁对象是 this
d.如果通过方法是静态方法,锁对象是字节码文件对象

解决办法方式二:同步方法

格式:
public synchronized 返回值 方法名(参数列表) {
//需要同步的代码块
}

如果锁对象是this,就可以考虑使用同步方法。
方法分为两种: 静态方法和非静态方法
非静态方法的锁对象: this
静态方法的锁对象: 类的字节码文件对象

解决办法方式三:Lock锁

需求:深圳罗湖火车站目前正在出售车票,共有100张票,而它有3个售票窗口售票,
请设计一个程序模拟该火车站售票。

要求:使用两种方式实现
继承Thread类
实现Runnable接口

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

// 继承Thread类
/*class SellTicketThread extends Thread {
	
	private static int tickets = 100;
	
	@Override
	public void run() {
		while(true) {
			if (tickets > 0) {
				try {
					Thread.sleep(100L);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(getName() + "正在出售第" + (tickets --) + "张票!!!");
			} else {
				break;
			}
		}
	}
}*/

// 实现Runnable接口的方式
class SellTicketThread implements Runnable {
	
	public Lock lock = new ReentrantLock();
	
	private int tickets = 100;

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

12 死锁现象

死锁:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
代码演示死锁现象。

代码演示如下

/*
  获取到else语句体中的LockB锁
  获取到if语句体中的LockA锁
  
  获取到if语句体中的LockA锁
  获取到else语句体中的LockB锁
  
  获取到if语句体中的LockA锁
  获取到if语句体中的LockB锁
  获取到else语句体中的LockB锁
  获取到else语句体中的LockA锁
  
  获取到else语句体中的LockB锁
  获取到else语句体中的LockA锁
  获取到if语句体中的LockA锁
  获取到if语句体中的LockB锁
*/
public class ThreadDemo01 {
  public static void main(String[] args) {
  	DieLock dl1 = new DieLock(true);
  	DieLock dl2 = new DieLock(false);
  	
  	dl1.start();
  	dl2.start();
  }
}

class DieLock extends Thread {
  
  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锁");
  			}
  		}
  	}
  }
}

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

13 线程池,线程组

1 线程池

池: 本质就是缓冲区 数组 集合等容器 例如:数据库连接池,线程池… Integer 锁池 等待池
组: 分组,分门别类方便管理

池的好处:

提高效率
1.一般创建线程消耗的时间比较长,防止频繁创建线程浪费时间
2.可以使用完毕之后归还给池子,重复利用

池的设计 Thread[] List

池参数:

池子的初始容量: 10
最小空闲数: 3
增量: 5
最大空闲数: 6
最大容量: 16

线程池

当程序中要创建大量生存期很短的线程时,应该考虑使用线程池,
程序启动一个新线程占用资源大,使用线程池可以很好的提高性能,
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,
等待下一个对象来使用。
Executors工厂类来产生线程池。

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

public class ThreadDemo01 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		threadPool.submit(new MyRunnable());
		Future<Integer> submit = threadPool.submit(new MyCallable(1, 100));
		Integer result = submit.get();
		System.out.println(result);
		threadPool.submit(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 'A'; i <= 'Z'; i++) {
					System.out.println(Thread.currentThread().getName() + "|" + (char)i);
				}
			}
		});
		threadPool.shutdown();
		
	}
}

class MyRunnable implements Runnable {

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

class MyCallable implements Callable<Integer> {
	
	private int m;
	private int n;
	
	public MyCallable() {
		super();
	}
	
	public MyCallable(int m, int 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(Thread.currentThread().getName() + "|" + i);
			sum += i;
		}
		return sum;
	}
	
}

2 线程组

线程组:将多个线程分为一组,方便多个线程的统一管理,本质可以理解为 Thread threads[];

hread(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 ThreadDemo01 {
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();
		
		Thread mainThread = Thread.currentThread();
		ThreadGroup tg = new ThreadGroup("三国演义组");
		Thread t1 = new Thread(tg, mr, "刘备");
		Thread t2 = new Thread(tg, mr, "曹操");
		Thread t3 = new Thread(tg, mr, "孙权");
		Thread t7 = new Thread(tg, mainThread, "孙权");
		
		ThreadGroup tg2 = new ThreadGroup("西游记组");
		Thread t4 = new Thread(tg2, mr, "猪八戒");
		Thread t5 = new Thread(tg2, mr, "孙悟空");
		Thread t6 = new Thread(tg2, mr, "沙悟净");
		
		System.out.println(tg.getName());
		System.out.println(t3.getThreadGroup().getName());
		System.out.println(t6.getThreadGroup().getName());
		tg.setDaemon(true);
		
		tg.stop();
		// List<Thread>
		/*List<Thread> threadList = new ArrayList<>();
		for (Thread thread : threadList) {
			thread.stop();
		}*/
		
		String mainThreadGroupName = Thread.currentThread().getThreadGroup().getName();
		System.out.println(mainThreadGroupName);
		System.out.println(t7.getThreadGroup().getName());
	}
}

class MyRunnable implements Runnable {

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

14 线程通信: 线程和线程之间实现数据交互

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

15 等待唤醒机制

等待唤醒机制依赖的几个方法

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

生产者消费者模型

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

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

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

16 volatile关键字

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修饰的变量并不保证对它的操作(自增)具有原子性。
AtomicInteger

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

1:int a = 1;
2:int b = 2;
3:boolean flag = false;

–>
3:boolean flag = false;
2:int b = 2;
1:int a = 1;

如何保证线程安全? 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();
        }
    }
}

17 本地线程

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

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

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 + "]";
	}
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值