多线程简述

进程和线程

进程的介绍
  • 是一个程序的运行状态和资源占用(内存,cpu)的描述
  • 进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程
  • 进程的特点:
    • 独立性:不同的进程之间是独立的,相互之间资源不共享
    • 动态性:进程在系统中不是静止不动的,而是在系统中一直活动的
    • 并发性:多个进程可以在单个处理器上同时进行,且互不影响
线程的介绍
  • 是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务
  • 线程的执行时抢占式的,多个线程在同一个进程中可以并发执行,其实就是cpu快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另一个线程可以运行
进程和线程的关系以及区别
  • 一个程序运行后至少有一个进程
  • 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
  • 进程间不能共享资源,但线程之间可以
  • 系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多线程的效率高

线程的实现

继承Thread类
  • 进程自Thread类,Thread类是所有线程类的父类,实现了对线程的抽取和封装
  • 继承Thread类创建并启动多线程的步骤:
    • 定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
    • 创建Thread子类的对象,即创建了子线程
    • 用线程对象的start方法来启动该线程
package waking.test.xc;
/**
 * 线程的实现,继承Thread类
 * @author waking
 *
 */
public class Demo01 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==>"+i);
		}
	}
	
	public static void main(String[] args) {
		Demo01 d = new Demo01();
		d.setName("a");
		d.start();
		for (int i = 0; i <100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
		
	}
}

  • 多线程买票
package waking.test.xc;
/**
 * 售票线程,Thread
 * @author waking
 *
 */
public class Demo02 {
	public static void main(String[] args) {
		SellTickets s1 = new SellTickets();
		SellTickets s2 = new SellTickets();
		SellTickets s3 = new SellTickets();
		SellTickets s4 = new SellTickets();
		
		//开启线程,4个窗口买票
		s1.start();
		s2.start();
		s3.start();
		s4.start();
	}
}
/**
 * 售票线程
 * @author waking
 *
 */
class SellTickets extends Thread{
	
	static int num = 100;
	
	@Override
	public void run() {
		while(num>0) {
			System.out.println(Thread.currentThread().getName()+"===>"+num--);
		}
	}
}
实现Runnable接口
  • 实现Runnable接口创建并启动多线程的步骤:
    • 定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体
    • 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
    • 调用线程对象的start方法来启动该线程
package waking.test.xc;
/**
 * 线程实现:实现Runnable接口
 * @author waking
 *
 */
public class Demo03 {
	public static void main(String[] args) {
		A a = new A();
		Thread t1 = new Thread(a,"a");
		t1.start();
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
}
class A implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
  • 模拟买票
package waking.test.xc;
/**
 * 实现Runnable买票
 * @author waking
 *
 */
public class Demo04 {
	public static void main(String[] args) {
		RunnableTest r = new RunnableTest();
		
		//启动线程
		new Thread(r, "A").start();
		new Thread(r, "B").start();
		new Thread(r, "C").start();
		new Thread(r, "D").start();
	}
}
class RunnableTest implements Runnable{

	static int num = 100;
	@Override
	public void run() {
		while(num>0) {
			System.out.println(Thread.currentThread().getName()+"===>"+num--);
		}
	}
	
}
两种方法的比较
  • 实现Runnable接口的方法
a.线程类只是实现Runnable接口,还可以继承其他类【java特性,一个类在实现接口的同时还可以继承另一个类】
b.可以多个线程共享同一个target对象,所以非常适合多线程来处理同一份资源的情况
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()
  • 继承Thread类的方式
a.编写简单,如果要访问当前线程,除了可以通过Thread.currentThread()方法之外,还可以使用super关键字
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】
  • 注意:实际上大多数的是、多线程应用都可以采用实现Runnable接口的方法来实现【推荐使用匿名内部类】
Callable接口
  • 创建FutureTask对象,创建Callable子类对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一 个Runnable)。
  • 创建Thread类对象,将FutureTask对象传递给Thread对象。
  • 调用start方法开启线程。这种方式可以 获得线程执行完之后的返回值。
  • 示例
package waking.test.xc;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
 * 多线程实现Callable接口
 * @author waking
 *
 */
public class Demo14 {
	public static void main(String[] args) {
		I i = new I();
		FutureTask<Integer> f = new FutureTask<Integer>(i);
		new Thread(f).start();
		
		for (int j = 0; j < 100; j++) {
			System.out.println(Thread.currentThread().getName()+"===>"+j);
		}
	}

}
/**
 * 实现Callable接口
 * @author waking
 *
 */
class I implements Callable<Integer>{

	
	@Override
	public Integer call() throws Exception {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
		return null;
	}
	
}
调用start()与run()方法的区别
  • 当调用start()方法时将创建新的线程,并且执行run()方法里的代码,但是如果直接调用start()方法,不会创建新的线程也不会执行调用线程的代码

线程常用方法

线程休眠
  • 使用当前正在执行的线程休眠一段时间,释放时间片,导致线程进行阻塞状态
  • sleep(1000),1000的单位是毫秒,设置了sleep就相当于将当前线程挂起1秒,这个操作跟线程的优先级无关
package waking.test.xc;
/**
 * 线程sleep()方法
 * @author waking
 *
 */
public class Demo05 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i%20==0) {
				try {
					//休眠100毫秒
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
	public static void main(String[] args) {
		Demo05 d = new Demo05();
		new Thread(d,"A").start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
设置线程优先级
  • 可以通过设置优先级来改变线程抢到时间片的概率,优先级高的线程获得较多的执行机会
  • 默认情况下,每个线程的优先级都与创建它的父线程具有相同的优先级,列如:main线程具有普通优先级,则由main线程创建的子线程也有相同的普通优先级
  • 注意:所传的参数范围1~10,默认为5,对应的数值越大,说明优先级越高,这个方法的设置一定要在start之前线程的优先级低并不意味着争抢不到时间片,只是抢到时间片的概率比较低而已
package waking.test.xc;
/**
 * 线程,设置线程优先级
 * @author waking
 *
 */
public class Demo06 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==>"+i);
		}
	}
	public static void main(String[] args) {
		Demo06 d = new Demo06();
		Thread t = new Thread(d,"A");
		//设置优先级为10
		t.setPriority(10);
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
}
合并线程
  • 在执行原来线程的过程中,如果遇到了合并线程,则优先执行合并进来的线程,执行完合并进来的线程后,再回到原来的任务中,继续执行原来的线程
  • 特点:
    • 线程合并,当前线程一定会释放cpu时间片,cpu会将时间片分给要joio的线程
    • 哪个线程需要合并就在当前线程中,添加要合并的线程
    • join之前,一定要将线程处于准备状态start
package waking.test.xc;
/**
 * 合并线程
 * @author waking
 *
 */
public class Demo07 {
	public static void main(String[] args) throws InterruptedException {
		B b = new B();
		Thread t = new Thread(b,"B");
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
			if(i==20) {
				//合并线程
				t.join();
			}
		}
		
	}
}
class B implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
后台线程
  • 隐藏起来一直在默默运行的线程,直到进程介绍,又被称为守护线程或精灵线程,JVM的垃圾回收线程就是典型的后台线程
  • 特征:如果所有的前台线程都死亡,后台线程会自动死亡,必须要在start之前执行
package waking.test.xc;
/**
 * 守护线程
 * @author waking
 *
 */
public class Demo08 {
	public static void main(String[] args) {
		C  c = new C();
		Thread t = new Thread(c,"C");
		//设置守护线程
		t.setDaemon(true);
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}

}
class C implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
线程让步
  • 可以让当前正在执行的线程暂停,但它不会阻碍该线程,他只是将该线程转入就绪状态,完全可能出现的情况是:当某个线程用了yield方法暂停之后,线程调度器又将其调度出来重新执行
  • 实际上,当某个线程调用了yield方法暂停之后,只有优先级与线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会
package waking.test.xc;
/**
 * yield()线程
 * @author waking
 *
 */
public class Demo09 {
	public static void main(String[] args) {
		D d = new D();
		Thread t = new Thread(d,"D");
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==>"+i);
		}
	}
}
class D implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
			if(i==50) {
				//礼让
				Thread.yield();
			}
		}
	}
	
}

线程的生命周期

  • 对于线程,当线程被创建并启动之后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它会经历各种不同的状态
New(新生):线程被实例化,但是还没有开始执行
Runnable(就绪):没有抢到时间片
Running(运行):抢到了时间片,cpu开始处理这个线程的任务
Blocked(阻塞):线程在执行过程中遇到特殊情况,使得其他线程就可以获得执行机会,被阻塞的线程会等待合适的时机重新进入就绪状态
Dead(死亡):线程终止
	a.run方法执行完成,线程正常结束【正常的死亡】
	b.直接调用该线程的stop方法强制终止这个线程

alt

多线程访问临界资源

多线程访问临界资源时的数据安全问题
  • 产生原因:有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其它线程抢走了,临界资源问题就产生了
解决临界资源问题
  • 解决方案:一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其它线程也要访问这个资源,就得在“锁”外面等待
  • 对象锁:任意的对象都可以被当做锁来使用
  • 类锁:把一个类当做锁
同步代码块
语法: 
	synchronized(锁){
		//需要访问的临界资源
	}
说明:
a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其它线程不能执行代码段中的代码,只能在锁外面等待
b.执行完成代码段中的这段代码,会自动解锁,然后剩下的其它线程开始争抢cpu时间片
c.一定要保证不同的线程看到的是同一把锁,否则同步代码块就没有意义
  • 示例
package waking.test.xc;
/**
 * 同步代码块
 * @author waking
 *
 */
public class Demo10 {
	public static void main(String[] args) {
		M m = new M();
		Thread t = new Thread(m, "A");
		Thread t1 = new Thread(m, "B");
		Thread t2 = new Thread(m, "C");
		Thread t3 = new Thread(m, "D");
		t.start();
		t1.start();
		t2.start();
		t3.start();
	}
	
}
class M implements Runnable{

	static int num = 100;
	
	@Override
	public void run() {
		while(num>0) {
			//加锁
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			synchronized (M.class) {
				if(num<0) {
					break;
				}
				System.out.println(Thread.currentThread().getName()+"===>"+num--);
			}
		}
	}
	
}


  • 同步代码块
package waking.test.xc;

import javax.swing.text.AbstractDocument.BranchElement;

/**
* 同步方法
* @author waking
*
*/
public class Demo11 {
   public static void main(String[] args) {
   	MM m = new MM();
   	Thread t = new Thread(m,"A");
   	Thread t1 = new Thread(m,"B");
   	Thread t2 = new Thread(m,"C");
   	Thread t3 = new Thread(m,"D");
   	
   	t.start();
   	t1.start();
   	t2.start();
   	t3.start();
   }

}
class MM implements Runnable{
   
   static int num = 100;
   
   @Override
   public void run() {
   	while(num>0) {
   		try {
   			test();
   		} catch (InterruptedException e) {
   			// TODO Auto-generated catch block
   			e.printStackTrace();
   		}
   		
   	}
   }
   //同步方法
   public synchronized void test() throws InterruptedException {
   	if(num<0) {
   		return;
   	}
   	Thread.sleep(100);
   	System.out.println(Thread.currentThread().getName()+"===>"+num--);
   
   }
   
}
ReentrantLock类
  • 通过显示定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操作
  • 注意:最好将unlock的操作放到finally块中
  • 通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentranrLock可以显示地加锁,释放锁
package waking.test.xc;

import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock
 * @author waking
 *
 */
public class Demo12 {
	public static void main(String[] args) {
		H h =new H();
		Thread t = new Thread(h,"A");
		Thread t1 = new Thread(h,"B");
		Thread t2 = new Thread(h,"C");
		Thread t3 = new Thread(h,"D");
		
		t.start();
		t1.start();
		t2.start();
		t3.start();
	}
}
class H implements Runnable{
	
	static ReentrantLock lock = new ReentrantLock();
	static int num = 100;
	@Override
	public void run() {
		
			while (num > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				try {
					//加锁
					lock.lock();
					if (num < 0) {
						return;
					}
					System.out.println(Thread.currentThread().getName() + "===>" + num--);
				} finally {
					//解锁
					lock.unlock();
				}
			} 
		
	}
	
}
死锁
  • 每个人都拥有其它人需要的资源,同时又等待其它人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源
  • 当多个线程完成功能需要同时获取多个共享资源的时候可能导致死锁
package waking.test.xc;
/**
 * 死锁
 * @author waking
 *
 */
public class Demo13 {
	public static void main(String[] args) {
		WW w = new WW();
		new Thread(w,"A").start();
		new Thread(w,"B").start();
		new Thread(w,"C").start();
		new Thread(w,"D").start();
	}
}
class WW implements Runnable{
	
	static boolean floge = true;
	static Object o1 = new Object();
	static Object o2 = new Object();

	@Override
	public void run() {
		if(floge){
			synchronized (o1) {
				floge=!floge;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized(o2) {
					for (int i = 0; i < 100; i++) {
						System.out.println(Thread.currentThread().getName()+"===>"+i);
					}
				}
			}
		}else {
			synchronized (o2) {
				floge=!floge;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (o1) {
					for (int i = 0; i < 100; i++) {
						System.out.println(Thread.currentThread().getName()+"===>"+i);
					}
				}
			}
		}
	}
	
}
  • 原理:
实现多个锁之间的嵌套产生死锁。
分析:线程0 想要得到obj2 锁进行下面的操作,而obj2锁被线程1 所占有。 线程1想得到obj1锁 进行下面的操作,而
obj1锁被线程0 所占有。
多线程先将到这里,下个博客会介绍单例模式、生产者、消费者模式中的多线程问题
发布了16 篇原创文章 · 获赞 15 · 访问量 2382

猜你喜欢

转载自blog.csdn.net/weixin_43688349/article/details/104170210