一,线程同步
- 问题的提出
例 题
模拟火车站售票程序,开启三个窗口售票。
public class Rwindow implements Runnable {
private int ticket =3;
@Override
public void run() {
while(ticket>0){
System.out.println("买票号"+ ticket);
ticket--;
}
}
}
class test1{
public static void main(String[] args) {
Rwindow t = new Rwindow();
Thread thread = new Thread(t);
Thread thread1 = new Thread(t);
Thread thread2= new Thread(t);
thread.setName("窗口1");
thread.setName("窗2");
thread.setName("窗3");
thread.start();
thread1.start();
thread2.start();
}
}
理想状态
极端状态 1 2 3 都sleep了进入阻塞状态
安全问题
由此就导致多线程出现了安全问题
.问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,
还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。
1.synchronized同步代码块:
synchronized(对象){
//需要被同步的代码块
}`
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:
多个线程共同操作的变量。比如: ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
这一点非常重要
Runnable()
Runnable方式可以
把synchronized里的关键字换成this
因为只有一个对象,
但是继承不可以
,因为有三个对象
synchronized里的关键字也可以换成 当前类.class
反射,类只会被加载一次,所有产生的Class对象唯一
继承
一定要加static 哦
2.synchronized同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步(synchronized)方法
注意:
1.同步方法仍然涉及到同步监视器,只是不需要显示的声明
2.非静态的同步方法,同步监视器 this
静态同步监视器,当前类本身
public synchronized void show(String name){
}
Runnable()
继承
懒汉单例模式变成线程安全的
懒汉单例模式:
class Bank{
private Bank(){}
private static Bank instance =null;
public static Bank getInstance() {
//方式一:效率较差
if (instance == null) {
instance = new Bank();
}
return instance;
}
}
方式一:
class Bank{
private Bank(){}
private static Bank instance =null;
public static Bank getInstance() {
//方式一:效率较差
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
return instance;
}
因为后面等待的数据就不用再判断等不等于null
直接返回instance 就好
方式二:
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
死锁
来看个例子:
在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
- 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1= new StringBuffer();
StringBuffer s2= new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
Lock(锁)
- 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。
构造函数:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
注意:如果同步代码有异常,要将unlock()写入finally语句块
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是
隐式锁,出了作用域自动释放
Lock手动挡,快 , synchronized 自动挡 - Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)
优先使用顺序:
Lock -> 同步代码块(已经进入了方法体,分配了相应资源) ->同步方法
(在方法体之外)
实战
class Account{
private double balance;
private ReentrantLock lock = new ReentrantLock();
public double depisit(double money){
if (money > 0) {
balance += money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "余额" + balance);
return balance;
}
}
class Customer extends Thread{
private Account acct;
public Customer(Account acct){
this.acct=acct;
}
@Override
public void run() {
for(int i=0;i<3;i++){
acct.depisit(1000);
}
}
}
class Test{
public static void main(String[] args) {
Account acct = new Account();
Customer t1= new Customer(acct);
Customer t2= new Customer(acct);
t1.setName("甲");
t2.setName("乙");
t1.start();
t2.start();
}
}
同步代码块一睡准出错:线程不安全
三种方式都可以,博主这里用了最简单的
但有没有发现这样只能实现甲用户自己取,乙用户自己取,能不能实现甲乙用户交替取钱呢?
当然可以啦,这就是线程通信了
线程通信
甲乙用户就可以交替执行了
生产者消费者问题
经典例题:生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处
取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图
生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通
知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如
果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
生产者比消费者快时,消费者会漏掉一些数据没有取到。
消费者比生产者快时,消费者会取相同的数据。
class Clerk{
private int productCount=0;
public synchronized void producerTask(){
if(productCount<20){
productCount++;
System.out.println(Thread.currentThread().getName()+"生产第"+productCount+"件");
//生产者只要生产了一个产品,就可以把对方唤醒
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumerTask(){
if(productCount>0){
System.out.println(Thread.currentThread().getName()+"消费第"+productCount+"件");
productCount--;
//消费者只要消费了一个产品就可以把对方唤醒
notifyAll();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println(getName()+"开始生产产品");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.producerTask();
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println(getName()+"开始消费产品");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumerTask();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p = new Producer(clerk);
Consumer c = new Consumer(clerk);
p.setName("生产者1");
c.setName("消费者1");
p.start();
c.start();
}
}
注意一点:博主烦的错误,一定要让两个线程阻塞起来,线程安全问题才更容易表示出来
不加sleep的话程序执行很快,就不会交替执行了,加了之后,看结果,交替执行