多线程

基本概念

程序:是为完成特定任务、用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象

进程:程序的一次执行过程,或是正在执行的一个程序,是一个动态的过程,有它自身的产生、存在和消亡的过程。进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位)

线程:进程的进一步细化为线程,是一个程序内部的一条执行路径。线程切换开销小。(线程是cpu调度的最小单位)

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事

并发:一个CPU同时执行多个任务。比如:多个人做同一件事

创建的四种方式

注意,以下程序不是多线程!!

只是方法的递进调用,执行过程还是一步一步的单线程

class  Untitled{
	public static void main(String[] args) {
		new Untitled().m2("草莓");
	}
	public void m2(String s){
		m1(s);
	}
	public void m1(String s){
		System.out.println(s);
	}
}

继承

方式一:继承Thread类(四个步骤)

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run()------>此线程要执行的操作放run()方法
  3. 创建Thread类的子类的对象
  4. 通过此子类对象调用Thread类的start()方法

start()方法的作用:
①启动当前线程
②调用当前线程的run()

获取当前线程名字的方法
Thread.currentThread().getName()
返回当前线程对象
Thread.currentThread()

class  Untitled{
	public static void main(String[] args) {
		//注意一下的代码操作都是主线程执行,包括 t.start();
		
		//创建Thread类的子类的对象
		MyThread t = new MyThread();
		//通过此子类对象调用Thread类的start()方法
		t.start();
		//直接调用run()方法,就直接是主线程执行,不是多线程
		//t.run();
		//主线程的方法
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}
}
//创建一个继承于Thread类的子类
class MyThread extends Thread{
	//重写Thread类的run()
	public void run(){
		//此线程要执行的操作放run()方法
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}
}

再启动一个线程执行循环,就需要在创建一个Thread类的子类的对象

class  Untitled{
	public static void main(String[] args) {
		//创建Thread类的子类的对象
		MyThread t = new MyThread();
		//通过此子类对象调用Thread类的start()方法
		t.start();
		//再启动一个线程执行循环
		MyThread t1 = new MyThread();
		t1.start();
	}
}
//创建一个继承于Thread类的子类
class MyThread extends Thread{
	//重写Thread类的run()
	public void run(){
		//此线程要执行的操作放run()方法
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(i);
			}
		}
	}
}

小练习:

创建两个分线程,其中一个线程遍历100以内的偶数,另一个遍历100以内的奇数

class  Untitled{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1();
		t1.start();
		MyThread2 t2 = new MyThread2();
		t2.start();
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(i);
			}
		}
	}
}
class MyThread2 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 != 0){
				System.out.println(i);
			}
		}
	}
}
继承多窗口卖票

需求:创建3个窗口同时卖票,总票数100张。(注意:是卖同一种票,所以票是共享资源)

注意:此时这个代码还会有线程同步问题,待后期解决

class Untitled {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.setName("A窗口");
		t2.setName("B窗口");
		t3.setName("C窗口");
		t1.start();
		t2.start();
		t3.start();
	}
}
class MyThread extends Thread{
	//对于共享数据,需要设置成静态的,详见static关键字
	public static int ticket = 100;
	public void run(){
		while(true){
			if(ticket > 0){
				System.out.println(getName()+ticket);
				ticket--;
			}else{
				break;
			}
		}
	}
}

实现

方式二:实现Runnable类(五个步骤)

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

再启动一个线程执行循环,就只需要再创建一个Thread类的对象。不需要创建实现类的对象

class Untitled {
	public static void main(String[] args) {
		//创建实现类的对象
		MyThread mthread = new MyThread();
		//将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
		Thread t = new Thread(mthread);
		//通过此Thread类的对象调用start()方法
		t.start();
		
		//再启动一个线程执行循环
		Thread t1 = new Thread(mthread);
		t1.start();
	}
}
//创建一个实现了Runable接口的类
class MyThread implements Runnable{
	//实现类去实现Runable接口的run()
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(i);
			}
		}
	}
}

获取与更改线程的名字

因为是实现的方式创建分线程,所以实现类中不可以直接调用Thread类的方法

class Untitled {
	public static void main(String[] args) {
		MyThread mthread = new MyThread();
		Thread t1 = new Thread(mthread);
		t1.setName("线程A");
		t1.start();
		
		Thread t2 = new Thread(mthread);
		t2.setName("B线程");
		t2.start();
	}
}
class MyThread implements Runnable{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName()+i);
			}
		}
	}
}
实现多窗口卖票

需求:创建3个窗口同时卖票,总票数100张。(注意:是卖同一种票,所以票是共享资源)

注意:此时这个代码还会有线程同步问题,待后期解决

class Untitled {
	public static void main(String[] args) {
		//在实现的方式中,只会创建一个实现类的对象,所以卖票的过程是共享的,就不需要static修饰ticket
		MyThread mthread = new MyThread();
		Thread t1 = new Thread(mthread);
		t1.setName("A窗口");
		t1.start();
		
		Thread t2 = new Thread(mthread);
		t2.setName("B窗口");
		t2.start();
		
		Thread t3 = new Thread(mthread);
		t3.setName("C窗口");
		t3.start();
	}
}
class MyThread implements Runnable{
	public int ticket = 100;
	public void run(){
		while(true){
			if(ticket > 0){
				System.out.println(Thread.currentThread().getName()+ticket);
				ticket--;
			}else{
				break;
			}
		}
	}
}

实现Callable接口

更优处:
1、与run()方法相比,可以有返回值
2、方法可以抛出异常,这样重写的时候若是有异常,可以抛出去,也可以try-catch,要不然只能选择try-catch
3、支持泛型的返回值
4、需要借助FutureTask 类,比如获取返回值结果

FutureTask 类是Future接口的唯一实现类,同时还实现了Runnable接口。既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
Future接口可以对具体的Callable、Runnable任务的执行结果进行取消、查询是否完成、获取结果等

步骤:
1、创建一个Callable接口的实现类
2、实现call()方法,将此线程需要执行的操作声明在call()
3、创建Callable接口实现类的对象
4、将此实现类的对象作为参数传递到FutureTask 类构造器中,创建FutureTask 类的对象
5、将FutureTask 类的对象作为参数传递到Thread类构造器中,并调用start()方法
6、获取Callable接口实现类重写的call()方法的返回值

class NumThread implements Callable{
	public Object call() throws Exception{
		int sum = 0;
		for(int i = 0;i <= 100; i ++){
			if(i % 2 == 0){
				sum += i;
			}
		}
		return sum;
	}
}
public class  Test{
	public static void main(String[] args) {
 		//创建一个Callable接口的实现类对象
		NumThread num = new NumThread();
		//Callable实现类对象作为参数放入FutureTask构造器,创建FutureTask对象
		FutureTask ft = new FutureTask(num);
		//FutureTask对象作为参数放入Thread。调用start
		new Thread(ft).start();
		try{
			//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值结果
			Object s = ft.get();
			System.out.println(s);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

使用线程池

JDK 5 开始提供了相关API:ExecutorServiceExecutors

1、 ExecutorService: 真正的线程池接口,常见子类 ThreadPoolExecutor

常见方法如下:
void execute(Runnable command):执行任务、命令,没有返回值,一般用来执行 Runnable
<T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行 Callable
void shutdown():关闭线程池

2、 Executors:工具类,是线程池的工厂类,用于创建并返回不同类型的线程池

常见方法如下:
Executors.newFixedThreadPool(int i):创建一个可重用固定线程数的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或定期地执行

class Untitled {
	public static void main(String[] args) {
		//1、提供指定线程数量的线程池
		ExecutorService service = Executors.newFixedThreadPool(10);
		//2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口的实现类对象
		service.execute(new Num());   //适合于Runnable
		service.execute(new Num22());
		//service.submit();   //适合于Callable
		//3、关闭线程池
		service.shutdown();
	}
}
class Num implements Runnable{
	public void run(){
		for(int i = 0; i < 10; i ++){
			System.out.println(Thread.currentThread().getName() + i);
		}
	}
}
class Num22 implements Runnable{
	public void run(){
		for(int i = 0; i > -10; i --){
			System.out.println(Thread.currentThread().getName() + i);
		}
	}
}

使用线程池的好处:

1、 提高相应速度(减少创建新线程的时间)
2、降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3、便于线程管理,调用以下方法
setCorePoolSize(int i):核心池的大小
setKeepAliveTime(): 线程没有任务时最多保持多长时间后会终止
setMaximumPoolSize(): 最大线程数

class Untitled {
	public static void main(String[] args) {
		//1、提供指定线程数量的线程池
		ExecutorService service = Executors.newFixedThreadPool(10);

		//设置线程池的属性,先要转成子类对象,要调用子类方法
		ThreadPoolExecutor s1 = (ThreadPoolExecutor)service;
		s1.setCorePoolSize(15);

		//2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口的实现类对象
		//service.execute(new Num());   //适合于Runnable
		//service.execute(new Num22());
		//service.submit();   //适合于Callable
		//3、关闭线程池
		service.shutdown();
	}
}

线程常用的方法

void setName():给线程命名

String getName():返回线程的名称

class  Untitled{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1();
		//命名要在启动线程之前
		t1.setName("线程一:");
		t1.start();
		
		//给主线程命名
		Thread.currentThread().setName("主线程:");
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + i);
			}
		}
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(getName() + i);
			}
		}
	}
}

使用有参构造器给线程命名

class  Untitled{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("线程一:");
		t1.start();
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(getName() + i);
			}
		}
	}
	public MyThread1(String name){
		super(name);
	}
}

yield():线程让步,释放CPU,但其他线程能不能抢到看情况

释放了,但只释放了一点点,也可能回头自己又抢回来了CPU的资源

class  Untitled{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("线程一:");
		t1.start();
		Thread.currentThread().setName("主线程:");
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + i);
			}
		}
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(getName() + i);
			}
			if(i % 20 == 0){
				//释放了,但只释放了一点点,也可能回头自己又抢回来了CPU的资源
				yield();
			}
		}
	}
	public MyThread1(String name){
		super(name);
	}
}

join():直接让调用的线程先执行(我放弃了,你先上,我礼让,你做完了我再做)

以下代码就是主线程不再执行了,先让分线程执行完之后,主线程再执行

注意需要处理异常InterruptedException,因为父类throws了异常

class  Untitled{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("线程一:");
		t1.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 == 0){
			//此时先让 t1 执行,执行结束之后主线程才接着执行
				try{
					t1.join();
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(getName() + i);
			}
		}
	}
	public MyThread1(String name){
		super(name);
	}
}

static void sleep(long time):让当前的线程“睡眠”time毫秒,在指定的time毫秒时间内,当前线程是阻塞状态,使其他线程有机会被执行,时间到了之后重新排队

注意需要处理异常InterruptedException,因为父类throws了异常

class  Untitled{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("线程一:");
		t1.start();
		Thread.currentThread().setName("主线程:");
		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);
			}
		}
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(getName() + i);
			}
		}
	}
	public MyThread1(String name){
		super(name);
	}
}

boolean isAlive():返回boolean,判断线程是否还活着

class  Untitled{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("线程一:");
		t1.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 == 0){
			//此时先让 t1 执行,执行结束之后主线程才接着执行
				try{
					t1.join();
				}catch(InterruptedException e){
					e.printStackTrace();
				}
			}
		}
		System.out.println(t1.isAlive());    //false
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(getName() + i);
			}
		}
	}
	public MyThread1(String name){
		super(name);
	}
}

线程优先级的设置(线程的调度)

调度策略:
时间片:线程的调度采用时间片轮转的方式
抢占式:高优先级的线程抢占CPU

说明:高优先级的线程只是抢占CPU资源的概率提升,但是最终花落谁家还不一定

MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 —> 默认优先级

getPriority():返回线程优先级
setPriority( int i):改变线程优先级,要在线程启动之前,里面的数可以用已有的变量

public class Test{
	public static void main(String[] args) {
		MyThread1 t1 = new MyThread1("线程一:");
		//设置分线程的优先级
		t1.setPriority(MAX_PRIORITY);
		t1.start();
		Thread.currentThread().setName("主线程:");
		//设置主线程的优先级
		Thread.currentThread().setPriority(MIN_PRIORITY);
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority()+":"+i);
			}
		}
	}
}
class MyThread1 extends Thread{
	public void run(){
		for(int i = 0; i <= 100; i++){
			if(i % 2 == 0){
				System.out.println(getName()+getPriority()+":"+ i);
			}
		}
	}
	public MyThread1(String name){
		super(name);
	}
}

线程的生命周期

新建:当Thread类或其子类被声明并创建的时候,就是新建了线程

就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,未分配到CPU资源

运行:就绪的线程被调度获得了CPU的资源,进入运行状态,分配到CPU资源

阻塞:早某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU

死亡:所有线程的最终状态只有这一个,就像人最后终有一死

请添加图片描述

线程安全问题

多个线程对账户进行操作,比如取钱的时候,刚进入账户,显示有余额100,线程阻塞了一下,此时你对象从淘宝付款也进入你的账户,并付走了100,此时你取钱的线程阻塞结束,取走了100,就形成了线程的安全问题

  1. 问题原因:
    当某个线程操作共享数据的时候,未操作完成时,其他线程参与进来,也操作共享数据
  2. 问题解决:
    当一个线程操作共享数据的时候,让其他线程不能参与进来,直到该线程操作结束,其他线程才可以开始操作共享数据
  3. 问题处理:两种方式
    同步代码块
    同步方法

同步代码块

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

好处:安全
坏处:同步的时候只有一个线程运行,其他线程等待,效率低,尽可能缩小被同步的范围

说明:

  1. 同步监视器:就是锁。任何一个对象都可以充当锁,但是必须多个线程共用一把锁
  2. 操作共享数据的代码,就是需要被同步的代码
  3. 共享数据:多个线程共同操作的变量,比如:ticket

这个锁就相当于高铁公厕的厕所的提示灯,只可以有一个,若是人手一个,当你去了厕所变了红色的时候,我这边还是绿色,就会出问题

同步代码块处理实现Runable

在实现Runnable的方式中,实现类只会创建一个,所以对于多个分线程来说,这个实现类就是唯一的一份

所以在同步监视器的位置可以放this

class Untitled {
	public static void main(String[] args) {
		//在实现的方式中,只会创建一个实现类的对象,所以卖票的过程是共享的,就不需要static修饰ticket
		MyThread mthread = new MyThread();
		t1.setName("A窗口");
		Thread t1 = new Thread(mthread);
		t1.start();
		
		Thread t2 = new Thread(mthread);
		t2.setName("B窗口");
		t2.start();
		
		Thread t3 = new Thread(mthread);
		t3.setName("C窗口");
		t3.start();
	}
}
class MyThread implements Runnable{
	public int ticket = 100;
	public void run(){
		while(true){
			//只有以下的语句中操作了共享数据ticket,所以把这一部分放进同步代码块
			//在实现Runnable的方式中,实现类只会创建一个,所以对于多个分线程来说,这个实现类就是唯一的一份
			synchronized(this){
				if(ticket > 0){
					System.out.println(Thread.currentThread().getName()+ticket);
					ticket--;
				}else{
					break;
				}
			}
		}
	}
}

但是在以上代码中,若是将while(true){}语句也包括在同步代码块当中,无论执行多少次,都会出现只有一个分线程执行需要被同步的代码

此时的情况,就相当于同步方法,被同步的作用域比较大

同步代码块处理继承Thread

MyThread.class也是一个对象,而且只会加载一次
class c = MyThread.class;

class Untitled {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.setName("A窗口");
		t2.setName("B窗口");
		t3.setName("C窗口");
		t1.start();
		t2.start();
		t3.start();
	}
}
class MyThread extends Thread{
	//对于共享数据,需要设置成静态的,详见static关键字
	public static int ticket = 100;
	public void run(){
		while(true){
		//此时的子类会创建多个对象,所以this不是唯一的
			synchronized(MyThread.class){
				if(ticket > 0){
					System.out.println(getName()+ticket);
					ticket--;
				}else{
					break;
				}
			}
		}
	}
}

同步方法

同步方法就是方法的修饰词中有synchronized

如果需要同步的代码声明在一个方法中,就不妨将这个方法声明同步的

同步方法仍然涉及到同步监视器,只不过不需要我们去声明
非静态的同步方法,同步监视器:this
静态的同步方法,同步监视器:当前类本身

同步方法处理实现Runable

此时的同步监视器就是调用方法的this

class MyThread implements Runnable{
	public int ticket = 100;
	public void run(){
		while(true){
			sell();
		}
	}
	public synchronized void sell(){  //同步监视器:this
		if(ticket > 0){
			System.out.println(Thread.currentThread().getName()+ticket);
			ticket--;
		}
	}
}

以上的同步方法和以下效果一致

public void sell(){
	synchronized(this){  //同步监视器:this
		if(ticket > 0){
			System.out.println(Thread.currentThread().getName()+ticket);
			ticket--;
		}
	}
}
同步方法处理继承Thread

因为继承Thread类的方法会创建多个子类的对象,所以不能像实现Runnable接口一样直接用同步方法,需要声明成静态的,保证只有一份同步监视器

class MyThread extends Thread{
	public static int ticket = 100;
	public void run(){
		while(true){
			sell();
		}
	}
	public static synchronized void sell(){//同步监视器:MyThread.class
		if(ticket > 0){
			System.out.println(getName()+ticket);
			ticket--;
		}
	}
}

以上的同步方法和以下效果一致

public void sell(){
	synchronized(MyThread.class){  //同步监视器:MyThread.class
		if(ticket > 0){
			System.out.println(getName()+ticket);
			ticket--;
		}
	}
}

Lock(锁)

Lock接口是提供了对共享资源的独占访问

ReentrantLock类实现了Lock接口

ReentrantLock的构造器中可以放入boolean类型的数据
true 表示接下来的多个分线程是一个一个执行的,A、B、C、线程轮流按顺序执行锁内共享资源
false则无此规定,随机,若为空即为默认false

  1. 实例化ReentrantLock
  2. 调用锁定方法lock()
  3. 调用解锁方法unlock()
class Untitled {
	public static void main(String[] args) {
		MyThread mthread = new MyThread();
		t1.setName("A窗口");
		Thread t1 = new Thread(mthread);
		t1.start();
		
		Thread t2 = new Thread(mthread);
		t2.setName("B窗口");
		t2.start();
		
		Thread t3 = new Thread(mthread);
		t3.setName("C窗口");
		t3.start();
	}
}
class MyThread implements Runable{
	//创建ReentrantLock的对象
	private ReentrantLock lock = new ReentrantLock();
	public int ticket = 100;
	public void run(){
		while(true){
			try{
				//调用锁定方法lock()
				lock.lock();
					if(ticket > 0){
						System.out.println(Thread.currentThread().getName()+ticket);
						ticket--;
					}else{
						break;
					}
			}finally{
				//调用解锁方法unlock()
				lock.unlock();
			}
		}
	}
}

同步机制的练习题

需求:
有两个储户,向同一个账户存3000元,每次存1000元,存3次。每次存完打印账户余额

老师的答案:

//是否有多线程问题?是,两个储户线程
//是否有共享数据?有,账户
//是否有线程安全问题?有
//需要考虑如何解决线程安全问题?同步机制:三种方法
class Untitled {
	public static void main(String[] args) {
		Account acct = new Account();
		Customer c1 = new Customer(acct);
		Customer c2 = new Customer(acct);
		c1.setName("储户A");
		c2.setName("储户B");
		c1.start();
		c2.start();
	}
}
class Customer extends Thread{
	private Account acct;
	public Customer(Account acct){
		this.acct = acct;
	}
	public void run(){
		for(int i = 0; i < 3; i ++){
			acct.deposit(1000);
		}
	}
}
//共同的账户
class Account{
	private int money;
	//存钱
	public synchronized void deposit(int num){
		money += num;
		System.out.println(Thread.currentThread().getName()+money);
	}
}

执行结果如下
在这里插入图片描述

使用同步代码块:

class Untitled {
	public static void main(String[] args) {
		MyThread my = new MyThread();
		Thread t1 = new Thread(my);
		Thread t2 = new Thread(my);
		t1.setName("线程1:");
		t2.setName("线程2:");
		t1.start();
		t2.start();
	}
}
class MyThread implements Runnable{
	private static int money = 0;
	Object obj = new Object();
	public void run(){
		if(money >= 0){
			synchronized(obj){
				for(int i = 1;i <= 3 ; i ++){
					money += 1000;
					System.out.println(Thread.currentThread().getName()+money);
				}
			}
		}
	}
}

执行结果如下
在这里插入图片描述
使用Lock(锁):

import java.util.concurrent.locks.ReentrantLock;
class Untitled {
	public static void main(String[] args) {
		Bank bb = new Bank();
		Thread t1 = new Thread(bb);
		t1.setName("储户A:");
		t1.start();
		Thread t2 = new Thread(bb);
		t2.setName("储户B:");
		t2.start();
	}
}
class Bank implements Runnable{
	private ReentrantLock lock = new ReentrantLock();
	public static int money = 0;
	public void run(){
		for(int i = 1;i <= 3; i ++){
			try{
				lock.lock();
				money += 1000;
			System.out.println(Thread.currentThread().getName()+money);
			}finally{
				lock.unlock();
			}
		}
	}
}

执行结果如下:
在这里插入图片描述

单例模式之懒汉式

懒汉式原来的有线程安全问题的代码:

class Untitled {
	public static void main(String[] args) {
		Test t1 = Test.m();
		Test t2 = Test.m();
		System.out.println(t1 == t2);
	}
}
class Test{
	//1/私有化构造器
	private Test(){
		System.out.println("a");
	}
	
	//声明 static 对象,但不创建
	private static Test t = null;
	
	//提供 public、static 的返回对象的方法
	public static Test m(){
		if(t == null)
			t = new Test();
		return t;
	}
}

处理方式一,效率稍差

class Test{
	private Test(){
		System.out.println("a");
	}
	private static Test t = null;
	public static Test m(){
		//方式一:效率稍差
		synchronized(Test.class){
			if(t == null)
				t = new Test();
			return t;
		}
	}
}

处理方式二,效率稍高

当第一个线程创建了对象之后,其他分线程只需要在第一步判断的时候就会直接拿走对象,不需要拿锁在判断

class Test{
	private Test(){
		System.out.println("a");
	}
	private static Test t = null;
	public static Test m(){
		//方式二:效率稍高
		if(t == null){
			//第一步的时候,这里可能会有多个线程在等待
			synchronized(Test.class){
				//除开第一个分线程进去了创建对象,其他会拿锁的分线程均在此处返回
				if(t == null)  //注意此处不可以省略
					t = new Test();
			}
		}
		return t;
	}
}

线程死锁问题

理解死锁问题

理解:
不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

出现死锁后,不会出现提示,还是所有的线程都处于阻塞状态,无法继续

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

死锁问题举例1
以下代码加上sleep()方法时候,会增大出现死锁的概率

class Untitled {
	public static void main(String[] args) {
		StringBuffer s1 = new StringBuffer();
		StringBuffer s2 = new StringBuffer();
		new Thread(){
			public void run(){
				synchronized(s1){
					s1.append("草");
					s2.append("西");
					System.out.println(s1);
					System.out.println(s2);
					//调用sleep()方法,增大死锁概率
					try{
						Thread.sleep(100);
					}catch(InterruptException e){
						e.printStackTrace();
					}
				}
				synchronized(s1){
					s1.append("111111");
					s2.append("222222");
					System.out.println(s1);
					System.out.println(s2);
				}
			}
		}.start();
		new Thread(new Runnable(){
			public void run(){
				synchronized(s1){
					s1.append("莓");
					s2.append("瓜");
					System.out.println(s1);
					System.out.println(s2);
					//调用sleep()方法,增大死锁概率
					try{
						Thread.sleep(100);
					}catch(InterruptException e){
						e.printStackTrace();
					}
				}
				synchronized(s1){
					s1.append("333333");
					s2.append("444444");
					System.out.println(s1);
					System.out.println(s2);
				}
			}
		}).start();
	}
}

死锁问题举例2

class A{
	public synchronized void foo(B b){
		System.out.println(Thread.currentThread().getName()+"进入A类的foo方法");
		//调用sleep()方法,增大死锁概率
		try{
			Thread.sleep(100);
		}catch(InterruptException e){
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"企图调用B类的last方法");
		b.last();
	}
	public synchronized void last(){
		System.out.println("这里是A类的last方法");
	}
}
class B{
	public synchronized void bar(A a){
		System.out.println(Thread.currentThread().getName()+"进入B类的bar方法");
		//调用sleep()方法,增大死锁概率
		try{
			Thread.sleep(100);
		}catch(InterruptException e){
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"企图调用A类的foo方法");
		a.last();
	}
	public synchronized void last(){
		System.out.println("这里是B类的last方法");
	}
}
public class Test implements Runnable{
	A a = new A();
	B b = new B();
	
	public void init(){
		Thread.currentThread().setName("主线程");
		a.foo();
		System.out.println("进入了主线程之后");
	}
	public void run(){
		Thread.currentThread().setName("分线程");
		b.bar(a);
		System.out.println("进入了分线程之后");
	}
	public static void main(String[] args) {
		Test t1 = new Test();
		new Thread(t1).start();
		t1.init();
	}
}

线程的通信

比如两个线程交替打印,这个交替过程就是线程间的通信

举例:
使用两个线程打印1-100,交替打印

wait():一旦执行,当前线程进入阻塞状态,并释放同步监视器!!!
notify():一旦执行,会唤醒被wait()的随机一个线程
notifyAll():一旦执行,会唤醒被wait()的所有线程

说明:
1、以上三个方法只可以使用在同步代码块或同步方法中

2、以上三个方法的调用者必须是同步监视器,否则会报异常

3、以上三个方法是定义在 java.lang.Object类中的,因为同步监视器可以使任何一个对象,因为可以被如何一个对象调用,所以是Object的方法

class Untitled {
	public static void main(String[] args) {
		//在实现的方式中,只会创建一个实现类的对象,所以卖票的过程是共享的,就不需要static修饰ticket
		MyThread mthread = new MyThread();
		t1.setName("A窗口");
		Thread t1 = new Thread(mthread);
		t1.start();
		
		Thread t2 = new Thread(mthread);
		t2.setName("B窗口");
		t2.start();
		
		Thread t3 = new Thread(mthread);
		t3.setName("C窗口");
		t3.start();
	}
}
class MyThread implements Runnable{
	public int num = 1;
	Object obj = new Object();
	public void run(){
		while(true){
			synchronized(obj){
				//让调用notify()方法以外的随机一个在wait()的线程苏醒
				obj.notify();
				if(num <= 100){
					System.out.println(Thread.currentThread().getName()+num);
					num++;
					//让调用wait()方法的线程进入阻塞状态
					try{
						obj.wait();
					}catch(InterruptException e){
						e.printStackTrace();
					}
				}else{
					break;
				}
			}
		}
	}
}

线程通信练习题

需求:
生产者(Productor)将产品交给店员(Clerk)。消费者(Customer)从店员处拿走产品。
店员一次只可以持有 20 的产品,如果店中产品满了,店员会让生产者停一下,等店中有空位再让生产者生产。如果店中没有产品了,店员会让消费者等一下,等店中有产品了再通知消费者拿走产品

注意: 生产者比消费者快的时候,消费者可能会漏掉一些数据没有取到。消费者比生产者快的时候,消费者会取相同的数据

小结

  1. 理解程序、进程、线程的概念
  2. 多线程创建的两个方式
  3. 方式一:继承Thread类(四个步骤)
    1. 创建一个继承于Thread类的子类
    2. 重写Thread类的run()
      此线程要执行的操作放run()方法
    3. 创建Thread类的子类的对象
    4. 通过此子类对象调用Thread类的start()方法
  4. 方式二:实现Runnable接口(五个步骤)
    1. 创建一个实现了Runnable接口的类
    2. 实现类去实现Runnable接口的run()
    3. 创建实现类的对象
    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    5. 通过此Thread类的对象调用start()方法
  5. 比较两种创建线程的方式:
    1. 优先选择实现Runable接口的方式
      因为实现的方式没有类的单继承的局限性
      实现的方式更适合来处理多个线程有共享数据的情况(比如多窗口卖票情况)
    2. 联系:实际上Thread类也是实现了Runable接口
    3. 相同点:两种方式都要重写run()方法,将线程要执行的逻辑放入当中
  6. 方式三:实现Callable接口(六个步骤)
    1. 创建一个Callable接口的实现类
    2. 实现call()方法,将此线程需要执行的操作声明在call()
    3. 创建Callable接口实现类的对象
    4. 将此实现类的对象作为参数传递到FutureTask 类构造器中,创建FutureTask 类的对象
    5. FutureTask 类的对象作为参数传递到Thread类构造器中,并调用start()方法
    6. 获取Callable接口实现类重写的call()方法的返回值
  7. 实现Callable接口创建线程的优处:
    可以有返回值、支持泛型的返回值、可以抛异常
  8. 方式四:使用线程池
  9. start()方法的作用:
    ①使当前线程变为可运行状态(CPU调资源执行)
    ②调用当前线程的run()
  10. 线程的常用方法
    1. void start():启动线程,并执行对象的run()方法
    2. run():线程在被调用是执行的操作
    3. String getName():返回线程的名称
    4. void setName():给线程命名,也可以通过有参构造器命名
    5. static Thread currentThread():返回当前线程对象,在Thread子类中就是this,通常用于主线程和Runnable实现类
    6. yield():线程让步,释放当前CPU的执行权,但其他线程能不能抢得到看情况(我停了一步,给你机会了,看你把不把握得住)
    7. join():线程a中调用线程b的join()方法,此时线程a就进入阻塞状态,直到线程b完全执行完之后,线程a才结束阻塞状态(我放弃了,你先上,我礼让,你做完了我再做)
      注意需要处理异常InterruptedException
  11. static void sleep(long l):令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到了之后重新排队
    注意需要处理异常InterruptedException
  12. boolean isAlive():返回boolean,判断线程是否还活着
  13. stop():已过时。强制线程生命期结束
  14. getPriority():返回线程优先级
  15. setPriority( int i):改变线程优先级
  16. 线程优先级的设置:高优先级的线程只是抢占CPU资源的概率提升,但是最终花落谁家还不一定
    MAX_PRIORITY:10
    MIN_PRIORITY:1
    NORM_PRIORITY:5
  17. 线程的生命周期
  18. 线程同步的两个处理方式
    1. 方式一:同步代码块
      synchronized(同步监视器){ //需要被同步的代码 }
      1. 同步监视器:就是锁。任何一个对象都可以充当锁,但是必须多个线程共用一把锁
        ① 在实现Runnable接口的方法中,可以考虑用this充当同步监视器
        ②在继承Thread类的方法中,慎用this充当,可以考虑用当前类充当
      2. 操作共享数据的代码,就是需要被同步的代码
      3. 共享数据:多个线程共同操作的变量,比如:ticket
    2. 方式二:同步方法
      1. 同步方法仍然涉及到同步监视器,只不过不需要我们去声明
      2. 非静态的同步方法,同步监视器:this
      3. 静态的同步方法,同步监视器:当前类本身
    3. 方式三:Lock(锁)
      1. 实例化ReentrantLock
      2. 调用锁定方法lock() 与解锁方法unlock()
    4. synchronizedLock 的异同
      1. Lock是显式锁,需要手动开启和关闭锁。 synchronized是隐式锁,除了作用域自动释放
      2. Lock只有代码块锁,synchronized有代码块锁和方法锁
      3. 使用Lock,JVM可以花费更少的时间调度线程,性能更好
      4. 优先使用顺序:Lock—同步代码块—同步方法
  19. 单例模式之懒汉式
    直接把提供对象的方法定义为同步方法,如下:
    提供 public、static、synchronized 的返回对象的方法
  20. 线程死锁问题
    不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
    两个人需要走两步,A已经走第一步拿了锁1,现在走第二步需要锁2,可是B已经走了第一步拿了锁2,现在走第二步需要锁1 。二人互不相让,就死锁了
  21. 线程的通信
    1. 三个方法:
      wait():一旦执行,当前线程进入阻塞状态,并释放同步监视器!!!
      notify():一旦执行,会唤醒被wait()的随机一个线程
      notifyAll():一旦执行,会唤醒被wait()的所有线程
    2. 注意:
      1. 三个方法只可以使用在同步代码块或同步方法中
      2. 三个方法的调用者必须是同步监视器,否则会报异常
      3. 三个方法是定义在 java.lang.Object类中的,因为同步监视器可以使任何一个对象,因为可以被如何一个对象调用,所以是Object的方法
  22. wait()sleep()的异同:
    1. 相同点:一旦执行,都可以让当前线程进入阻塞状态
    2. 不同点:
      ①声明位置不同
      sleep()是Thread类的静态方法,wait()是Object类的非静态方法
      ②调用要求不同
      sleep()可以任何场景使用,wait()需要在同步代码块或同步方法中用
      ③释放同步监视器问题
      wait()在阻塞的同时会释放同步监视器,sleep()不会,仅仅是阻塞
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值