javaSE(八).java多线程

一,在学习多线程之前,先来看看涉及到的概念。

进程(process):是操作系统运行的一个任务,一个应用程序运行在一个进程中。

线程(Thread):一个进程对应一块内存区域。操作系统系统会把进程划分为一个个小的功能单元。而进程中所包含的一个或多个这样的功能单元称为线程。

注意:

        1.一个进程会有一个私有的虚拟空间,而这个空间只能被它包含的线程访问。

       2.一个线程只能属于一个进程。

      3.当操作系统在创建一个进程时,该进程所包含的线程中会存在一个主线程。

并发:并发是怎么回事呢?我们所谓并发通常是指多个线程“同时”运行。

        实际上,当一个进程创建后,OS(操作系统)会将时间分成许多时间片并且均匀的分给每个线程。当某个线程获得时间片后开始运行,其他线程则处于等待状态。当这个线程获得时间片结束后,不管这个线程运行什么地方,都会停下来。然后另外一个获得时间片的线程继续运行,如此下去,直到任务完成。

       所以从微观的角度来看,这些线程并没有同时运行,会存在时间的先后顺序,总是”走走停停“。那么从宏观的角度来看(这些时间片非常小,我们通常感受不到差异)这些线程是在“同时”运行,就是我们通常所说的并发。

守护线程:该线程和普通的线程基本上没什么区别。唯一的区别是,当进程中只剩下守护线程时,所有守护线程全部终止。我们只需要通过Thread提供的方法来设定即可,利用void setDaemon(boolean param) 当参数param为true时该线程便被设为守护线程。

eg:  GC就是运行在一个守护线程上的。

注:守护线程也叫做后台线程,其守护着前台线程,当前台线程全部结束后,守护线程也随之结束。

二,接下来我们一起看看线程Thread所拥有的一些方法:

1.获取当前线程的方法

Thread.currentTread()         eg:  Thread  t = Thread.currentThread();

2.获取线程信息的方法

方法 返回类型 返回内容
getId() long 返回该线程的标识符
getName() String  返回该线程的名称
getPriority() int 返回线程的优先级
getState() Thread.state 返回该线程的状态
isAlive() boolean  测试线程是否处于活动状态
isDaemon() boolean  测试线程是否为守护线程
isInterrupted() boolean  测试线程是否已经中断

3,线程的其他方法

 

方法 作用
static void sleep(long s) Thread的静态方法sleep用于使当前线程进入阻塞状态。

该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前线程会重新进入到Runnable状态,等待分配时 间片。

该方法生命抛出一个InterruptException。所以在使用该方法时要捕获这个异常。

static void  yield()

该方法用于使当前线程主动让出cpu时间片片会到runnable的状态,等待分配时间片。

void  join()

该方法用户等待当前线程结束。

该方法声明抛出InterruptException

注:一个方法中定义了一个内部类,该内部类若想引用这个方法中的其他局部变量,那么这个变量必须是final的。

三,线程的优先级

一个时间片要分配给那个线程,以及这个线程结束后将要运行那个线程。这些线程之间的切换是由线程调度控制的,我们不能通过代码控制,可是只要我们把一个线程的优先级设的高点,那么这个线程获得时间片的概率就会大一点,运行概率也会大一点。

线程的优先级通常被分为10级,用1——10来表示,1最低,10最高。线程有3个常量来表示最高,最低,默认优先级,分别是:

Thread.MIN_PRIORITY            最低优先级

Thread.MAX_PRIORITY           最高优先级

Thread.NORM_PRIORITY        默认优先级

设置优先级方法为:void setPriority(int priority);

package thread;

/**
 * 线程优先级
 * 1——10
 * 理论上,线程优先级高的线程,
 * 被分配到时间片段的次数多
 * @author Administrator
 *
 */
public class ThreadDemo6 {
	public static void main(String[] args){
		
		Thread max = new Thread(){
			public void run(){
				for(int i=0;i<5000;i++){
					System.out.println("max");
				}
			}
		};
		
		Thread normal = new Thread(){
			public void run(){
				for(int i=0;i<5000;i++){
					System.out.println("normal");
				}
			}
		};
		
		Thread min = new Thread(){
			public void run(){
				for(int i=0;i<5000;i++){
					System.out.println("min");
				}
			}
		};
		
		max.setPriority(Thread.MAX_PRIORITY);
		normal.setPriority(Thread.NORM_PRIORITY);
		min.setPriority(Thread.MIN_PRIORITY);
		
		min.start();
		normal.start();
		max.start();
	}
}

四,创建线程的方式

1,继承Thread类,重写run方法

package thread;

/**
 * 第一种创建线程的方式
 * 继承Thread类,重写run()方法
 * @author Administrator
 *
 */
public class ThreadDemo1{
	public static void main(String[] args){	
		//有先后顺序运行的方式是同步运行。
		Thread t1 = new MyThread1();
		Thread t2 = new MyThread2();
		
		/**
		 * start()方法用于将线程纳入到线程调度
		 * 这时,线程进入到runnable状态,等待
		 * 线程调度分配时间片段。
		 * 当线程调度将时间片段分配给当前线程,该线程的run()方法才开始执行。
		 * 直到线程的run()方法执行完毕,线程结束最终被收回。
		 * 在线程的run()方法执行期间,该线程处于走走停停。
		 */
		t1.start();
		t2.start();
		
	}
}

/**
 * 解耦    耦合
 * @author Administrator
 */
class MyThread1 extends Thread{
	public void run(){
		for(int i=0;i<1000;i++){
			System.out.println("你是谁啊");
		}
	}
}

class MyThread2 extends Thread{
	public void run(){
		for(int i=0;i<1000;i++){
			System.out.println("我是查水表的");
		}
	}
}	public static void main(String[] args){	
		//有先后顺序运行的方式是同步运行。
		Thread t1 = new MyThread1();
		Thread t2 = new MyThread2();
		
		/**
		 * start()方法用于将线程纳入到线程调度
		 * 这时,线程进入到runnable状态,等待
		 * 线程调度分配时间片段。
		 * 当线程调度将时间片段分配给当前线程,该线程的run()方法才开始执行。
		 * 直到线程的run()方法执行完毕,线程结束最终被收回。
		 * 在线程的run()方法执行期间,该线程处于走走停停。
		 */
		t1.start();
		t2.start();
		
	}
}

/**
 * 解耦    耦合
 * @author Administrator
 */
class MyThread1 extends Thread{
	public void run(){
		for(int i=0;i<1000;i++){
			System.out.println("你是谁啊");
		}
	}
}

class MyThread2 extends Thread{
	public void run(){
		for(int i=0;i<1000;i++){
			System.out.println("我是查水表的");
		}
	}
}

2,定义线程体,实现Runnable接口

package thread;

/**
 * 第二种创建线程的方式
 * 定义线程体Runnable
 * @author Administrator
 *
 */
public class ThreadDemo2 {
	public static void main(String[] args){
		Runnable run1 = new MyRunnable1();
		Runnable run2 = new MyRunnable2();
		
		Thread t1 = new Thread(run1);
		Thread t2 = new Thread(run2);
		
		t1.start();
		t2.start();
	}
}

class MyRunnable1 implements Runnable{

	public void run(){
		for(int i=0;i<1000;i++){
			System.out.println("报暗号");
		}
	}
	
}

class MyRunnable2 implements Runnable{

	public void run(){
		for(int i=0;i<1000;i++){
			System.out.println("宝塔镇河妖");
		}
	}
	
}

五,线程存在的问题

1.要将异步变成同步。利用synchronized关键字

多个线程并发读写同一个临界资源时会发生“线程并发安全问题。

常见的临界资源:

       多线程共享实例变量

      多线程共享静态公共变量

要解决线程安全问题,要将异步操作变为同步操作。

异步操作: 多线程并发的操作,相当于各干各的

同步操作: 有先后顺序的操作,相当于你干完我再干

synchronized关键字是java中的同步锁

package thread;
/**
 * 线程并发问题
 * @author Administrator
 *
 */
public class SyncDemo {
	//桌子上有20个豆沙包
	public static int beans = 20;
	public static void main(String[] args){		
		Thread t1 = new Thread(){
			public void run (){
				int bean = 0;
				while(true){
					bean = getBean();
					System.out.println(this.getName() + bean+"个豆沙包");
				}
			}
		};	
		Thread t2 = new Thread(){
			public void run (){
				int bean = 0;
				while(true){
					bean = getBean();
					System.out.println(this.getName()+":剩下"+bean+"个豆沙包");
				}
			}
		};	
		t1.start();
		t2.start();
	}
	//每次从桌子上取一个豆沙包
	public static synchronized int getBean(){
		if(beans == 0){
			throw new RuntimeException("没有豆沙包");
		}
		Thread.yield();
		return beans;
	}
}

2.性能问题

加了同步锁synchronized后执行效率变低,性能变差为了最低限度的降低同步锁synchronized的影响。在保证线程安全的情况下,尽可能减小synchronized作用的范围。

同步代码块(synchronzied关键字),同步代码块包含两部分:

一个作为锁的对象的引用,一个作为由这个锁保护的代码块。

synchronized(同步监视器——锁对象引用){

    //代码块

    //代码块

}

若方法中的所有代码都需要同步也可以给方法直接加锁。

每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出代码时会自动释放锁,而且无论是通过正常途径退出还是通过抛异常退出都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

注:多线程看到的是同一个对象才有同步作用(锁对象必须是同一个)如果synchronized块在一个非静态方法中,通常锁对象用this

package thread;
public class SyncDemo2 {
//	private static Object obj = new Object();
	public static void main(String[] args){
		final SyncDemo2 demo = new SyncDemo2();
		Thread t1 = new Thread(){
			public void run(){
				demo.buy(getName());
			}
		};

		Thread t2 = new Thread(){
			public void run(){
				demo.buy(getName());
			}
		};

		t1.start();
		t2.start();
	}
       //场景,某个商场只有一个停车位,而有多辆车来停
	public void buy(String name){
		System.out.println(name+"正在找车位");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(name+"找到车位了");

		synchronized(this){
			System.out.println(name+"正在停车");
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(name+"车开离车位了");
		}
		System.out.println(name+"车离开这个停车场了");
	}
}

3.线程之间协同工作

wait和notify

例如:浏览器的一个显示图片的 displayThread想要执行显示图片的任务,必须等待下载线程。downloadThread将该图片下载完毕。

如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread,可以显示图片了,这时display继续执行。

以上逻辑简单的说法就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切相关的。

注:在downloadThread完成之后,如果有多个线程要被通知(解除锁),用notify会随机通知(解除)这多个线程(以任意顺序)。如果用notifyAll()的活,可以一次全部通知这些等待线程。

我们调用哪个对象的wait()和notify,就应当对当前对象加锁,锁的就是这个对象。

package thread;

/**
 * 线程协同工作
 * @author Administrator
 *
 */
public class ThreadDemo10 {
	//表示图片是否下载完毕
	public static boolean isFinish;
	public static Object obj = new Object();

	public static void main(String[] args){
		final Thread download = new Thread(){
			public void run(){
				System.out.println("down:开始下载图片");
				for(int i=1;i<=100;i++){
					System.out.println("down:已完成"+i+"%");
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("图片下载完毕");
				isFinish = true;
				/**
				 * 当图片下载完毕,就应当通知显示图片的线程开始工作了
				 */
				synchronized(obj){
					obj.notify();
				}
				System.out.println("down:开始下载附件");
				for(int i=1;i<=100;i++){
					System.out.println("down:已完成"+i+"%");
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("附件下载完毕");
				isFinish = true;

			}
		};


		/**
		 * mian()方法中定义了一个内部类show,
		 * 该内部类若想引用main方法中的其他局部变量
		 * 那么这个变量必须是final的
		 */
		Thread show = new Thread(){
			public void run(){
				System.out.println("show:开始图片显示...");
				//哈哈哈
				try {
					//						download.join();
					//在obj对象上等待
					synchronized(obj){
						obj.wait();
					}			
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if(!isFinish){
					throw new RuntimeException("图片没有下载完成");
				}
				System.out.println("show:图片显示完毕");
			}

		};
		download.start();
		show.start();
	}
}

4,将一些非线程安全的集合转换为线程安全的集合

利用collections类

                //将现有的list集合转换为线程安全的
                List<String> list = new ArrayList<String>();	
		list = Collections.synchronizedList(list);
		System.out.println(list);
		
		//将现有的set集合转换成线程安全的
		Set<String> set = new HashSet<String>();
		set = Collections.synchronizedSet(set);

		//将现有的map集合转换成线程安全的
		Map<String,Object> map = new HashMap<String,Object>();
		map = Collections.synchronizedMap(map);

六,线程池

使用ExecutorService实现线程池,是java提供用来管理线程的线程池的类

作用:控制线程数量、重用线程

当一个程序中创建大量线程,并在任务结束后销毁,会过度消耗资源,以及过度切换线程的危险,从而导致系统崩溃。为此我们应使用线程池来解决这个问题。

线程池有以下几种实现策略:

方法 说明
Executors.newCachedThreadPool( )

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。

Executors.newFixedThreadPool(int  nThreads )

创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。

Executors.newScheduledThreadPool(int corePoolSize) 创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行。

Executors.newSingleThreadExecutor()

创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。

package thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池
 * 用于重用线程以及控制线程数量
 * @author Administrator
 */
public class ThreadPoolDemo {
	public static void main(String[] args){
		ExecutorService threadPool = Executors.newFixedThreadPool(2);
		for(int i=0;i<5;i++){
			Runnable runn = new Runnable(){
				public void run(){
					for(int i=0;i<3;i++){
						System.out.println(i);
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
			};
			threadPool.execute(runn);
		}
	}
}

声明:该线程学习笔记是在我学习多线程之后完成的,其中用到一些他人的代码思路,向您致敬。目的是在有网的情况下能随时查看多线程知识,也可以让更多的小伙伴看到,不做任何商业用途,如有侵权之处,请联系我,以便删改。

猜你喜欢

转载自blog.csdn.net/weixin_41968788/article/details/80836115