`

传智播客风清扬视频-------线程简介

阅读更多
想了解线程,必须先了解进程,因为线程是依赖进程存在的。

什么是进程?
    进程就是正在运行的程序;是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

多进程有什么意义?
     单进程的计算机只能做一件事情,而我们现在的计算机可以做多件事情。
     举例:一边玩游戏,一边听音乐等
     现在的计算机都支持多进程的,可以在一个时间段内执行多个任务。并且,可以提高CPU使用率。

什么是线程?
     在同一个进程内又可以执行多个任务,而每一个任务可以看成是一个线程。
     线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
     单线程:如果程序只有一条执行路径。
     多线程:如果程序有多条执行路径。

多线程有什么意义呢?
     多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
     程序的执行其实都是在抢CPU的资源,CPU的执行权。
     多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的机率抢到CPU的执行权。
     我们不敢保证哪一个线程能够在哪个时刻抢到,所以线程执行有随机性。


并行和并发的区别:
     并行:是逻辑上同时发生,指在某一个时间内同时运行多个程序。
     并发:是物理上同时发生,指在某一个时间点同时运行多个程序。

Java程序的运行原理:
     由java命令启动JVM,JVM启动就相当于启动一个进程。
     接着有该进程创建了一个主线程去调main方法。


方法1:继承Thread实现线程
步骤:
     A:自定义类MyThread继承Thread类
     B:MyThread类里面重写run()
    C:创建对象
     D:启动线程

该类为什么重写run()方法? 不是类中所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,Java提供了run方法来包含被线程执行的代码。

public class MyThread extends Thread {

	@Override
	public void run() {
		// 一般来说,被线程执行的代码肯定是比较耗时的。
                     // 所以我们用循环改进
		for (int x = 0; x < 200; x++) {
			System.out.println(x);
		}
	}

}

public class MyThreadDemo {
	public static void main(String[] args) {
		// 创建线程对象
		// MyThread my = new MyThread();
		// // 启动线程
		// my.run();
		// my.run();
		// 调用run()方法为什么是单线程的呢?
		// 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果
		// 要想看到多线程的效果,就必须说说另一个方法:start()
		// 面试题:run()和start()的区别?
		// run():仅仅是封装被线程执行的代码,直接调用是普通方法
		// start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
		// MyThread my = new MyThread();
		// my.start();
		// // IllegalThreadStateException:非法的线程状态异常
		// // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。
		// my.start();

		// 创建两个线程对象
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();

		my1.start();
		my2.start();
	}
}



获取线程对象的名称:
   public final String getName() :获取线程对象的名称

设置线程对象的名称:
   public final void setName()

针对不是Thread类的子类中如何获取线程对象名称
   public static Thread currentThread() : 返回当前正在执行的线程对象
   Thread.currentThread().getName()

public class MyThreadDemo {
	public static void main(String[] args) {
		// 创建线程对象
		//无参构造+setXxx()
		// MyThread my1 = new MyThread();
		// MyThread my2 = new MyThread();
		// //调用方法设置名称
		// my1.setName("林青霞");
		// my2.setName("刘意");
		// my1.start();
		// my2.start();
		
		//带参构造方法给线程起名字
		// MyThread my1 = new MyThread("林青霞");
		// MyThread my2 = new MyThread("刘意");
		// my1.start();
		// my2.start();
		
		//我要获取main方法所在的线程对象的名称,该怎么办呢?
		//遇到这种情况,Thread类提供了一个很好玩的方法:
		//public static Thread currentThread():返回当前正在执行的线程对象
		System.out.println(Thread.currentThread().getName());
	}
}


public final void setDaemon(boolean on)将线程设置为守护线程或用户线程
当正在运行的线程都是守护线程时,Java虚拟机退出, 该方法必须在启动前调用


public class ThreadDaemon extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
		}
	}
}

public class ThreadDaemonDemo {
	public static void main(String[] args) {
		ThreadDaemon td1 = new ThreadDaemon();
		ThreadDaemon td2 = new ThreadDaemon();

		td1.setName("关羽");
		td2.setName("张飞");

		// 设置守护线程
		td1.setDaemon(true);
		td2.setDaemon(true);

		td1.start();
		td2.start();

		Thread.currentThread().setName("刘备");
		for (int x = 0; x < 5; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}
}

 
public final void join() ; 等待该线程终止后再调用其它线程
public class ThreadJoinDemo {
	public static void main(String[] args) {
		ThreadJoin tj1 = new ThreadJoin();
		ThreadJoin tj2 = new ThreadJoin();
		ThreadJoin tj3 = new ThreadJoin();

		tj1.setName("李渊");
		tj2.setName("李世民");
		tj3.setName("李元霸");

		tj1.start();
		try {
			tj1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		tj2.start();
		tj3.start();
	}
}


设置线程对象优先级
public final int getPriority() :返回线程对象优先级
public final void setPriority(int ne) : 设置线程优先级

注意:
    线程默认优先级是5
   线程优先级的范围是 : 1-10
   线程优先级高仅表示线程获取的CPU时间片的几率高,需要在次数比较多,或多次运行时效果才比较显著。

IllegalArgumentException:非法参数异常。
抛出的异常表明向方法传递了一个不合法或不正确的参数。
public class ThreadPriority extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
		}
	}
}


public class ThreadPriorityDemo {
	public static void main(String[] args) {
		ThreadPriority tp1 = new ThreadPriority();
		ThreadPriority tp2 = new ThreadPriority();
		ThreadPriority tp3 = new ThreadPriority();

		tp1.setName("东方不败");
		tp2.setName("岳不群");
		tp3.setName("林平之");

		// 获取默认优先级
		// System.out.println(tp1.getPriority());
		// System.out.println(tp2.getPriority());
		// System.out.println(tp3.getPriority());

		// 设置线程优先级
		// tp1.setPriority(100000);
		
		//设置正确的线程优先级
		tp1.setPriority(10);
		tp2.setPriority(1);

		tp1.start();
		tp2.start();
		tp3.start();
	}
}


public static void sleep() 线程休眠

public class ThreadSleep extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x + ",日期:" + new Date());
			// 睡眠
			// 困了,我稍微休息1秒钟
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class ThreadSleepDemo {
	public static void main(String[] args) {
		ThreadSleep ts1 = new ThreadSleep();
		ThreadSleep ts2 = new ThreadSleep();
		ThreadSleep ts3 = new ThreadSleep();

		ts1.setName("林青霞");
		ts2.setName("林志玲");
		ts3.setName("林志颖");

		ts1.start();
		ts2.start();
		ts3.start();
	}
}


线程中断
public final void stop() : 让线程停止,过时了, 超过一定时间,直接结束进程
public final void interrupt() : 中断线程,把线程的状态终止,并抛出一个InterruptedException。

public class ThreadStop extends Thread {
	@Override
	public void run() {
		System.out.println("开始执行:" + new Date());

		// 我要休息10秒钟,亲,不要打扰我哦
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// e.printStackTrace();
			System.out.println("线程被终止了");
		}

		System.out.println("结束执行:" + new Date());
	}
}

public class ThreadStopDemo {
	public static void main(String[] args) {
		ThreadStop ts = new ThreadStop();
		ts.start();

		// 你超过三秒不醒过来,
		try {
			Thread.sleep(3000);
			// ts.stop();   // 结束线程
			ts.interrupt(); //抛出异常,将线程状态终止
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


public static void yield() : 暂停当前正在执行的线程,让CPU去执行其它线程
让多个线程执行更和谐
public class ThreadYield extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
			Thread.yield();
		}
	}
}

public class ThreadYieldDemo {
	public static void main(String[] args) {
		ThreadYield ty1 = new ThreadYield();
		ThreadYield ty2 = new ThreadYield();

		ty1.setName("林青霞");
		ty2.setName("刘意");

		ty1.start();
		ty2.start();
	}
}



方法二: 实现Runnable接口
步骤 : A:自定义MyRunnable实现Runnable接口
        B:重写run()方法
        C:创建MyRunnable类的对象
        D:创建Thread类的对象,并把c步骤的对象作为构造参数传递


public class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}

}

public class MyRunnableDemo {
	public static void main(String[] args) {
		// 创建MyRunnable类的对象
		MyRunnable my = new MyRunnable();

		// 创建Thread类的对象,并把C步骤的对象作为构造参数传递
		// Thread(Runnable target)
		// Thread t1 = new Thread(my);
		// Thread t2 = new Thread(my);
		// t1.setName("林青霞");
		// t2.setName("刘意");

		// Thread(Runnable target, String name)
		Thread t1 = new Thread(my, "林青霞");
		Thread t2 = new Thread(my, "刘意");

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




案例:模拟售票

版本1 : 继承Thread类
public class SellTicket extends Thread {

	// 定义100张票
	// private int tickets = 100;
	// 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
	private static int tickets = 100;

	@Override
	public void run() {
		// 定义100张票
		// 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
		// int tickets = 100;

		// 是为了模拟一直有票
		while (true) {
			if (tickets > 0) {
				System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
			}
		}
	}
}

public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建三个线程对象
		SellTicket st1 = new SellTicket();
		SellTicket st2 = new SellTicket();
		SellTicket st3 = new SellTicket();

		// 给线程对象起名字
		st1.setName("窗口1");
		st2.setName("窗口2");
		st3.setName("窗口3");

		// 启动线程
		st1.start();
		st2.start();
		st3.start();
	}
}


版本二: 实现Runnable接口
public class SellTicket implements Runnable {
	// 定义100张票
	private int tickets = 100;

	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {
				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ (tickets--) + "张票");
			}
		}
	}
}

public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}



版本三:通过sleep() 暴露出问题
public class SellTicket implements Runnable {
	// 定义100张票
	private int tickets = 100;

//	@Override
//	public void run() {
//		while (true) {
//			// t1,t2,t3三个线程
//			// 这一次的tickets = 100;
//			if (tickets > 0) {
//				// 为了模拟更真实的场景,我们稍作休息
//				try {
//					Thread.sleep(100); // t1就稍作休息,t2就稍作休息
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
//
//				System.out.println(Thread.currentThread().getName() + "正在出售第"
//						+ (tickets--) + "张票");
//				// 理想状态:
//				// 窗口1正在出售第100张票
//				// 窗口2正在出售第99张票
//				// 但是呢?
//				// CPU的每一次执行必须是一个原子性(最简单基本的)的操作。
//				// 先记录以前的值
//				// 接着把ticket--
//				// 然后输出以前的值(t2来了)
//				// ticket的值就变成了99
//				// 窗口1正在出售第100张票
//				// 窗口2正在出售第100张票
//
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			// t1,t2,t3三个线程
			// 这一次的tickets = 1;
			if (tickets > 0) {
				// 为了模拟更真实的场景,我们稍作休息
				try {
					Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息,
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ (tickets--) + "张票");
				//窗口1正在出售第1张票,tickets=0
				//窗口2正在出售第0张票,tickets=-1
				//窗口3正在出售第-1张票,tickets=-2
			}
		}
	}
}

/*
 * 实现Runnable接口的方式实现
 * 
 * 通过加入延迟后,就产生了连个问题:
 * A:相同的票卖了多次
 * 		CPU的一次操作必须是原子性的
 * B:出现了负数票
 * 		随机性和延迟导致的
 */
public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}



如何解决线程安全问题?
  分析哪些原因导致问题:
       A: 是否是多线程环境
       B: 是否共享数据
       C: 是否有多条语句操作共享数据

  解决思路:
       把多条语句操作共享数据的代码给包成一个整体,让某一个线程在执行的时候,别的线程不能进入。
   同步代码块:
           synchronized(对象){
              需要同步的代码
         }
   注意: 同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能。
多个线程必须是同一把锁。

版本四:使用synchronized 同步解决问题
public class SellTicket implements Runnable {
	// 定义100张票
	private int tickets = 100;
	//创建锁对象
	private Object obj = new Object();

//	@Override
//	public void run() {
//		while (true) {
//			synchronized(new Object()){
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100); 
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName() + "正在出售第"
//							+ (tickets--) + "张票");
//				}
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (obj) {
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + (tickets--) + "张票");
				}
			}
		}
	}
}

public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}



同步的好处: 可以解决多线程的安全问题
同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,
             这是很耗资源的,无形中降低了程序运行的效率。


A:同步代码块的锁对象是: 任意对象
B:同步方法的格式及锁对象:
        把同步方法关键字加在方法上
        同步方法的锁对象是this
C:静态方法及代码块是:  类的字节码对象

public class SellTicket implements Runnable {

	// 定义100张票
	private static int tickets = 100;

	// 定义同一把锁
	private Object obj = new Object();
	private Demo d = new Demo();

	private int x = 0;
	
	//同步代码块用obj做锁
//	@Override
//	public void run() {
//		while (true) {
//			synchronized (obj) {
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName()
//							+ "正在出售第" + (tickets--) + "张票 ");
//				}
//			}
//		}
//	}
	
	//同步代码块用任意对象做锁
//	@Override
//	public void run() {
//		while (true) {
//			synchronized (d) {
//				if (tickets > 0) {
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName()
//							+ "正在出售第" + (tickets--) + "张票 ");
//				}
//			}
//		}
//	}
	
	@Override
	public void run() {
		while (true) {
			if(x%2==0){
				synchronized (SellTicket.class) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "张票 ");
					}
				}
			}else {
//				synchronized (d) {
//					if (tickets > 0) {
//						try {
//							Thread.sleep(100);
//						} catch (InterruptedException e) {
//							e.printStackTrace();
//						}
//						System.out.println(Thread.currentThread().getName()
//								+ "正在出售第" + (tickets--) + "张票 ");
//					}
//				}
				
				sellTicket();
				
			}
			x++;
		}
	}

//	private void sellTicket() {
//		synchronized (d) {
//			if (tickets > 0) {
//			try {
//					Thread.sleep(100);
//			} catch (InterruptedException e) {
//					e.printStackTrace();
//			}
//			System.out.println(Thread.currentThread().getName()
//						+ "正在出售第" + (tickets--) + "张票 ");
//			}
//		}
//	}
	
	//如果一个方法一进去就看到了代码被同步了,那么我就再想能不能把这个同步加在方法上呢?
//	 private synchronized void sellTicket() {
//			if (tickets > 0) {
//			try {
//					Thread.sleep(100);
//			} catch (InterruptedException e) {
//					e.printStackTrace();
//			}
//			System.out.println(Thread.currentThread().getName()
//						+ "正在出售第" + (tickets--) + "张票 ");
//			}
//	}
	
	private static synchronized void sellTicket() {
		if (tickets > 0) {
		try {
				Thread.sleep(100);
		} catch (InterruptedException e) {
				e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()
					+ "正在出售第" + (tickets--) + "张票 ");
		}
}
}

class Demo {
}



public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}


将集合变成线程安全
public class ThreadDemo {
	public static void main(String[] args) {
		// 线程安全的类
		StringBuffer sb = new StringBuffer();
		Vector<String> v = new Vector<String>();
		Hashtable<String, String> h = new Hashtable<String, String>();

		// Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
		// 那么到底用谁呢?
		// public static <T> List<T> synchronizedList(List<T> list)
		List<String> list1 = new ArrayList<String>();// 线程不安全
		List<String> list2 = Collections
				.synchronizedList(new ArrayList<String>()); // 线程安全
	}
}



总结:

(1)多线程: 一个应用程序多条执行路径
     进程: 正在执行的应用程序
     线程: 进程的执行单元,执行路径
     单线程: 一个应用程序只有一条执行路径
     多线程: 一个应用程序有多条执行路径

     多进程的意义: 提高CPU的使用率
     多线程的意义: 提高应用程序的使用率

(2)Java程序的运行原理及JVM的启动是多线程的吗?
      A:JAVA命令去启动JVM,JVM就会启动一个进程,该进程会启动主线程。
      B:JVM的启动是多线程的,因为最低有两个线程启动主线程和垃圾回收线程

(3)多线程实现方案
      A:继承Thread类
      B:实现Runnable接口
(4)线程的调度和优先级问题
      A:线程的调度 a:分时调度 b:抢占式调度(java采用该种)
      B:获取和设置线程优先级  a默认是5 b范围是1-10
(5)线程的控制
      A:休眠线程  B:加入线程 C:礼让线程 D:后台线程 E:终止线程
(6)线程的生命周期
      A:新建 B:就绪 C:运行 D:阻塞 E:死亡
(7)多线程安全问题的原因
      A:是否有多线程环境
      B:是否有共享数据
      C:是否有多条语句操作共享数据
(8)同步解决线程安全问题
      A:同步代码块
           synchronized(对象){
             需要被同步的代码
         }
      这里的锁对象可以是任意对象
      B:同步方法
         把同步加在方法上
         这里的锁对象是this
     C:静态同步方法
         把同步加在方法上
         这里的锁对象是当前类的字节码文件对象

(9)回顾以前的线程安全的类
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。

 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics