java语法基础 - 第九部分 - 线程同步

news/2024/5/17 19:19:00 标签: 线程, 同步, synchronized, Timer, 生产者、消费者

文章目录

      • 1. 概念
      • 2. 线程运行状态图 -- 重点
        • 2.1 线程终止 - 代码测试
        • 2.2 线程阻塞join() - 代码测试
        • 2.3 当前线程抢到CPU,但强制其重新跟其他线程进行此次的竞争 - 代码测试 - yield()
        • 2.4 线程等待, 不释放锁 -- sleep()
        • 2.5 线程等待, 释放锁 -- wait()
      • 3. Thread类、Runnable接口
      • 3. Timer工具类、TimerTask抽象类 -- 线程执行次数

1. 概念

同步:解决多线程访问同一资源,导致资源的不安全性 → 故引入线程同步

【一个系统可运行多个程序】
【一个程序一个进程】
【一个进程多个并行线程
电脑
程序:代码集合 -- 静态
进程:操作系统 调度 程序 -- 动态
CPU
Data
Code
线程:进程内多条执行路径

并发:同一时刻(时间段)
并行:同一时间点

2. 线程运行状态图 – 重点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gqJSq6rw-1574792097591)(en-resource://database/10575:1)]

2.1 线程终止 - 代码测试


  简单示例 --线程终止 → ( 标志符,外部干涉 )

class Run4 implements Runnable {
	
	// 1. 当前线程停止的标志位
	boolean isStop = false;
	
	@Override
	public void run() {
		while(!isStop) {
			System.out.println(Thread.currentThread().getName() + ": 正在执行");
		}
	}
	
	// 2. 让线程停止
	public void stop() {
		isStop = true;
	}
	
}

// 测试代码
public static void main(String[] args) throws Exception {
    Run4 run4 = new Run4();

    Thread thread = new Thread(run4);

    thread.start();

    Thread.sleep(1000);
    int i = 0;
    while(true) {
        i++;
        if(i == 100) {
            run4.stop();
            System.out.println(thread.getName() + " :停止");
            break;
        }
    }
}

  运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oqWQ1aj9-1574792097593)(en-resource://database/10579:1)]

2.2 线程阻塞join() - 代码测试

线程对象.join()当前线程一直占CPU, 其他线程阻塞, 直到当前线程执行完毕

  简单示例 - Thread.join()

class Thread1 extends Thread {	
	@Override
	public void  run() {
		for(int i = 0; i<10; i++) {
			System.out.println(Thread.currentThread().getName() + ".....正在运行");
		}
	}
}

// 代码测试
public static void main(String[] args) throws Exception {

    // 1. 开始main线程 开始创建 t1对象
    Thread t1 = new Thread1();

    // 2. main线程将  t1 线程放入就绪状态 -- 让 t1、main本身线程进行抢CPU
    t1.start();

    // 3. 当main线程抢到CPU时,main线程告诉CPU,把CPU让给t1线程进行使用,直到执行完毕为止,其他线程阻塞状态
    t1.join();
    
    // 4. 当t1线程执行完毕,该行的main线程的代码才执行
    for(int i = 0; i < 10; i++) {
        System.out.println(Thread.currentThread().getName() + "...正在运行");
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FSxn6K90-1574792097604)(en-resource://database/10581:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OWHg7ogr-1574792097612)(en-resource://database/10583:1)]

2.3 当前线程抢到CPU,但强制其重新跟其他线程进行此次的竞争 - 代码测试 - yield()


  代码示例 - Thread.yield()

class Thread2 extends Thread {	
	@Override
	public void  run() {
		for(int i = 0; i<1000; i++) {
			System.out.println(Thread.currentThread().getName() + ".....正在运行");		
		}
	}
}

// 测试代码
public static void main(String[] args) throws Exception {

    Thread t1 = new Thread2();

    t1.start();

    for(int i = 0; i<1000; i++) {
        if(i%5 == 0) {
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + i);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lHjQoYs9-1574792097615)(en-resource://database/10587:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ft42HrrV-1574792097623)(en-resource://database/10589:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDYN2ssr-1574792097626)(en-resource://database/10591:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eYVnbURj-1574792097632)(en-resource://database/10593:1)]

2.4 线程等待, 不释放锁 – sleep()


简单示例 - Thread.sleep( long ) - 打印时间

public static void main(String[] args) throws Exception {
		
		// 1. 获取运行时那刻的时间
		Date date = new Date();
		long Millisecond = date.getTime();
		
		// 2. 打印时间
		while(true) {
			// 3. 获取下一秒的时间
			date = new Date();
			
			// 4. 20秒后自动停止main进程
			if(Millisecond + 20000 < date.getTime()) {
				break;
			}
			System.out.println(date);
			Thread.sleep(1000);
		}

2.5 线程等待, 释放锁 – wait()

3. Thread类、Runnable接口

1. 线程对象直接执行run()方法是不可以执行的,只能调用start(),由CPU调用run方法

2. Thread类:已经实现Runnable接口

3. Thread是静态代理

4. 静态代理特点:

  • 4.1 真实角色、代理角色
  • 4.2 两者实现相同的接口
  • 4.3 代理角色持有真实角色的引用

3.1 构造线程的形式

方法1 - 继承Thread

  1. 线程实现 - 1

缺点: 不可以在继承其他类

class ThreadDemo1 extends Thread {
    @Overrride
    public void run() {
         代码
    }
}

Thread t1 = new ThreadDemo1();


方法2 - 实现Runnable

  2. 线程实现 - 2(推荐这种)

优点

  • 1. 只实现接口,所以可以继承器其他类
  • 2. 方便共享资源,多个代理访问
  • 3. 静态代理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xYRXaWu7-1574792097636)(en-resource://database/10569:1)]

class RunDemo1 implements Runnable {
    @Overrride
    public void run() {
         代码
    }
}

Thread t1 = new Thread( new RunDemo1() );


  模拟三个人( 代理角色 ) 抢 食物( 真实角色 )

class Foods implements Runnable {

	private int breadCount = 50;

	@Override
	public void run() {
		while (true) {
			if (breadCount < 0) {
				break;
			}
			System.out.println(Thread.currentThread().getName() + " 抢到了第 " + breadCount + " 面包");
			--breadCount;
		}
	}
}


//测试代码
public static void main(String[] args) {
    // 1. 真实角色
    Foods foods = new Foods();

    // 2. 三个代理角色,并且都持有同一个真实角色foods的引用
    Thread student1 = new Thread(foods, "student1");
    Thread student2 = new Thread(foods, "student2");
    Thread student3 = new Thread(foods, "student3");

    // 3. 三个学生开始抢面包
    student1.start();
    student2.start();
    student3.start();
}


方法3 - 实现Callable

优点:

  • 1. 可返回值、可声明异常


步骤:

  • 1. 实现Callable接口方法
  • 2. Executors.newFixedThreadPool( int ) – 获取ExecutorService对象( 创建线程池 )
  • 3. ExecutorService对象.submit( Callable实现类 ) – 获取Future对象 – 运行线程
  • 4. Future对象.get() – 线程运行时的返回值 – T类型
  • 5. ExecutorService对象.shutdownNow() – 停止线程

class Thr3 implements Callable<String> {
	int id;
	public Thr3(int id) {
		this.id = id;
	}
    
	@Override
	public String call() throws Exception {
		return "Callable"+ id +"实现 --" + Thread.currentThread().getName();
	}
}

// 测试代码
public static void main(String[] args) throws Exception {

    // 1. 创建Callable对象
    Callable<String> thread1 = new Thr3(1);
    Callable<String> thread2 = new Thr3(2);

    // 2. 创建线程
    ExecutorService es = Executors.newFixedThreadPool( 3 );

    // 3.  Callable对象的方法执行由线程池的某个线程来执行--  返回执行完线程后的结果对象
    Future<String> result = es.submit(thread1);
    Future<String> result2 = es.submit(thread2);

    // 4. 得到3步骤 获取线程执行完的结果
    String res = result.get();
    System.out.println(res);
    res = result2.get();
    System.out.println(res);

    // 5. 停止线程池中所有正在执行、等待的线程
    es.shutdownNow();
}

  运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPVFgDmB-1574792097643)(en-resource://database/10571:1)]

3.2 同步(锁)

同步线程安全): 解决 并发 导致的多线程访问同一资源,数据不同步

方法内的同步代码块:synchronized( ? ) { }
引用类型
this
类.class
同步方法(成员方法、静态方法)
3.2.1 同步成员方法

当前线程抢到真实角色调用方法的使用,其他线程不可以使用同一真实角色的同步方法,直到当线程同步方法运行完毕,其他线程才可以去抢同一真实角色的同步方法调用

class Foods2 implements Runnable {

	int count = 50;
	boolean isStop = false;

	@Override
	public void run() {
		while (!isStop) {
			// 1. 线程同步 -- 当前真实角色上锁 - 同一真实角色同一同步方法只能由一个线程进行调用
			gain1();

			// 2. 线程同步 -- 当前真实角色不上锁 -- 同一真实角色非同步方法可由多个线程同时调用
//			gain2();  
		}
	}

	private synchronized void gain1() {
		
		// 1. 进来先查看面包是否还有,没有则立即停止所有线程的运行
		if (count < 1) {
			isStop = true;
			return;
		}
		
		// 2. 打印当前线程抢到的面包数
		System.out.println(Thread.currentThread().getName() + " : 获取第 " + count-- + " 个面包");
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void gain2() {
		if (count < 1) {
			isStop = true;
			return;
		}
		System.out.println(Thread.currentThread().getName() + " : 获取第 " + count-- + " 个面包");
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

//---------------------------
// 测试代码

// 1. 创建一个真实角色 --   一份资源
Runnable foods = new Foods2();

// 2. 创建三个线程 --  共同争抢 同一个真实角色
Thread student1 = new Thread(foods, "学生1");
Thread student2 = new Thread(foods, "学生2");
Thread student3 = new Thread(foods, "学生3");

// 3. 线程启动
student1.start();
student2.start();
student3.start();

  运行测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnjim2l4-1574792097645)(en-resource://database/10595:1)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TnRyY9pn-1574792097649)(en-resource://database/10597:1)]

3.2.1 同步代码块 – 方法内 – 锁对象

如果是 synchronized(this){ } 跟 同步成员方法 是一样的 – 都是锁当前对象


  简单示例 - synchronized( this ) - 锁当前的真实对象

class Foods3 implements Runnable {

	int count = 50;
	boolean isStop = false;

	@Override
	public void run() {
		while (!isStop) {
			gain1();
		}
	}

	private void gain1() {
		
		synchronized (this) {
			// 1. 进来先查看面包是否还有,没有则立即停止所有线程的运行
			if (count < 1) {
				isStop = true;
				return;
			}
			// 2. 打印当前线程抢到的面包数
			System.out.println(Thread.currentThread().getName() + " : 获取第 " + count-- + " 个面包");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}


  简单示例 - synchronized( this ) - 锁当前的真实对象

3.3 死锁 - 过多的同步

线程可能需要两个资源锁定才能运行完,然后只占有一个,另一个被另一个线程占用,互相等待,资源互相释放不了

  Thread3线程类 – 完成执行完run方法需要先抢到o2对象,然后在抢到o1对象才能执行完

class Thread3 extends  Thread {
	
	Object o1 ;
	Object o2;
	
	public Thread3(Object o1, Object o2) {
		super();
		this.o1 = o1;
		this.o2 = o2;
	}

	@Override
	public void run() {
		synchronized(o1) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(o2) {
				System.out.println(Thread.currentThread().getName() + ":执行完成");
			}
		}
	}
	
}

  Thread4线程类 – 完成执行完run方法需要先抢到o1对象,然后在抢到o2对象才能执行完

class Thread3 extends  Thread {
	
	Object o1 ;
	Object o2;
	
	public Thread3(Object o1, Object o2) {
		super();
		this.o1 = o1;
		this.o2 = o2;
	}

	@Override
	public void run() {
		synchronized(o1) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(o2) {
				System.out.println(Thread.currentThread().getName() + ":执行完成");
			}
		}
	}
	
}


  利用上述两个线程类,进行死锁情况的制作

Object o1 = new Object();
Object o2 = new Object();

Thread thread1 = new Thread3(o1, o2);
Thread thread2 = new Thread4(o1, o2);

thread1.start();
thread2.start();

运行结果 – 没死锁多运行几遍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GpcqTufv-1574792097659)(en-resource://database/10709:1)]

3.4 生产者、消费者 - 减少多锁导致死锁的机率

Producer-consumer problem 或者 Bounded-Buffer problem

思想:

    1. 固定的大小缓冲区 — 线程共享
    1. 生产者往缓冲区添加数据,消费者往缓冲区取走数据
    1. 缓冲区满时,生产者休眠 – 等待下次消费者取走数据时才唤醒
    1. 缓冲区空时,消费者休眠 – 等待下次生产者添加数据时才唤醒
3.4.0 模拟生产者、消费者遇到的问题

即消费者明知缓冲区空依然还在取数据

  共享缓冲区类

// 消费者、生产者共同分享访问的资源
class Movices {
	
	// 1. 生产者、消费者共同的缓存区
	String[] movies = new String[10];
	
	// 2. movies的位置
	int index = 0;
	
	// 3. 生产者生产的第几个片
	static int addCount = 1;
	
	// 4. 消费者已经看了第几个片
	static int watchCount = 1;
	public synchronized void play(String pic) {
		
		// 超出数组缓冲区,直接结束方法
		if(index >= 10) {
			System.out.println("生产者别存片了,我已经放不下啦");
			return;
		}
		
		movies[index] = pic;
		System.out.println(Thread.currentThread().getName() + ": 增加" + movies[index] + " -  第" + addCount++ + "部");
		++index;
		
	}
	public synchronized void watch() {
		
		// 数组一旦存满index=10,防止数组超出数组长度
		if(index >= 10) {
			index = 9;
		}
		System.out.println(Thread.currentThread().getName() + ": 观看" + movies[index] + " -  第" + watchCount++ + "部");
		if(index <= 0) {
			System.out.println("消费者你都没片看了,你到底在看什么啊!--------------------------------");
			return;
		}
		--index;
	}
	
}

生产者 – 将电影放入播放数组中

class Player implements Runnable {
	Movices mov;
	Player(Movices mov) {
		this.mov = mov;
	}
	
	@Override
	public void run() {
		// 一共生产15张电影
		for(int i = 1; i <= 15; i++) {
			mov.play("电影");
		}
	}
}

消费者 – 观看电影的人

class Watcher implements Runnable {
	
	Movices mov;
	
	Watcher(Movices mov) { this.mov = mov; }
	
	@Override
	public void run() {
		// 消费者即将观看15张电影
		for(int i = 1; i<= 15; i++) {
			mov.watch();
		}
	}
}

测试代码

Movices mov = new Movices();
new Thread( new Player(mov), "生产者").start();
new Thread( new Watcher(mov), "消费者").start();



运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QhioXLIM-1574792097660)(en-resource://database/10713:0)]

synchronizedwaitnotifyAll_689">3.4.1 信号灯法 - 标志位 - synchronized、wait()释放锁、notifyAll()唤醒线程

  修改一下共享缓冲区域的代码

class Movices {
	
	/*
	 * 1. true:生产者生产,消费者等待
	 * 2. false: 生产者等待,消费者消费
	 */
	boolean flag = true;
	
	// 1. 生产者、消费者共同的缓存区
	String[] movies = new String[10];
	
	// 2. movies的位置
	int index = -1;
	
	// 3. 生产者生产的第几个片
	static int addCount = 1;
	
	// 4. 消费者已经看了第几个片
	static int watchCount = 1;
	
	public synchronized void play(String pic) {
		 
		// flag=false则让生产线程此时此刻等待不运行,释放锁,让消费线程使用释放的锁
		if(!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		// 生产一部电影
		++index;
		movies[index] = pic;
		System.out.println(Thread.currentThread().getName() + ": 增加" + movies[index] + " -  第" + addCount++ + "部");
		
		// 变为false,提醒消费者进行消费
		flag = false;
		this.notifyAll();
	}
	public synchronized void watch() {
		
		// flag=true则让消费线程此时此刻等待不运行,释放锁,让生产线程使用释放的锁
		if(flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		// 消费一部电影
		System.out.println(Thread.currentThread().getName() + ": 观看" + movies[index] + " -  第" + watchCount++ + "部");
		if(index < 0) {
			System.out.println("消费者你都没片看了,你到底在看什么啊!--------------------------------");
			return;
		}
		--index;
		
		// 提醒生产线程进行生产
		flag = true;
		this.notifyAll();
	}
	
}


  运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1yFjRtB-1574792097668)(en-resource://database/10715:0)]

TimerTimerTask___764">3. Timer工具类、TimerTask抽象类 – 线程执行次数

注意这个工具类已经被 java.lang.concurrent 包下的类取代了,尽量少用TimerTimerTask
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yOb0jlNL-1574792097669)(en-resource://database/10717:0)]

Timer_770">Timer的使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tt1CgE4l-1574792097674)(en-resource://database/10719:0)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1qwbRUtg-1574792097679)(en-resource://database/10723:0)]

  代码示例 – Timer.schedule( TimeTask, Date )

class Task extends TimerTask {
	
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "响了!:  该做作业了");
	}
	
}
        
//测试代码
 public static void main(String[] args) throws Exception {

		// 1. 声明闹钟 -- 并设置TimerTask线程的名字
		Timer timer = new Timer("闹钟");
		
		// 2.声明闹钟响后所做的事情
		Task task = new Task(); 
		
		// 3. 设置2秒后的时间点
		Date date = new Date( new Date().getTime() + 1000);
		
		// 4. 运行闹钟
		timer.schedule(task, date, 2);

  运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sUwJiFYM-1574792097685)(en-resource://database/10725:0)]


http://www.niftyadmin.cn/n/1658143.html

相关文章

Java设计模式 - 单例模式

文章目录1. 懒汉式1.1 懒汉式 -- 需要在生成单例对象 -- 线程不安全1.2 懒汉式 -- 需要在生成单例对象 -- 线程安全--单检查1.3 懒汉式 -- 需要在生成单例对象 -- 线程安全--双重检查2. 饿汉式 -- 已经是线程安全2.1 使用时单例加载2.2 调用getStudent()时单例才加载特点&#…

php dwg转pdf文件怎么打开,cad怎么打开pdf的图纸

cad打开pdf图纸的方法&#xff1a;首先打开软件&#xff0c;执行【插入】【PDF参考底图】&#xff1b;然后选中需要导入的PDF文件&#xff0c;点击确定&#xff1b;最后按一下键盘上的【回车】键即可。cad打开pdf图纸的方法&#xff1a;1、如下图所示&#xff0c;打开AutoCAD软…

php中什么是中间人,php – REST API身份验证:如何防止中间人重放?

我正在编写REST API,并希望实现类似于AWS的身份验证系统.基本上,在AWS上,客户端使用客户端和服务器之间共享的密钥对一些请求数据加密Authorization标头. (授权&#xff1a;AWS用户:)服务器使用密钥使用共享密钥解密标头,并与请求数据进行比较.如果成功,这意味着客户端是合法的…

Swing - 简单入门

文章目录1. 概念2. 类继承图3. 代码示例3.1 主窗体居中显示3.2 窗体内组件刷新 -- 解决增加的组件没在窗体显示3.3 模态对话框、按钮点击事件处理3.4 JLabel3.4.1 带图片以及文本显示3.4.2 修改JLabel图片的大小3.5 布局3.5.1 绝对定位 - null3.5.2 流式布局 - FlowLayout - 窗…

cps网店php源码,100%开源程序 PHP源码 页游联运系统 CPA+CPS

《页游联运系统》是一套以页游接入经营推广为主的处理方案。系统包含管理系统、PC官网、渠道联盟等。没有经验&#xff1f;没有技术&#xff1f;开发周期太长&#xff1f;完全不用担心&#xff01;选择溪谷&#xff0c;处理您的一切后顾之忧&#xff01;推荐配置&#xff1a;My…

数据结构与算法1 - 稀疏数组

文章目录稀疏数组 - 只存储有效元素的位置信息与有效值1.1 代码实现1.2 测试 - 代码稀疏数组 - 只存储有效元素的位置信息与有效值 1.1 代码实现 背景&#xff1a; 做个五子棋棋盘&#xff0c;整个棋盘由二维数组的0( 无棋 )、1( 白方棋 )、2( 黑方棋)元素进行映射棋盘&#x…

php自然周,Oracle实现算出自然周

很多時間我們需要用Oracle提供的to_char()函數來取得某個日期是屬於今年的第幾週.例如:TO_CHAR(TO_DATE(20090112,YYYYMMDD), WW) ,得到的結果是02,就是第2?可看看日歷上,2009年1月12日第三週的週一.為什麼會有這樣的問題呢?由於Oracle在to_char()函數計算一年中的第幾周是從…

数据结构与算法6 - 单向链表 - Java

文章目录概念单链表 - 新元素直接添加到尾部代码实现测试单链表 - 新元素动态添加到链表代码实现测试代码单链表 - 链表元素删除代码实现单链表 - 获取链表有效节点的个数代码实现单链表 - 获取倒数第n个节点的数据代码实现单链表 - 节点反转代码实现单链表 - 从尾开始打印节点…