线程
1、线程的同步
例:买票出现了重票和错票 -->线程的安全问题
出现问题的原因在于当某个窗口卖票过程中还未结束,另一个出口也参与进来导致 同一张车票出现被两个窗口同时操作的问题
如何解决呢?
答:当一个线程在操作共享数据的(票)时候,其他线程不允许参与进来,直到这个线程操作完共享数据后才可以参与进来,即使这个线程出现了阻塞也不能改变
//java中通过同步机制来解决线程安全问题
/***
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码:(操作共享数据的代码,放到synchronized中)
//什么是共享数据:多个线程同时操作的数据,比如卖票的票
//什么是同步监视器:俗称锁。任何一个类的对象都可以充当锁。(但是多个线程必须共用同一把锁)
}
*/
Object object = new Object();
@Override
public void run(){
while(true){
synchronized(obj){
if(ticket > 0 ){
try{
//让线程阻塞100毫秒
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
ticket--;
}else{
break;
}
}
}
}
注意:线程同步的好处解决了安全的问题,但是线程同步只能有一个线程参与,其他线程等待相当于一个单线程的过程,效率低,但是必须使用同步保障线程安全。
用实现Runnable的方式可以synchronized(this){},可以考虑用this替换代表当前对象,继承的方式不建议用this,但是可以用类进行替换 Synchronozed(类名.class) ,所有的方式都要保证synchronized()括号中的 一定要是同一把锁
/***
方法二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我门不妨将此方法声明同步的
在方法中添加synchronized
例如:public synchronized void show(){
}
如果是继承的方式,因为创建的对象是不同的,所以不能用this当作锁,可以在方法中添加static作为静态方法,这时就代表以方法所在类为锁,因为类在初始化声明一次,所以是唯一的
*/
2、懒汉式
(1)使用同步机制将单例模式中的懒汉式改写成线程安全的
class Bank{
private Bank(){
}
//懒汉式
private static Bank instance = null;
public static Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance;
}
}
此时上面的代码就属于线程安全问题了,那么该如何修改呢?
//1、用同步方法的方,在方法上添加上synchronized,这是同步方法的锁就是Bank.class
class Bank{
private Bank(){
}
//懒汉式
private static synchronized Bank instance = null;
public static Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance;
}
}
//2、用同步代码块的方式
//方式1:
class Bank{
private Bank(){
}
//懒汉式
private static Bank instance = null;
public static Bank getInstance(){
//效率较差
synchronized(Bank.class){
if(instance == null){
instance = new Bank();
}
return instance;
}
}
}
//方式2:
class Bank{
private Bank(){
}
//懒汉式
private static Bank instance = null;
public static Bank getInstance(){
//效率比较高
if(instance == null){
synchronized(Bank.class){
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
3、死锁
(1)什么是死锁?
不同的线程分别占用对方需要的同步资源不放弃,都在等对方放弃自己需要的同步资源,就形成了死锁。这时候不会出现异常,所有的线程都会处于阻塞的状态。
举个例子,两个人都会彼此有好感,但是PersonA和PersonB都不好意思对彼此表达爱意,这时就处于僵持状态,好比死锁。
//演示线程的死锁问题
public class ThreadTest{
public static void main(String[] args){
StringBuffer sb1 = new StringBuffer();
StringBuffer sb2 = new StringBuffer();
//通过继承的方式创建匿名类
new Thread(
@Override
public void run(){
synchronized(sb1){
sb1.append("a");
sb2.append("1");
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(sb2){
sb1.append("b");
sb2.append("2");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
).start();
//通过实现的方式创建线程内部类
new Thread(new Runnable(){
@Override
public void run(){
synchronized(sb2){
sb1.append("d");
sb2.append("3");
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(sb1){
sb1.append("e");
sb2.append("4");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}).start();
}
}
上面的代码就会出现死锁的状态,因为继承的线程sb1进入了阻塞的状态,实现的方式sb2也进入了阻塞的状态,这时候sb1在等sb2 ,sb2也在等sb1就会出现僵持状态,这时代码不会报错也不会往下进行就一直在僵持。
4、Lock
解决线程安全问题的方式三
public class LockTest{
public static void main(String[] args){
Window w1 = new Window();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
private int ticket = 100;
//创建Lock的实现类ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run(){
while(true){
try{
//调用锁定方法
lock.lock();
if(ticket > 0){
try{
Thread.sleep(100);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally{
//程序执行完要调用解锁方法
lock.unlock();
}
}
}
}
5、Lock和synchronized的区别
synchronized机制是在执行完相同大的同步代码后,自动释放同步监视器
lock是需要手动启动同步(lock()),关闭也需要(unlock())
6、同步线程练习题
**题:**银行有一个共用的账户。两个储户往这个账户里面存3000元,每次存1000,分三次存,没存一次打印一次余额。
代码:
public class BankDemo{
public static void main(String[] args){
Account acc = new Account(0);
Customer c1 = new Customer(acc);
Customer c2 = new Customer(acc);
c1.setName("甲");
c2.setName("乙");
}
}
//账户类
class Account{
private double balance;
public Account(double blance){
this.blance = blance;
}
//存钱方法
public synchronized void deposit(double amt){
if(amt > 0){
blance += amt;
try{
Thread.sleep(1000);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存钱了,账户余额为:" + blance);
}
}
}
//客户类
class Customer extends Thread{
private Account account;
public Customer(Account account){
this.account = account;
}
@Override
public void run(){
for(int i = 0; i< 3 ;i++){
account.deposit(1000);
}
}
}
7、线程的通信
wait()方法搭配 ------- notify()方法
阻塞 唤醒
wait() : 一旦执行此方法,这个线程就进入了阻塞状态,并释放同步监视器
notify() : 一旦执行此方法,就会唤醒wait的一个线程,如果多个线程被wait() ,优先级高的被唤醒
e
public void run(){
for(int i = 0; i< 3 ;i++){
account.deposit(1000);
}
}
}
**7、线程的通信**
wait()方法搭配 ------- notify()方法
阻塞 唤醒
wait() : 一旦执行此方法,这个线程就进入了阻塞状态,并释放同步监视器
notify() : 一旦执行此方法,就会唤醒wait的一个线程,如果多个线程被wait() ,优先级高的被唤醒