1、线程同步的案例
需求:某电影院目前正在上映国产大片,共有100张票。而他有三个售票窗口,请设计一个程序模拟该影院售票
思路:
1 定义一个类SellTicket 实习Runnbale接口,定义一个成员变量 private int tickets = 100;
2 在SellTicket类中 重写run()。实现卖票:
A 判断票数是否大于0
B 售出票之后 票数减一
C票没有了,也可能还有人来问,所以这个地方使用死循环 让卖票行为一直持续
3 定义一个测试类,实现售票动作
A 创建SellTicket 类的对象
B 创建三个Thread类 对象,将SellTicket 类的对象传递给线程 并给线程命名
C 启动线程
public class SellTicket implements Runnable{
private int ticktes = 100;
@Override
public void run() {
while(true){
if(ticktes > 0 ){
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
//创建三个线程
Thread t1 = new Thread(st,"1号窗口");
Thread t2 = new Thread(st,"2号窗口");
Thread t3 = new Thread(st,"3号窗口");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
出现每个窗口销售各自的100张票,实际生活中 售票是需要时间的 ,因此我们售卖一张票的时间为100毫秒
public class SellTicket implements Runnable{
private int ticktes = 100;
@Override
public void run() {
while(true){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
}
出现负票 和相同号的票
原因线程:执行的顺序是随机的
2、解决数据安全问题
出现问题的条件:
1 多线程环境
2 有共享数据
3 有多条语句操作共享数据
如何解决线程安全问题:
基本的思想:让程序没有安全问题的环境
怎么实现呢?
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行。
java提供了解决的方式是使用同步代码块或同步方法:synchronized 相当于给代码加锁
可以用在代码块和方法上 分别称为同步代码块和同步方法
2.1 同步代码块
public class SellTicket implements Runnable{
private int ticktes = 100;
private Object obj = new Object();
@Override
public void run() {
while(true){
// 给一下代码加锁 括号中需要一个所对象(任意对象都可以充当所对象 一般情况下使用this)
synchronized (obj){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
}
}
2.2 同步方法
在方法的声明上添加synchronized关键字
public class SellTicket implements Runnable{
private int ticktes = 100;
// private String s = "abc";
@Override
public void run() {
while(true){
sellTicket();
}
}
public synchronized void sellTicket(){//这才是真正存在数据安全的操作 这个就称为同步方法 这里锁对象时this
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
2.3 静态同步方法
public class SellTicket implements Runnable{
private static int ticktes = 100;
// private String s = "abc";
@Override
public void run() {
while(true){
sellTicket();
}
}
public static synchronized void sellTicket(){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
静态方法的同步代码块
public class SellTicket implements Runnable{
private static int ticktes = 100;// 共享数据应该是静态的
// private String s = "abc";
@Override
public void run() {
while(true){
sellTicket();
}
}
public static void sellTicket(){//这才是真正存在数据安全的操作 这个就称为同步方法 这里锁对象时this
synchronized(SellTicket.class){
if(ticktes > 0 ){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第" + ticktes+"张票");
ticktes--;
}
}
}
静态的同步方法或者静态方法中的同步代码块的所对象是类名.class对象
3、 线程安全的类
StringBuffer 线程安全的可变字符序列
StringBuilder 线程不安全的可变字符序列
Vector 线程同步的
HashTable 线程同步的 是一个哈希表的实现
在实际使用时,如果不需要线程安全的实现,推荐使用与之功能相同的 但是线程不同步的实现
4、 线程的死锁的演示
死锁是我们需要规避的问题
不同的线程分别占用对方所需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就行成了线程死锁
出现死锁 不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态 无法继续
public class DeadLockDemo {
public static void main(String[] args) {
final StringBuilder s1 = new StringBuilder();
final StringBuilder s2 = new StringBuilder();
new Thread(){
@Override
public void run() {
synchronized (s1){
s2.append("A");
synchronized (s2){
s2.append("B");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (s2){
s2.append("C");
synchronized (s1){
s1.append("D");
System.out.println(s2);
System.out.println(s1);
}
}
}
}.start();
}
}
死锁问题的出现是一个概率时间。
死锁问题的解决:
1 减少同步代码块或同步方法的嵌套
2 尽量减少同步资源的定义
3 使用专门的算法