Java线程笔记1



一、几个概念

1.进程
    一个正在执行中的程序
    每个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
2.线程
    进程中一个独立的控制单元,线程在控制着进程的执行,线程才是进程中真正执行的部分
    一个进程中至少有一个线程,但是可以拥有多条执行路径,即多个线程
3.多线程
    多个线程并发执行
4.多线程存在的意义
    多线程可以让程序产生同时运行效果,提高程序执行效率。
    以虚拟机为例,Java vm 启动的时候会有一个进程java.exe,该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存在main方法中,该线程称之为主线程。jvm启动时不止一个线程,除了主线程之外,还有一个线程专门负责垃圾回收机制。如果只有主线程没有垃圾负责垃圾回收的线程,一旦垃圾过多内存用完,程序将无法执行下去,抑或主线程停下来去处理垃圾,原本正在执行的程序将处于等待状态,影响效率。而多线程可以使多段代码同时执行,可以一边执行程序一边处理垃圾。
二、创建线程的两种方式
第一种:继承Thread类
    1.定义类继承Thread
    2.覆写Thread类中的run()方法
    3.调用线程的start方法
程序示例
<span style="font-size:14px;"><span style="font-size:12px;">class SubThread extends Thread{
	/**
	 * 创建线程类的子类,重写run()方法
	 */
//	String name;
	public SubThread(String name){
//		this.name = name;
		super(name);
	}
	//覆写run方法
	public void run(){
		for(int i=1; i<=600; i++)
		//输出线程名以及运行的次数
		System.out.println(Thread.currentThread().getName()+"SubThread run----"+i);
	}
}
public class ThreadDemo1 {
	public static void main(String[] args) {
		SubThread st1 = new SubThread("one---");//创建一个线程
		st1.start();//开启线程
		SubThread st2 = new SubThread("two+++");//创建一个线程
		st2.start();//开启线程
		//主线程代码部分
		for(int i=1; i<=600; i++)
			System.out.println("hello world!--"+i);
	}

}</span></span>
public static Thread currentThread()   获取当前线程对象
public String getName()  获取线程名称
设置线程名称:setName方法或者构造函数
线程都有自己默认的名称,Thread-编号 该编号从0开始,如果不设置线程名就会采用这种默认的命名方式

运行截图
       每一次的运行结果都不一样



第二种:实现Runnable接口
   1.定义类实现Runnable接口
   2.覆盖Runnable接口中的run方法
   3.通过Thread类建立线程对象
   4.将Runnale接口的子类对象作为实际参数传递给Thread类的构造函数
   5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
程序示例
<span style="font-size:14px;"><span style="font-size:12px;">/*
 *  卖票的例子
 */
class Ticket implements Runnable{
	private static int ticket = 100; //总票数
	@Override
	public void run(){
		while(true){
			if(ticket>0){
				try {
					Thread.sleep(10);//线程休眠10毫秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}  							
				//打印当前线程名称以及售出的票
				System.out.println(Thread.currentThread().getName()+"---sale---   "+ticket--);							
			}					
		}						
	}
}
public  class TicketDemo {
	public static void main(String[] args) {
		//创建Runable子类的对象
		Ticket ticket = new Ticket();
		//创建Thread类,将Runnable接口的子类对象传给它
		Thread t1 = new Thread(ticket);
		Thread t2 = new Thread(ticket);
		//开启线程
		t1.start();	
		t2.start();
	}

}</span></span>

运行结果截图

这里涉及到的线程安全的问题将在后面解释

两种方式中都覆写了run方法了,为什么要覆盖run方法?
    Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,即run方法用于存储线程要运行的代码。复写run方法的目的是将自定义代码存储在run方法,让线程运行。
既然run方法用于存储线程要运行的代码,为什么要不直接调用run方法而是调用start方法?
    start方法有两个作用:启动线程,调用run()方法
调用start方法与run方法的区别
    start用于开启线程并执行该线程的方法。注意:重复调用start方法程序会抛出异常
    调用run与一般的对象调用没有区别,线程创建了,并没有运行,依然只有一个线程
继承方式和实现方式的区别
    继承Thread:线程代码存放在Thread子类的run方法中
    实现Runnable:线程代码存放在Runnable接口子类run方法中
实现方式方式的好处:避免了单继承的局限行,定义线程时,建议使用这种方式
两个程序每次运行的结果都不一样,这又是为什么呢?
    这和CPU的执行原理有关。  
    因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行行为看成是在互相抢夺CPU的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,由CPU决定。

三、线程的几种状态
    1.被创建:通过new关键字创建了Thread类(或其子类)的对象,等待被启动
    2.运行状态:具备运行资格和执行权
    3.临时阻塞状态:具备运行资格但是没有执行权
    4.冻结状态:线程因为调用sleep方法或者wait方法等进入阻塞,放弃CPU的执行权
    5.消亡状态:run方法结束或者线程调用了stop方法
线程状态图


四、多线程安全问题
    卖票的程序中,票据出现了0号票,现实生活中是不应该出现0号票,这里就涉及到了线程安全的问题
产生的原因:
    当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
     对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与执行。
Java中提供的解决方法----同步
    1.同步代码块
       用法:
        synchronized(对象){
            //需要被同步的代码
       }
      同步代码块解决线程安全的原因在于这个对象,好比一把锁,每个线程要执行被同步的代码都必须获取此对象的锁,如果这个对象的锁被其他线程获取就必须等待其他对象释放这个锁。
加入了同步的卖票的例子
<span style="font-size:14px;"><span style="font-size:12px;">/*
 *  卖票的例子,已加入同步
 */
class Ticket2 implements Runnable{
	private static int ticket = 100;   //总票数
	Object obj = new Object();  //用于同步的对象
	@Override
	public void run(){
		while(true){
			synchronized(obj){   //加入同步
    				if(ticket>0){
					try {
						Thread.sleep(20);//线程休眠10毫秒
					} catch (InterruptedException e) {
						e.printStackTrace();
					}  							
					//打印当前线程名称以及售出的票
					System.out.println(Thread.currentThread().getName()+"---sale---   "+ticket--);							
				}					
			}
		}
	}
}</span></span>
程序运行截图

可以看到没有0号票了
2.同步函数
    用法:
        在函数返回值类型前加上synchronized关键字
    同步函数用的是哪一个锁?
        函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this
    如果同步函数被静态修饰后,使用的锁又是什么呢?
        静态函数使用的不是this锁,因为在静态方法中也不可以定义this。
        静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码对象  类名.class  ,该对象的类型是Class
        静态的同步方法,使用的锁是该方法所在类的字节码对象 ,即类名.class
示例:
<span style="font-size:14px;"><span style="font-size:12px;">/*
 * 懒汉式单例设计模式的安全问题
 */
public class Single {
	private static Single s = null;
	private Single(){}
	public static Single getInstance(){
	    if(s == null)
		synchronized(Single.class){  //加入同步,静态函数中使用的锁只能是类对应的字节码对象
		    if(s == null)
	                s = new Single();
		}
	    return s;
		
	}
}</span></span>
3.同步的前提:
    1).必须要有两个或者两个以上的线程
    2).必须是多个线程使用同一个锁
4.如何找到多线程中的安全问题
    1).明确哪些代码是多线程运行代码
    2).明确共享数据
    3).明确多线程运行代码中哪些语句是操作共享数据的
5.同步的好处与弊端
    好处:解决了多线程的安全问题
    弊端:多个线程都需要判断锁,较为消耗资源
五、死锁问题
死锁产生的原因:   
    同步中嵌套同步

死锁的例子
<span style="font-size:14px;"><span style="font-size:12px;">
/*
 * 死锁的例子
 */
class MyLock {
	static Object lockA = new Object();
	static Object lockB = new Object();
}
class Test implements Runnable{
	private boolean flag;
	public Test(boolean flag){
		this.flag = flag;
	}
	@Override
	public void run() {
		if(flag){
		    synchronized(MyLock.lockA){   //上lockA锁
			System.out.println("if lockA");
			synchronized(MyLock.lockB){   //上lockB锁
			    System.out.println("if lockB");
			}	
		    }	
		}else{
		    synchronized(MyLock.lockB){  //上lockB锁
			System.out.println("else lockB");
			synchronized(MyLock.lockA){  //上lockA锁
			    System.out.println("else lockA");
			}	
		    }	
		}
	}
}
public class DeadLockDemo {

	public static void main(String[] args) {
		Thread t1 = new Thread(new Test(true));
		Thread t2 = new Thread(new Test(false));
		t1.start();
		t2.start();
	}

}
</span></span>

程序运行结果截图

出现死锁,程序无法继续执行下去


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值