温故而知新(5)-Java基础之线程的使用

前言

一个应用程序可能有多个进程,一个进程可能有多个线程,多线程并非同时进行,而是充分利用cpu的资源,因为其切换时间很短,所以直观上认为是并行的。

创建线程的两种方式

不管是哪种方式,都需要重写run方法,所有的业务处理都在这里

继承Thread

自定义线程类:

public class MyThread extends Thread {
	//定义指定线程名称的构造方法    
	public MyThread(String name) {    
		super(name); //调用父类的String参数的构造方法,指定线程的名称           
	}    
    //重写run方法,完成该线程执行的逻辑       
    @Override    
    public void run() {    
	  for (int i = 0; i < 10; i++) {        
	  System.out.println(getName()+":正在执行!"+i);            
	}        
  }    
}

测试类

public class Demo01 {
public static void main(String[] args) {    
	MyThread mt = new MyThread("新的线程!"); //创建自定义线程对象              
	mt.start();  //开启新线程        
	for (int i = 0; i < 10; i++) {//在主方法中执行for循环        
	System.out.println("main线程!"+i);            
	}        
  }    
}

上面可以发现,会分别打印main线程!和新的线程!他们是分开在不同线程中执行的。

实现Runnable

自定义线程类:

public class MyRunnable implements Runnable{
  @Override    
  public void run() {    
	for (int i = 0; i < 20; i++) {        
	  System.out.println(Thread.currentThread().getName()+" "+i);            
	}        
  }    
}

测试类:

public class Demo {
    public static void main(String[] args) {
        //创建自定义类对象  线程任务对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t = new Thread(mr, "新的线程");
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程" + i);
        }
    }
}

跟继承Thread基本一致,只是写法不同,其中也是有一些区别的。

继承Thread 和实现Runnable的区别

应该说实现Runnable更加有优势,具体大概如下:

  1. 实现Runnable,可以避免java中的单继承的局限性
  2. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
  3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类

匿名内部线程

有时候只需要新起一个线程,处理一些事情,不需要被调用,可以直接新建内部线程,方便快捷。

 public class NoNameInnerClassThread {
   public static void main(String[] args) {            
     new Thread() {
            public void run() {
                for(int i=0;i<10;i++) {
                    System.out.println("aaaaaaa");
                }
            }
        }.start();
     new Thread(new Runnable() {
        public void run() {
            for(int i=0;i<10;i++) {
                System.out.println("bbbbbbb");
            }
        }
     }).start();
   }
 }

线程安全

多线程面临的一个问题就是,多个线程,同时共享一个数据的情况下,并且同时对其存在写的操作,就存在线程一取到数据,还没来得及更新写,线程二就取到了老数据,并对其进行处理,最典型的就是卖票,一百个票,线程一拿到的总数是100,并准备卖,还没写入数据库100-1.这时候线程二又拿到100,并减1写入数据库,相当于第100张票被卖了两次,这肯定是不行的。

卖票代码:

public class Ticket implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while (true) {
            if (ticket > 0) {//有票 可以卖
                //出票操作
                //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //获取当前线程对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖:" + ticket--);
            }
        }
    }
}

执行代码

public class TestTicket1 {
	public static void main(String[] args) {
		// 创建线程任务对象
		Ticket ticket = new Ticket();
		// 创建三个窗口对象
		Thread t1 = new Thread(ticket, "窗口1");
		Thread t2 = new Thread(ticket, "窗口2");
		Thread t3 = new Thread(ticket, "窗口3");

		// 同时卖票
		t1.start();
		t2.start();
		t3.start();
	}
}

解决线程安全的三种方法

线程同步

主要是使用synchronized关键字,当一个线程正在对其进行操作的时候,其他线程无法进入。最多允许一个线程拥有同步锁,修改后的代码如下

public class Ticket2 implements Runnable{
    private int ticket = 100;
    
    Object lock = new Object();
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while (true) {
        	synchronized (lock) {
		        if (ticket > 0) {//有票 可以卖
		            //出票操作
		            //使用sleep模拟一下出票时间
		            try {
		                Thread.sleep(100);
		            } catch (InterruptedException e) {
		                // TODO Auto‐generated catch block
		                e.printStackTrace();
		            }
		            //获取当前线程对象的名字
		            String name = Thread.currentThread().getName();
		            System.out.println(name + "正在卖:" + ticket--);
		        }
        	}
        }
    }
}

锁对象 可以是任意类型,String lock = new String();也可以

方法同步

synchronized 修饰的方法保证A线程执行该方法的时候,其他线程只能在方法外等着。
改造后的代码如下:

public class Ticket3 implements Runnable {
	private int ticket = 100;
	@Override
	public void run() {
		// 每个窗口卖票的操作
		// 窗口 永远开启
		while (true) {
			sellTicket();
		}
	}

	public synchronized void sellTicket() {
		if (ticket > 0) {// 有票 可以卖
			// 出票操作
			// 使用sleep模拟一下出票时间
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto‐generated catch block
				e.printStackTrace();
			}
			// 获取当前线程对象的名字
			String name = Thread.currentThread().getName();
			System.out.println(name + "正在卖:" + ticket--);
		}
	}
}

Lock锁

Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。其使用方法更加方便,在需要加锁的代码前加上锁,直到不需要解锁的地方解锁,代码如下:

public class Ticket4 implements Runnable {
	private int ticket = 100;

	Lock lock = new ReentrantLock();

	@Override
	public void run() {
		// 每个窗口卖票的操作
		// 窗口 永远开启
		while (true) {
			lock.lock();
			if (ticket > 0) {// 有票 可以卖
				// 出票操作
				// 使用sleep模拟一下出票时间
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto‐generated catch block
					e.printStackTrace();
				}
				// 获取当前线程对象的名字
				String name = Thread.currentThread().getName();
				System.out.println(name + "正在卖:" + ticket--);
			}
			lock.unlock();
		}
	}
}

线程状态

借用网上的一张图:
在这里插入图片描述

项目Value
New线程对象被创建后,就进入了新建状态
Runnable就绪状态,代码调用.start()方法即进入。
Running运行状态,线程获得cpu使用权,进入运行状态
Blocked阻塞状态,变为阻塞状态有几种情况,1、线程调用wait方法进入。2、线程获取synchronized同步锁失败进入,因为其他线程在使用同步锁。3、线程调用sleep()或join()或发出了I/O请求时进入。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
Dead死亡状态,线程执行完毕或异常退出run()方法,生命周期结束。

线程间通信

上面说到wait/notify方法,一个是让线程等待,一个是唤醒正在沉睡的线程,为什么会有这玩意?因为线程间需要通信,比如吃米饭的和做米饭的,分别是两个动作,分别在两个线程中执行,即生产者和消费者之间的关系,其二者需要共享一个变量,判断是都有米饭,如果有,则唤醒吃米饭线程来吃,做米饭线程等待,如果没有,则唤醒做米饭线程来做,吃米饭线程等待。

线程池

新建一个线程,执行完以后销毁,这样没问题,但是频繁的创建线程很消耗资源,所以出现了线程池,需要线程的时候,去线程池拿,用完再放回去,避免频繁创建新的线程。

创建线程池:

ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象

获取线程池中的线程,并执行run方法

 service.submit(r);//获取线程并执run,submit都执行了

如果线程池不需要了可以用service.shutdown();关闭

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值