多线程之基本使用

多线程的基本使用,及其方法,但关于多线程的并不止这些,关于多线程以后会持续更新!!!


目录

1. 多线程的创建

1.1 继承 Thread 类

1.2 实现Runnable接口

1.3 实现Callable接口

1.4 线程池

2. Thead类中的方法

3. 买票问题

4. 线程安全问题

5. 死锁问题

6. 解决死锁


1. 多线程的创建

1.1 继承 Thread 类

(1)创建一个继承与Thread的子类

(2)重写Thread的run方法 -- >将创建此线程执行的操作声明在方法中

(3)创建Thread子类的对象

(4)通过对象调用start()方法

//1.创建一个继承与Thread的子类
class MyThread extends Thread{
	//2.重写Thread的run方法
	
	@Override
	public void run() {
		for (int i = 0; i <100 ; i++) {
			if (i%2==0){
				System.out.println(i);
			}
		}
	}
}

public static void main(String[] args) {
	//3.创建Thread子类的对象
	MyThread m = new MyThread();
	//4.通过对象调用start()方法
	m.start();//启动当前线程,调用当前线程的方法
	//不能直接对象.run方法调用启动线程
	
	//再启动一个线程,输出偶数,不可以还让已经start的线程执行
	MyThread m1 = new MyThread();
	//m1.start();
	
	
	//创建Thread类匿名子类的方式
	new Thread(){
		@Override
		public void run() {
			for (int i = 0; i <100 ; i++) {
				if (i%2!=0){
					System.out.println(Thread.currentThread().getName() +" "+ i);
				}
			}
		}
	}.start();
	
    }

1.2 实现Runnable接口

(1)创建一个实现了Runnable接口的类

(2)实现了Runnable中的抽象方法run()

(3)创建类的对象

(4)将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

(5)通过Thread类的对象调用start()

//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
	//2.实现了Runnable中的抽象方法run()
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"  "+i+" ");
		}
	}
}

public static void main(String[] args) {
	// 3.创建类的对象
	MThread m = new MThread();
	//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
	Thread t1 = new Thread(m);
	//在启动一个线程
	t1.setName("线程1:");
	Thread t2 = new Thread(m);
	// 5.通过Thread类的对象调用start() ①启动线程 ②调用当前线程的run()方法
	t2.setName("线程2:");
	t1.start();
	t2.start();
    }

1.3 实现Callable接口

实现Callable接口,JDK5.0新增

实现Callable接口的方式创建多线程比实现Runnable接口实现多线程更强大?

  • call有返回值
  • call可以抛出异常
class NnmThead implements Callable{
	//2.实现call方法,将此线程的操作写入call'方法中
	public Object call() throws Exception {
		int sum=0;
		for (int i = 1; i <= 100; i++) {
			if (i%2==0){
				System.out.println(i);
				sum +=i;
			}
		}
		//此方法有返回值
		return sum;
	}
}

public static void main(String[] args) {
	//3.创建Callable接口的实现类对象
	NnmThead n = new NnmThead();
	//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
	FutureTask f1 = new FutureTask(n);
	//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,调用start方法,启动线程
	new Thread(f1).start();
	try {
		//6.获取Callable方法中call的返回值
		//get方法的返回值即为FutureTask构造器参数Callable实现类重写call()的返回值
		Object sum = f1.get();
		System.out.println("总和为:"+sum);
	} catch (InterruptedException e) {
		e.printStackTrace();
	} catch (ExecutionException e) {
		e.printStackTrace();
	}
    }

1.4 线程池

线程池的好处:

(1)提高响应速度

(2)降低资源消耗

(3)偏于线程管理

//3.提供实现Runnable接口 或 Callable接口的方法
class NumThead implements Runnable{
	@Override
	public void run() {
		for (int i = 1 ;i <= 100; i++) {
			if (i%2 == 0){
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}
	
	public static void main(String[] args) {
		//1.提供指定线程数量的线程池
		ExecutorService service = Executors.newFixedThreadPool(10);
		
		//将接口强转为类
		//ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
		//service1.setCorePoolSize(15);
		//可以设置线程池的属性
		
		//2.执行指定线程的操作
		service.execute(new NumThead());//适合适用于Runnable
		//service.submit();//适合适用于Callable
		
		//4.关闭连接池
		service.shutdown();
    }

2. Thead类中的方法

Thead类中的方法:

(1)start():启动当前线程,调用当前线程的run方法

(2)run():将创建线程的操作(业务)写在此方法中

(3)currentThread():静态方法,返回执行当前代码的线程

(4)getName():获取线程名

(5)setName():设置线程名,不设置会有默认名

(6)yield():释放cup的当前执行权

(7)join():进入指定线程,在线程A中调用线程B的join(),线程A就会进入阻塞状态

(8)stop():已过时,当执行此方法,强制结束此方法

(9)sleep():进入睡眠状态,单位为ms毫秒,在毫秒时间内,当前线程是阻塞状态

(10)isAlive:判断线程是否还存活

线程的优先级:

  • MAX_PRIORITY
  • MIN_PRIORITY
  • NORM_PRIORITY(默认优先级)

设置线程的优先级:

  • setPriority:设置优先级
  • getPriority:获取优先级
  • 高优先级的线程【大概率】抢占低优先级的线程执行权,但不意味着高优先级的线程先执行

相关方法演示:

@Override
public void run() {
	for (int i = 0; i < 100; i++) {
		if (i%2==0){
			System.out.println(Thread.currentThread().getName() + ": "+i+" "+ Thread.currentThread().getPriority()    );
			//进入睡眠状态,单位为ms毫秒
			//                try {
			//                    sleep(10);
			//                } catch (InterruptedException e) {
			//                    e.printStackTrace();
			//                }
		}
		//释放当前cup的执行权
		if (i%20 ==0){
			yield();
		}
	}
}


public static void main(String[] args) {
	//通过构造器给线程命名
	TheadMethod t = new TheadMethod("分线程");
	//设置分线程的优先级
	t.setPriority(Thread.MAX_PRIORITY);
	//setName():设置线程名,不设置会有默认名
	//t.setName("线程1:");
	t.start();
	//给主线程设置名
	Thread.currentThread().setName("主线程:");
	Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
	for (int i = 0; i < 100; i++) {
		if (i%2==0){
			System.out.println(Thread.currentThread().getName() + ": "+i+" " + Thread.currentThread().getPriority());
		}
		//7.join():进入指定线程,在线程A中调用线程B的join(),线程A就会进入阻塞状态
		//            if (i ==20){
		//                try {
		//                    //进入
		//                    t.join();
		//                } catch (InterruptedException e) {
		//                    e.printStackTrace();
		//                }
		//            }
	}
	//判断指定线程是否还存活
	//System.out.println(t.isAlive());
    }

3. 买票问题

使用实现Runnable接口方式

但此方法会出现线程安全问题--->出现错票,重票等

解决方法:给线程加入锁,即使a线程被阻塞,其他线程也不可以执行

这里使用synchronized同步代码块解决

public class WindowsTestExer1 {
	
	public static void main(String[] args) {
		Window1 w = new Window1();
		
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("窗口1-");
		t2.setName("窗口2-");
		t3.setName("窗口3-");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class Window1 implements Runnable{
	private  int ticket = 100;
	
	//创建一个随机锁
	Object object = new Object();
	@Override
	public void run() {
		while (true){
			//操作共享数据的代码
			//多个线程共用一个锁
			//可使用当前对象来当锁
			synchronized (this){
				if (ticket>0){
					System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket );
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					ticket--;
				}else {
					break;
				}
			}
		}
	}
}

4. 线程安全问题

在java中通过同步机制解决线程安全问题

(1)方法一:同步代码块

  • 解决Runnable接口多线程
    synchronized(同步监视器){
    //需要被同步的代码
    }

//需要被同步的代码:操作共享数据的代码
共享数据:多个线程共同操作的变量,数据。比如本问题的ticket
同步监视器: 俗称:锁。任何一个类的对象都可以充当锁
要求:多个线程必须公用同一把锁

(2)方法二:同步方法

  • 同步方法处理Runnable接口的线程安全问题

如果共享数据的代码完整的声明在同一个方法中,这个方法可以放在同步中,就叫同步方法,在方法前声明synchronized

public synchronized void show(){
	if (ticket>0){
		System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket );
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		ticket--;
	}
    }

关于同步方法的总结:

  • 任然涉及到同步监视器,只是不需要我们显示的声明
  • 非静态的同步方法,他的锁为this
  • 静态方法的同步方法,锁为当前类

注意

使用同步锁好处:解决了线程安全问题

坏处(局限性):操作同步代码时只能有一个线程,相当于是单线程

(1)解决线程安全的方式三:Lock锁 (JDK5.0以后出现的)

synchronized 与 lock的异同

  1. 相同点:都解决了线程安全问题
  2. 不同点:synchronized在执行完相应的同步代码以后自动的释放同步监视器
  3. lock需要手动的启动同步,同时要手动的结束同步
public class LockTest {
	public static void main(String[] args) {
		WindowTest w = new WindowTest();
		
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("窗口1:");
		t2.setName("窗口2:");
		t3.setName("窗口3:");
		
		t1.start();
		t2.start();
		t3.start();
	}
}
class WindowTest implements Runnable{
	
	private int ticket = 100;
	
	//1.实例化ReentrantLock
	private ReentrantLock lock = new ReentrantLock();
	@Override
	public void run() {
		while (true){
			try {
				//调用锁定方法:lock
				lock.lock();
				if (ticket>0){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName()+"卖票,票号为:" + ticket);
					ticket--;
				}else {
					break;
				}
			}finally {
				//调用解锁方法:unlock
				lock.unlock();
			}
		}
	}
}

5. 死锁问题

出现锁一般就要发生死锁问题

出现死锁,程序既不会停止运行,也不会抛出异常

在开发中要避免死锁

当一个线程等待另一个线程释放资源,而另一个线程有等待此线程释放资源时,就会发生死锁

现在先演示一下死锁

public class DeadLock {
	public static void main(String[] args) {
		
		StringBuffer s1 = new StringBuffer();
		StringBuffer s2 = new StringBuffer();
		
		new Thread(){
			@Override
			public void run() {
				synchronized (s1){
					s1.append("a");
					s2.append("1");
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized (s2){
						s1.append("b");
						s2.append("2");
						
						System.out.println(s1);
						System.out.println(s2);
					}
				}
			}
		}.start();
		
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (s2){
					s1.append("c");
					s2.append("3");
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (s1){
						s1.append("d");
						s2.append("4");
						
						System.out.println(s1);
						System.out.println(s2);
					}
				}
			}
		}).start();
	}
}

6. 解决死锁

有死锁就要解决死锁

解决死锁就要先知道死锁的条件

(1)互斥条件:一个资源每次只能被一个线程使用。图上每条路上只能让一个方向的汽车通过,故满足产生死锁的条件之一

(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。可以看出,图上每个方向的汽车都在等待其他方向的汽车撤走,故满足产生死锁的条件之二

(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。这里假设没有交警,那么没有人能强行要求其他方向上的汽车撤离,故满足产生死锁的条件之三

(4)循环等待条件:若干进程或线程之间形成一种头尾相接的循环等待资源关系。这个在图中很直观地表达出来了

 解决死锁就要打破这四个死锁条件:

(1)打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;

(2)打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;

(3)进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低

(4)避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。


制作不易,记得点赞!!!!!!

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值