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);
		}
	}
}

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值