线程同步
案例 卖票
public class SellTicket implements Runnable{
private int tickets = 100;
// 重写 run 办法 实现卖票
@Override
public void run(){while(true){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的
sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
tickets --;
}
}
// 主程序 SellTicketDemo
// 创立 SellTicket 对象
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();
卖票案例的思考
public class SellTicket implements Runnable{
private int tickets = 100;
// 重写 run 办法 实现卖票
@Override
public void run(){while(true){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的
// 通过 sleep 办法来模拟出票工夫
Thread.sleep(millis:100);//alt enter 捕捉异样
sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
tickets --;
}
}
// 主程序 SellTicketDemo
// 创立 SellTicket 对象
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();
下面那个在卖票类外面加了 sleep 函数,会呈现三个窗口卖同一张票或者卖票是正数
-
三个窗口卖同一张票
tickets = 100;t1 t2 t3 假如 t1 线程抢到 cpu 的执行权。而后 t1 线程劳动 100ms,在 t1 劳动的时候 t2 线程抢到了 cpu 的执行权,t2 线程就开始执行,也是从 tickets = 100 执行,100>0,走进循环,而后 t2 线程劳动 100ms,同理,在 t2 劳动的时候 t3 线程抢到了 cpu 的执行权,t3 线程就开始执行,也是从 tickets = 100 执行,100>0,走进循环,而后 t3 线程劳动 100ms 而后走到输入,假如线程依照劳动的程序醒过来,t1 抢到 cpu 的执行权,在控制台输入窗口 1 正在发售第 100 张票,而后就应该执行 tickets --,然而如果在这会正好 t2 抢到 cpu 的执行权,在控制台输入窗口 2 正在发售第 100 张票,同理,如果在这会正好 t3 抢到 cpu 的执行权,在控制台输入窗口 3 正在发售第 100 张票,如果这三个线程还是依照程序来,那么这里前面就会执行三次 tickets --,最终票就会变成 97
-
售票呈现正数
tickets = 1;t1 t2 t3 假如 t1 线程抢到 cpu 的执行权。而后 t1 线程劳动 100ms,在 t1 劳动的时候 t2 线程抢到了 cpu 的执行权,t2 线程就开始执行,也是从 tickets = 1 执行,100>0,走进循环,而后 t2 线程劳动 100ms,同理,在 t2 劳动的时候 t3 线程抢到了 cpu 的执行权,t3 线程就开始执行,也是从 tickets = 1 执行,1>0,走进循环,而后 t3 线程劳动 100ms 而后走到输入,假如线程依照劳动的程序醒过来,t1 抢到 cpu 的执行权,在控制台输入窗口 1 正在发售第 1 张票,假如 t1 持续领有 cpu 的使用权,则就会执行 tickets --,tickets 就会变成 0。而后就进不去循环了,而后与此同时,正好 t2 抢到 cpu 的执行权,在控制台输入窗口 2 正在发售第 0 张票,假如 t2 持续领有 cpu 的使用权,则就会执行 tickets --,tickets 就会变成 -1,而后就进不去循环了,同理,正好 t3 抢到 cpu 的执行权,在控制台输入窗口 2 正在发售第 - 1 张票,假如 t3 持续领有 cpu 的使用权,则就会执行 tickets --,tickets 就会变成 -2,
卖票案例数据安全问题的解决
为什么呈现问题(也是判断多线程程序是否会有数据安全问题的规范)
- 是否是多线程环境
- 是否共享数据
- 是否有多条语句操作共享数据
public class SellTicket implements Runnable{
private int tickets = 100;
// 重写 run 办法 实现卖票
@Override
public void run(){while(true){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的
// 通过 sleep 办法来模拟出票工夫
Thread.sleep(millis:100);//alt enter 捕捉异样
sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
tickets --;
}
}
// 主程序 SellTicketDemo
// 创立 SellTicket 对象
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();
如何解决多线程平安问题?
- 根本思维:让程序没有平安问题的环境 个别解决下面第三个问题
怎么实现
- 把多条语句操作共享数据的代码锁起来,然任意时刻只能有一个线程执行
- java 提供了 同步代码块 的形式解决
同步代码块
public class SellTicket implements Runnable{
private int tickets = 100;
private Object obj = new Object();// 创立一个新的对象,不论是什么线程进来都是执行 obj 是同一把锁
// 重写 run 办法 实现卖票
@Override
public void run(){while(true){synchronized(obj){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的
// 通过 sleep 办法来模拟出票工夫
Thread.sleep(millis:100);//alt enter 捕捉异样
sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
tickets --;
}
}
}
}
}
tickets = 100;t1 t2 t3
假如 t1 线程抢到 cpu 的执行权,t1 进来胡,就会执行 synchronized 把这段代码锁起来,锁完之后,就会执行 if 语句,而后 t1 线程劳动 100ms,假如在 t1 劳动的时候 t2 线程抢到了 cpu 的执行权,t2 线程而后 t2 线程发现被锁住了,就只能等着,而后等 t1 劳动好了,t1 持续向下执行,在控制台输入窗口 1 正在发售第 100 张票,而后执行 tickets --,tickets 就会变成 99,而后 t1 就进去了,t1 进去之后这段代码的锁就会被开释了
而后三个线程再持续抢 cpu 的使用权
同步办法
public class SellTicket implements Runnable{
private int tickets = 100;
private Object obj = new Object();// 创立一个新的对象,不论是什么线程进来都是执行 obj 是同一把锁
private int x= 0;
// 重写 run 办法 实现卖票
@Override
public void run(){while(true){if(x%2==0){synchronized(obj){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的
// 通过 sleep 办法来模拟出票工夫
Thread.sleep(millis:100);//alt enter 捕捉异样
sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
tickets --;
}
}
}else{synchronized(obj){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的
// 通过 sleep 办法来模拟出票工夫
Thread.sleep(millis:100);//alt enter 捕捉异样
sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
tickets --;
}
}
}
x++;
}
}
}
// 主程序 SellTicketDemo
// 创立 SellTicket 对象
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();
下面的 if 和 else 外面管制的都是一样的也是同一把锁,能够写成办法
同步办法 就是把 synchronized 关键字加到办法上
- 格局:修饰符 synchronized 返回值类型 办法名 (办法参数){}
而后下面那个两种办法的对象应该是一样的 都是办法里的 this 对象 -
同步办法的锁的对象是 this这个对象
private synchronized void sellTickett(){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 // 通过 sleep 办法来模拟出票工夫 Thread.sleep(millis:100);//alt enter 捕捉异样 sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票"); tickets --; } }
同步静态方法 就是把 synchronized 关键字加到静态方法上
- 格局:修饰符 static synchronized 返回值类型 办法名 (办法参数){}
而后下面那个两种办法的对象应该是一样的 都是办法里的 类名.class 对象 - 同步静态方法的锁的对象是 类名.class这个对象
线程平安的类
- StringBuffer
线程平安,可变的字符序列
从 JDK5 开始,被 StringBuilder 代替,通常应该应用 StringBuilder 类,因为它反对所有雷同的操作,但它更快,因为它不执行同步 - Vector
从 java2 平台 v1.2 开始,该类改良了 List 接口,使其成为 JavaCollectionFramework 的成员。与新的汇合实现不同,Vector 被同步,如果不须要线程平安的实现,倡议应用 ArrayList 代替 Vector - Hashtable
该类实现了一个哈希表,它将键映射到值,任何非 null 对象都能够用作键或者值
从 java2 平台 v1.2 开始,该类进行了改良,实现了 Map 接口,使其成为 JavaCollectionFramework 的成员。与新的汇合实现不同,Hashtable 被同步。如果不须要线程平安的实现,倡议应用 HashMap 代替 Hashtable
StringBuffer sb = new StringBuffer();// 加了 synchronized 关键字,所以也是同步办法,是线程平安的
StringBuilder sb2 = new StringBuilder();// 办法没有加 synchronized 关键字,所以是线程不平安
Vector<String> v = new Vector<String>();// 加了 synchronized 关键字,所以也是同步办法,是线程平安的
ArrayList<String> array = new ArrayList<String>();// 办法没有加 synchronized 关键字,所以是线程不平安
Hashtable<String,String> ht = new Hashtable<String,String>();// 加了 synchronized 关键字,所以也是同步办法,是线程平安的
HashMap<String,String> hm = new HashMap<String,String>();// 办法没有加 synchronized 关键字,所以是线程不平安
// 将线程不平安变成线程平安 所有的都是相似的示意
List<String> list = Collections.synchronizedlist(new ArrayList<String>())
Lock 锁
public class SellTicket implements Runnable{
private int tickets = 100;
private Lock lock = new Reentrantlock();
// 重写 run 办法 实现卖票
@Override
public void run(){while(true){lock.lock();
if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的
// 通过 sleep 办法来模拟出票工夫
Thread.sleep(millis:100);//alt enter 捕捉异样
sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
tickets --;
}
lock.unlock();}
// 主程序 SellTicketDemo
// 创立 SellTicket 对象
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();
通过 lock 和 unlock 加锁和开释锁
个别会用如下表是,用 try catch