Day 06 多线程与线程安全

Day06 多线程与线程安全

一、 多线程

1.1多线程原理

每一个线程有一个自己的栈内存空间,程序运行时,JVM开启main线程,如果在main线程中调用了启动线程的satrt()方法,就会开辟新线程。

在java中,每次程序运行至少启动2 个线程 。一个是main线程 ,一个是垃圾收集线程 。因为每当使用java命令执行一个 类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启 动了一个进程

1.2 Thread类

构造方法:

  • public Thread()分配新线程对象
  • public Thread(String name)分配指定名字新线程
  • public Thread(Runnable target)分配带有指定目标的新线程
  • public Thread(Runnable target,String name)分配带有指定目标的新线程并赋名

常用方法:

  • public String getName()获取当前线程的名称
  • public void start()使JVM调用此线程的run方法
  • public void run()此线程要执行的任务
  • public static void sleep(long millsecond)使当前正在执行的线程以指定的方式暂停数秒
  • public static Thread currentThread()返回当前正在执行的线程对象的引用

1.3 创建线程的第二种方式

​ 第一种方式新建一个类继承于Thread类,重写其中的run()方法,再建一个此类的对象,调用start()方法来开启线程。

​ 第二种方式通过实现Runnable接口,重写Runnable来定义一个线程执行体,再通过Thread来实例化这个内容的对象,通过线程对象的start()方法来启动线程。它又可以分为两种

run方法()是多线程程序的一个执行目标,所有多线程程序都在run方法里。

  • 创建一个实现Runnable的类,实例化此类,再用Thred创建线程对象,执行线程

    public class Day06 {
    
    	public static void main(String[] args) {
    		MyThread mt = new MyThread();
    		Thread thread = new Thread(mt,"新线程");
    		thread.start();
    		for(int i =10;i<100;i+=10) {
    			System.out.println("."+i);
    		}
    	}
    }
    class MyThread implements Runnable{
    
    	@Override
    	public void run() {
    		for(int i = 0;i<10;i++) {
    			System.out.println(","+i);
    		}
    	}
    }
    
  • 采用匿名内部类的方式

    public class Day06 {
    	public static void main(String[] args) {
    		Thread thread = new Thread(new Runnable() {		
    			@Override
    			public void run() {			
    				for(int i = 0;i<10;i++) {
    					System.out.println(","+i);
    				}
    			}
    		},"新线程");
    		thread.start();
    		for(int i =10;i<100;i+=10) {
    			System.out.println("."+i);
    		}
    	}
    }
    

1.4 Thread和Runnable的区别

  • Runnable接口可以多实现,避免了Thread单继承的局限性
  • 对于多个相同线程执行更方便
  • 线程池中只能放入Runnable或Callable线程不能直接放入继承Thread的类
  • 增加程序的健壮性,实现解耦操作,代码可以被多线程共享,代码与线程独立

二、 线程安全

2.1 线程安全问题

​ 多个线程共享一个数据的时候,如果只对数据进行读的操作,数据的安全性较高,如果还带有写入的操作,具有一定的危险性。

public class Day06 {
	public static void main(String[] args) {
		Ticket window = new Ticket();
		Thread windows1 = new Thread(window,"窗口1");
		Thread windows2 = new Thread(window,"窗口2");
		windows1.start();
		windows2.start();
  }
}
class Ticket implements Runnable{

	private int num = 20;
	public void run() {
		while(num>0){
			System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
	}	
}

在这里插入图片描述
这是因为在窗口1卖出的瞬间,还没有执行num–,窗口2就获取了此数。

2.2 线程同步

为了保证多线程在对同一资源进行写操作时的安全性,需要线程同步。有三种实现方式:

关键字:synchronized,用于某个方法的某个区块中,表示对该区块的资源实行互斥访问-----在一个瞬间,只有一个线程能访问

  • 同步代码块

  • synchronized(同步锁){
    同步操作代码
    }
    

同步锁相当于在一个对象上标记一个锁,锁对象可以是任意类型,多个线程的锁是同一个。在任何时刻,同步锁只能属于多个线程的其中一个

改造后的Ticket类,以Object 类的obj对象为锁:

class Ticket implements Runnable{
	private Object obj = new Object();
	private int num = 20;
	public void run() {
		while(num>0){
			synchronized(obj) {
	System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		}
	}	
}
  • 同步方法

    • 格式:

      public synchronized void method(){
      	可能会出现线程安全问题的代码	
      }
      

      例子用同步方法修改后:

      这种方法的同步锁:

      • 对于非static方法,同步锁是this
      • 对于static方法,是当前方法所在类的字节码对象(类名.class)
    class Ticket implements Runnable{
    	private int num = 20;
    	public synchronized void run() {
    		while(num>0){	
    			System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--);
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();			
    			}
    		}
    		}
    	}
    
  • Lock锁

    java.tuilconcurrent.locks.Lock,它除了包含上面两种方法的功能,还有更强大的功能。将加锁解锁方法化:

    • public void lock()

    • public void unlock()

      构造方式:

      Lock lock = new ReentrantLock();

      class Ticket implements Runnable{
      	private int num = 100;
      	 Lock lock = new ReentrantLock();
      	public  void run() {
      		while(num>0){
      			lock.lock();			System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--);
      			try {
      				Thread.sleep(100);
      			} catch (InterruptedException e) {
      				// TODO Auto-generated catch block
      				e.printStackTrace();
      			}lock.unlock();
      		}
      		}
      	}	
      

三、 线程状态

  • New:刚被创建,还没有启动(没有被start()方法调用)
  • Runnable:可以在JVM里运行的状态,但是不一定正在运行(等待CPU执行)
  • Blocked:锁阻塞,由于线程没有拿到对象锁(被其他线程占用),而停滞的状态,拿到锁后会转变为Runnable状态。调用wait()以及wait(millsecond)都会使线程进入等待状态,并且失去锁。
  • TimedWaiting:等待唤醒状态,这是由于调用了含有超时参数的方法,如:Thread.sleep(5000),Object.wait()方法等。等到超时期满后或者在超时期前收到唤醒通知时,会转变为Runnable状态
  • Waiting:无限等待状态,等待另一个线程执行唤醒动作的状态。原因是调用了Object中的wait方法,当另一个线程调用notify方法或者notifyAll方法才能唤醒。唤醒后,如果拿到对象锁,变为Runnable,拿不到转为Blocked状态。
  • Terminate:run方法结束的状态,结束的原因又可分为
    • 正常结束
    • 由于出现异常,但是没有捕获而导致程序结束

无限等待状态举例:

public class Day06 {
	public static Object obj = new Object();
	public static Object obj2 = new Object();
	public static void main(String[] args) {
		new Thread(new Runnable() {			
			@Override
			public void run() {
				while(true) {					System.out.println(Thread.currentThread().getName()+"试图拿锁");
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				synchronized(obj) {
					try {					System.out.println(Thread.currentThread().getName()+"抢到了锁")						System.out.println(Thread.currentThread().getName()+"准备进入无限等待,失去同步锁!");
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}					System.out.println(Thread.currentThread().getName()+"唤醒成功!");					System.out.println("_________________________________________");
				}
			}
		}			
		},"线程一").start();		
			new Thread(new Runnable() {			
			@Override
			public void run() {
				while(true) {					System.out.println(Thread.currentThread().getName()+"试图拿锁");
					try {
						Thread.sleep(3000);
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				synchronized(obj) {
					try {
						System.out.println(Thread.currentThread().getName()+"抢到了锁");
						System.out.println(Thread.currentThread().getName()+"准备进入无限等待,失去同步锁!");
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"唤醒成功!");					System.out.println("_________________________________________");
				}
			}
		}
		},"线程三").start();
		
		new Thread(new Runnable() {
					@Override
					public void run() {
					while(true) {					System.out.println(Thread.currentThread().getName()+"试图拿锁");
						try {
							Thread.sleep(3000);
							
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						synchronized(obj) {							System.out.println(Thread.currentThread().getName()+"抢到了锁");
							System.out.println("准备唤醒其他线程");
							obj.notify();
						}
						
					}
				}
				},"唤醒线程:线程二").start();
	}
}

notify改为notifyall之后,两个线程都会被唤醒此时出现两种情况

  • 被唤醒的线程其中一个抢到对象锁,转为Runnable,另一个转为Blocked
  • 唤醒线程抢到锁,剩下两个均为Blocked状态
  • wating状态不是线程的操作,它体现了多个线程间的通信,存在争取锁与协作完成任务两种关系

    在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值