今天主要来学习一下线程同步,线程状态。以及线程通信
一、线程同步
package com.ticket2;
/*
* 如何解决线程安全问题呢?
*
* 要想解决问题,就要知道哪些原因会导致出问题:(而且这些原因也是以后我们判断一个程序是否会有线程安全问题的标准)
* A:是否是多线程环境
* B:是否有共享数据
* C:是否有多条语句操作共享数据
*
* 我们来回想一下我们的程序有没有上面的问题呢?
* A:是否是多线程环境 是
* B:是否有共享数据 是
* C:是否有多条语句操作共享数据 是
*
* 由此可见我们的程序出现问题是正常的,因为它满足出问题的条件。
* 接下来才是我们要想想如何解决问题呢?
* A和B的问题我们改变不了,我们只能想办法去把C改变一下。
* 思想:
* 把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
* 问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。
*
* 同步代码块:
* synchronized(对象){
* 需要同步的代码;
* }
*
* A:对象是什么呢?
* 我们可以随便创建一个对象试试。
* B:需要同步的代码是哪些呢?
* 把多条语句操作共享数据的代码的部分给包起来
*
* 注意:
* 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。多个线程必须是同一把锁。
*/
public class Test {
public static void main(String[] args) {
TicketRunnable runnable=new TicketRunnable();
Thread t1=new Thread(runnable);
t1.setName("111窗口");
Thread t2=new Thread(runnable);
t2.setName("222窗口");
Thread t3=new Thread(runnable);
t3.setName("333窗口");
t1.start();
t2.start();
t3.start();
}
}
package com.ticket2;
/**
* synchronized(一个对象,代表锁){
* //代码块
* }
两个窗口卖100张票 加锁实现同步
*/
public class TicketRunnable implements Runnable{
private int tickets=100;
private Object lock=new Object();
@Override
public void run() {
while(true) {
synchronized (lock) {//锁
if(tickets>0) {
try {
//1和2都在睡
Thread.sleep(100);//模拟售票阿姨休息
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在销售 "+(tickets--)+"张票");
}
}
}
}
private synchronized void test() {
}
}
注意:这里实现多线程 要采用实现Runnable接口的方式来实现,目的是为了实现资源的共享。
二:线程状态
1.新建状态:线程对象已经创建,还没有在其上调用start()方法。
2.可运行状态:当线程有资格运行,但调度程序还没有把他选定为运行线程时线程所处的状态。当start()调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待、或睡眠状态回来后,也返回到可运行状态。
3.运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程时所处的状态。这也是线程进入运行状态的唯一一种方式。
4.等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这三个状态组合为一种。其共同点是:线程仍旧是活的,但是当前没有条件运行,换句话说,他是可运行的。但是如果某件事情出现,他可能返回到可运行状态。
5.死亡状态:当线程的run()方法完成时,就认为它死去。这个线程对象也许是活的,但是,他已经不是单独执行的进程,线程一旦死亡,就不能复生。如果在一个死去的线程调用start()方法,会抛出java.lang.ILLegaIthreadStateException。
三:线程通信(利用wait,notify通信)
生产者–消费者模式
package www.cn.ftt;
public class Student {
String name;
int age;
boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}
package www.cn.ftt;
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(s.flag){
try {
s.wait(); //t1等着,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "丽丽";
s.age = 27;
} else {
s.name = "郜同学";
s.age = 30;
}
x++;
//修改标记
s.flag = true;
//唤醒线程
s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
}
}
}
}
package www.cn.ftt;
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//既然有,那我就消费!!消费掉
System.out.println(s.name + "---" + s.age);
//消费掉之后就没有了,所以我把标记改为false
s.flag = false;
//既然没有了,那么就唤醒线程(生产者)进行生产
s.notify(); //唤醒t1
}
}
}
}
package www.cn.ftt;
/*
* 解决方案:
* 加锁。
* 注意:
* A:不同种类的线程都要加锁。
* B:不同种类的线程加的锁必须是同一把。
*
* 想依次的一次一个输出。
*
* 如何实现呢?
* 通过Java提供的等待唤醒机制解决。
*
* 思路:
*
* 生产者:先看是否有数据,如果有就等待;没有就生产,生产完之后通知消费者来消费
*
* 消费者:先看是否有数据,如果有就消费;如果没有就等待,通知生产者生产数据
*
* 等待唤醒:
* Object类中提供了三个方法:
* wait():等待
* notify():唤醒单个线程
* notifyAll():唤醒所有线程
* 为什么这些方法不定义在Thread类中呢?
* 这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
* 所以,这些方法必须定义在Object类中。
*
* 小结:
* wait()被调用后,会自动释放所持有的锁。
* notify()方法被调用后,不会自动释放锁。
*/
public class StudentDemo {
public static void main(String[] args) {
//创建资源
Student s = new Student();
//设置和获取的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//线程类
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//启动线程
t1.start();
t2.start();
}
}
这些知识点并不是太全面,只是自己在学习的时候做的一个总结。
本文详细介绍了线程同步的基本概念和技术实现,包括同步代码块的使用、线程状态的变化及线程间的通信机制。通过具体示例展示了如何解决线程安全问题,以及如何运用等待唤醒机制实现线程间的有序交互。

被折叠的 条评论
为什么被折叠?



