Java基础——多线程

一、线程的生命周期
在这里插入图片描述
 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能
 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
 死亡:线程完成了它的全部工作或线程被提前强制性的中止

二、创建线程的方式
 1、继承于Thread类

class PrintNum extends Thread{
	public void run(){
		//子线程执行的代码
		for(int i = 1;i <= 100;i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
	public PrintNum(String name){
		super(name);
	}
}

public class TestThread {
	public static void main(String[] args) {
		PrintNum p1 = new PrintNum("线程1");
		PrintNum p2 = new PrintNum("线程2");//开启几个线程就需要几个对象
		p1.setPriority(Thread.MAX_PRIORITY);//10
		p2.setPriority(Thread.MIN_PRIORITY);//1
		p1.start();
		p2.start();
	}
}

//匿名对象
new Thread(){
	public void run() {
		for(int i = 0;i <= 100;i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
}.start();

 2、实现Runnable接口

class SubThread implements Runnable{
	public void run(){
		//子线程执行的代码
		for(int i = 1;i <= 100;i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}			
	}
}

public class TestThread{
	public static void main(String[] args){
		SubThread s = new SubThread();//只需要一个实例对象
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		
		t1.setName("线程1");
		t2.setName("线程2");
		
		t1.start();
		t2.start();
	}
}

//匿名子线程
new Thread(new Runnable(){
	@Override
	public void run() {
		System.out.println("分线程执行的操作");
	}
	
}).start();

Tip
  1️⃣一个线程只能调用一次start()方法,但可以创建多个子线程类的对象,每个对象分别调用start()方法是可以的
  2️⃣多线程可看作是多个子线程的run()方法和main()方法交互执行,争夺cpu资源的过程,子线程对象不能直接调用run()方法,若直接调用run()方法的话相当于没有启动子线程,此时的run()方法和普通方法没什么两样,程序会按顺序执行完run()方法再执行其他语句,只有调用了子线程对象的start()方法才能启动该子线程
 3、实现Callable接口

public class CallerTask implements Callable<String>{
	@Override
	public String call() throws Exception{
		return "Hello future!";
	}
	public static void main(String[] args){
		FutureTask<String> task = new FutureTask<>(new CallerTask());
		new Thread(task).start();
		try{
			String result = task.get();
			System.out.println(result);
		}catch(ExecutionException | InterruptedException e){
			e.printStackTrace();
		}
	}
}

  这种方式可以获取线程执行的结果。

 4、两种方式的对比
  联系:class Thread implements Runnable,Thread类本身实现了Runnable接口
  实现的方式较好:
   ①解决了单继承的局限性
   ②如果多个线程需要共享数据的话,建议使用实现的方式(继承的方式因为要创建多个对象来启动线程,就必须将共享数据声明为static的),同时,共享数据所在的类可以作为Runnable接口的实现类。
 当然,创建多线程可以同时继承Thread和实现Runnable,看似有些不伦不类,此时无需再专门借助Thread类的对象的start()方法来启动线程,而直接使用子线程类对象的start()方法即可,另外在有共享数据时是可能发生线程安全问题的,这时需考虑同步:

public class ThreadTest {
	public static void main(String[] args) {

		SubThread t1 = new SubThread();
		SubThread t2 = new SubThread();
		SubThread t3 = new SubThread();

		t1.setName("线程1");
		t2.setName("线程2");
		t3.setName("线程3");

		t1.start();
		t2.start();
		t3.start();
	}
}

//子线程类即继承Thread又实现Runnable
class SubThread extends Thread implements Runnable {

	private static int ticket = 100;

	@Override
	public void run() {//该run方法是对Thread类中run方法的重写
		while (ticket > 0)
			synchronized (SubThread.class) {
				if (ticket > 0)
					System.out.println(Thread.currentThread().getName() + ":" + ticket--);
			}
	}
}

三、多线程常用的方法
 1、start():开启线程;执行相应的run()
 2、run():分线程要执行的逻辑声明在run()中
 3、currentThread():获取当前正在执行的线程
 4、getName():获取线程的名字
 5、setName():设置线程的名字
 6、yield():释放线程对当前cpu的占用,但不是放锁
 7、join():在线程a中调用b.join()表示当前线程a进入阻塞状态,线程b开始执行,直到线程b执行结束,线程a才继续执行
 8、sleep(long millitimes):显示的使当前线程睡眠指定的毫秒数,这个过程中,当前线程处于阻塞状态,不会释放锁
 9、isAlive():当前线程是否还存活
 10、线程的优先级:getPriority() / setPriority(int i),值越大优先级越高,提供的值有MIN_PRIORITY(值为1)、NORM_PRIORITY(值为5)、MAX_PRIORITY(值为10),同优先级线程组成先进先出队列,高优先级优先调度,使用时间片策略;线程创建时继承父线程的优先级。优先级设置的越高,该线程越容易抢到CPU资源,但并不意味着一定先执行。
 11、wait():会释放锁

四、线程的同步
 如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中的一个线程操作共享数据,在还未操作完成时,另外的线程就参与进来,导致对共享数据的操作出现问题。
 解决方式:要求一个线程操作共享数据时,只有当其完成操作,其它线程才有机会操作共享数据,也就是说共享数据最多只能有一个线程在操作
 线程同步的方式:
  1、同步代码块

synchronized(同步监视器){
	//操作共享数据的代码
}

  单例模式线程安全问题的解决:

class Singleton {
	private Singleton() {
		// 私有化构造器
	}
	private static Singleton instance = null;
	public static Singleton getInstance() {
		if (instance == null) {// 提高了效率,外面的进程不用再等待了
			synchronized (Singleton.class) {// 当前类充当锁
				if (instance == null) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

  
   1️⃣同步监视器:俗称锁,任何一个类的对象都可以充当锁。要想保证线程的安全,必须要求所有的线程共用同一把锁;
   2️⃣使用实现Runnable接口的方式创建多线程的话,同步代码块中的锁,可以考虑使用this,因为这时可以只创建一个线程对象。如果使用继承Thread类的方式,则慎用this;
   3️⃣明确哪部分是操作共享数据的代码,理论上说同步代码块越小越好
  2、同步方法:将操作共享数据的方法声明为synchronized

public synchronized void show(){ 
	//操作共享数据的代码
}

  
   1️⃣对于非静态的方法,同步方法的锁默认为this,即当前对象,因此需要保证对象时单例的。如果使用继承的方式实现多线程的话,慎用同步方法;
   2️⃣对于静态的方法,如果使用同步,默认的锁为当前类本身
   3️⃣线程的死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁;
  3、在线程安全控制中,通常使用ReentrantLock(可重入锁)。使用该Lock对象可以显示加锁、释放锁:

class C{
	//锁对象:注意是static final的,保证唯一
	private static final ReentrantLock lock = new ReentrantLock();
	......
	//保证线程安全方法
	public void method() {
		//上锁
		lock.lock();
		try {
			//保证线程安全操作代码
		} catch() {
		
		} finally {
			lock.unlock();//释放锁
		}
	}
}

   使用Lock对象进行同步时,注意把释放锁的操作放在finally中,保证一定能够执行。使用锁和使用同步类似,只是使用Lock是显示的调用lock方法来完成同步。而使用synchronized时系统会隐式使用当前对象作为同步监视器,同样都是“加锁->访问->释放锁”的操作模式,都可以保证只有一个线程在操作资源。同步方法和同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且获得多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有资源。Lock提供了同步方法和同步代码块没有的其他功能,包括用于非块结构的tryLock方法,以及试图获取可中断锁lockInterruptibly()方法,还有获取超时失效锁的tryLock(long, timeUnit)方法。
   ReentrantLock具有重入性,也就是说线程可以对它已经加锁的ReentrantLock再次加锁,ReentrantLock对象会维持一个计数器来追踪lock方法的嵌套调用,线程在每次调用lock()加锁后,必须显示的调用unlock()来释放锁,所以一段被保护的代码可以调用另一个被相同锁保护的方法。

五、同步示例
 1、两人同向一个账户存钱,每次存1000,共存三次
  ①采用继承的方式

public class Bank {
	public static void main(String args[]) {
		Account acct = new Account();// 一个账户
		Customer cust1 = new Customer();
		Customer cust2 = new Customer();
		cust1.setAccount(acct);
		cust2.setAccount(acct);// 两人共有
		cust1.start();// 开启线程开始存钱
		cust2.start();
	}
}

// 账户
class Account {
	private double balance;// 两人共享同一个账户,实际上就是共享余额这个数据
	public double getBalance() {
		return this.balance;
	}
	public synchronized void depsit(double amt) {// 对余额的操作要同步
		this.balance += amt;
		System.out.println(Thread.currentThread().getName() + ":" + balance);
	}
}

//采用继承的方式
class Customer extends Thread {
	private Account account;
	public void setAccount(Account account) {
		this.account = account;
	}

	//存钱
	public void run() {
		for (int i = 0; i < 3; i++) {
			account.depsit(1000);
		}
	}
}

  ②采用实现的方式

// 实现的方式
public class Bank {
	public static void main(String[] args) {
		Account acct = new Account();
		Customer cust = new Customer(acct);
		Thread t1 = new Thread(cust);
		Thread t2 = new Thread(cust);
		t1.start();
		t2.start();
	}
}

class Account {
	private double balance;
	public synchronized void deposit(double amt) {
		this.balance += amt;
		System.out.println(Thread.currentThread().getName() + ":" + balance);
	}
}

class Customer implements Runnable {
	private Account account;
	public Customer(Account acct) {
		this.account = acct;
	}
	public void run() {
		for (int i = 0; i < 3; i++) {
			account.deposit(1000);
		}
	}
}

 2、生产者和消费者模型

public class ProducerCusumer {
	public static void main(String[] args) {
		Production pro = new Production();// 产品
		Producer p = new Producer(pro);// 生产者
		Consumer c = new Consumer(pro);// 消费者
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);
		t1.setName("生产者");
		t2.setName("消费");
		t1.start();
		t2.start();
	}
}

class Producer implements Runnable {
	private Production production;

	public Producer(Production pro) {
		this.production = pro;
	}

	public void run() {
		while (true) {
			production.producePro();
		}
	}
}

class Consumer implements Runnable {
	private Production production;

	public Consumer(Production pro) {
		this.production = pro;
	}

	public void run() {
		while (true) {
			production.consumePro();
		}
	}
}

class Production {
	private int num;

	public synchronized void producePro() {
		if (num < 20) {
			try {
				Thread.currentThread().sleep(100);
			} catch (Exception e) {
				e.printStackTrace();
			}
			num++;
			notifyAll();// 只要生产了一个就可以唤醒消费者了
			System.out.println(Thread.currentThread().getName() + "目前生产是的第" + num + "个商品。");
		} else {
			try {
				wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public synchronized void consumePro() {
		if (num > 0) {
			System.out.println(Thread.currentThread().getName() + "目前消费是的第" + num + "个商品。");
			try {
				Thread.currentThread().sleep(100);
			} catch (Exception e) {
				e.printStackTrace();
			}
			num--;
			notifyAll();// 只要消费了一个就可以唤醒生产者了
		} else {
			try {
				wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值