java 之 多线程


1、多线程:
	概念:
		1 进程:正在运行的程序
		2 线程:进程中一个程序执行的控制单元(执行路径)。
	
	P.S.
	1、一个进程中可以有多个执行路径,称之为多线程。
	2、一个进程中至少要有一个线程。
	3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执
	行的任务。

	多线程的好处:解决多部分代码同时运行的问题。
			弊端:线程太多,会导致效率的降低。
				因为,多个程序同时运行,cpu需要在各个线程间进行不停的切换,这也需要消耗资源

	PS:JVM启动时,至少启动了两条线程:
		1 执行main函数的线程
		2 垃圾回收线程
	 ————————————————————————————————————————————————————————————————————
	class Test14 {
	public static void main(String[] args) {
			A a = new A();
			a = null;
			System.gc();// garbage collection
			System.out.println(Thread.currentThread().getName() + " over");
		}
	}

	class A {
		@Override
		protected void finalize() throws Throwable {
			System.out.println("A 的 finalize()被调用,调用进程是:"
					+ Thread.currentThread().getName());
		}
	}
	输出结果:
	main over
	A 的 finalize()被调用,调用进程是:Finalizer
	 ————————————————————————————————————————————————————————————————————
2、创建线程的方式:
	方法1 继承Thread类:
		1 定义类继承Thread类
		2 重写run方法
		3 直接创建Thread类的子类,并调用start()方法启动线程
	方法2 定义类实现Runnable接口
		1 定义类实现Runnable接口
		2 重写run方法
		3 创建实现Runnable接口的对象
		4 将对象作为参数传递给一个Thread类的对象,并调用该对象的start()方法启动线程

		实现Runnable接口的好处:
			1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,更符合面向对象的思想。
			2. 避免了Java单继承的局限性。 所以,创建线程的第二种方式较为常用。
	
	PS:JVM创建的主线程的主任务定义在了主函数中,而自定义的线程其任务定义在run方法中,也就说
	run方法是封装了自定义线程运行任务的函数
	————————————————————————————————————————————————————————————————————
	public class CreatThreadDemo {
		public static void main(String[] args) {
			new MyThread().start();
			new Thread(new RunnableImpl()).start();
		}
	}

	class MyThread extends Thread {
		public void run() {
			while (true) {
				try {
					Thread.sleep(200);// 父类没有抛出异常,子类调用有抛异常的方法时,只能进行catch处理
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("我是继承Thread创建的进程:"
						+ Thread.currentThread().getName());
			}
		}
	}

	class RunnableImpl implements Runnable {
		public void run() {
			while (true) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("我是实现了Runnable创建的进程:"
						+ Thread.currentThread().getName());
			}
		}
	}
	运行结果:
	我是实现了Runnable创建的进程:Thread-1
	我是继承Thread创建的进程:Thread-0
	我是继承Thread创建的进程:Thread-0
	我是实现了Runnable创建的进程:Thread-1
	我是继承Thread创建的进程:Thread-0
	我是实现了Runnable创建的进程:Thread-1
	我是实现了Runnable创建的进程:Thread-1
	————————————————————————————————————————————————————————————————————
3、线程安全:
	产生原因:
		1. 多个线程在操作共享的数据。
		2. 操作共享数据的线程代码有多条。
		当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
	
	————————————————————————————————————————————————————————————————————
	public class ThreadSafetyDemo {
		public static void main(String[] args) {
			final Function f = new Function();
			new Thread() {
				public void run() {
					f.print();
				};
			}.start();
			new Thread() {
				public void run() {
					f.show();
				};
			}.start();
		}

	}

	class Function {
		public void print() {
			while (true) {

				for(char c:"骚年努力~~".toCharArray())
					System.out.print(c);
				System.out.println();
			}
		}

		public void show() {
			while (true) {
				for(char c:"Hello Java~~".toCharArray())
					System.out.print(c);
				System.out.println();
			}
		}
	}
	输出结果:
	Hello Java~~
	Hello Java~~
	Hel~            //线程安全,只打印了一部分
	骚年努力~~

	骚年努力~lo Java~~  线程安全,打印重叠
	Hello Java~~

	这里的公共资源是控制台。。。
	————————————————————————————————————————————————————————————————————

4、线程安全的解决方案
	1、
	思路:
		将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运
	算。 必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

	在java中,用同步代码块就可以解决这个问题。

	同步代码块的格式:

	synchronized(对象){
		需要被同步的代码;
	} 

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

		PS:为了更容易实现多个线程共同使用一个锁,最好将对公共资源操作的代码,封装成方法,并作为同一个类的成员,
		然后通过run方法来调用这些操作资源的方法

	2、安全问题的另一种解决方案:同步代码块
		格式:在函数上加上synchronized修饰符即可。

	P.S.
		同步函数和同步代码块的区别:
			1. 同步函数的锁是固定的this。
			2. 同步代码块的锁是任意的对象。
		
			由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this
		作为锁,就可以实现同步。

			静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class
		表示。

	————————————————————————————————————————————————————————————————————

	package ticket.demo;

	public class SellDemo2 {
		public static void main(String[] args) {
			MyRunnable m = new MyRunnable(new TicketShared());
			for (int i = 0; i < 3; i++) {
				new Thread(m).start();
			}
		}

	}

	class MyRunnable implements Runnable {
		private TicketShared t;

		public MyRunnable(TicketShared t) {
			super();
			this.t = t;
		}
		@Override
		public void run() {
			while(true){
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				t.dec();
			}
		}
	}

	class TicketShared {
		private int t = 100;

		public synchronized void dec() {
			if (t > 0) {
				t--;
				System.out.println(Thread.currentThread().getName() + "...售出一张,剩余"
						+ t);
			} else {
				System.out.println("票售完了~~");
				System.exit(0);
			}
		}
	}

	package ticket.demo;

	public class SellTicket {

		/**
		 * 卖票程序
		 */
		private int j = 100;

		public static void main(String[] args) {
			SellTicket st = new SellTicket();
			Inc inc = st.new Inc();
			Dec dec = st.new Dec();
			
			new Thread(dec).start();
			new Thread(dec).start();
			new Thread(inc).start();
			new Thread(inc).start();
		}

		public synchronized void inc() {
			j++;
			System.out.println("加票:"+Thread.currentThread().getName() + ".."+j);
		}

		public synchronized void dec() {
			if(j>0){			
				System.out.println("售票:"+Thread.currentThread().getName() + ".."+j);
				j--;
			}
		}

		class Inc implements Runnable {

			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					inc();
				}
			}
		}

		class Dec implements Runnable {

			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					dec();
				}
			}
		}
	}
	————————————————————————————————————————————————————————————————————
5、单利设计模式
	//单利设计模式,单利即只能有一个实例,所以构造函数要私有化

	// 饿汉式:类初始化时就创建实例对象,线程安全
	class SingletonHungryType {
		private static final SingletonHungryType s = new SingletonHungryType();// 类初始化时就创建对象

		private SingletonHungryType() {
		}// 私有化,以实现单利

		public static SingletonHungryType getInstance() {// 返回实例对象
			return s;
		}
	}

	// 懒汉式:类初始化时不创建对象,等函数调用时,才创建。因为涉及可能存在多个线程同时调用创建对象的代码,所以线程不安全,需要实现同步
	class SingletonLazyType1 { // 同步代码块实现
		private static SingletonLazyType1 s = null;

		private SingletonLazyType1() {
		}

		public static SingletonLazyType1 getInstance() {
			if (s == null) {// 在这加判断能提高效率,因为当s已经建立后,线程可以直接跳过同步代码块,而不需要没次都等待锁
							// 这也就是使用同步代码块比同步函数效率高的原因
				synchronized (SingletonLazyType1.class) {
					if (null == s) {
						s = new SingletonLazyType1();
					}
				}
			}
			return s;
		}
	}

	class SingletonLazyType2 { // 同步函数实现
		private static SingletonLazyType2 s = null;

		private SingletonLazyType2() {
		}

		public static synchronized SingletonLazyType2 getInstance() {
			s = new SingletonLazyType2();
			return s;
		}
	}

6、死锁
	/**
	 *        死锁演示
	 */

	public class DeadLock implements Runnable {
			private Object objA = new Object();
			private Object objB = new Object();//锁对象
			private boolean flag = false;
			
	//        DeadLock(boolean b){
	//                this.flag = b;
	//        }
			@Override
			public void run() {
					// 相互嵌套是锁,可能会出现互相等待的现象导致死锁
					while(true){
							if(flag){
									synchronized(objA){//嵌套锁
											System.out.println("if A..."+Thread.currentThread().getName());
											synchronized(objB){
													System.out.println("if B..."+Thread.currentThread().getName());
											}
											flag = flag^true; //flag取反
									}
							}else{
									synchronized(objB){//嵌套锁
											System.out.println("else A..."+Thread.currentThread().getName());
											synchronized(objA){
													System.out.println("else B..."+Thread.currentThread().getName());
																																											
											}
											flag = flag^true;  //flag取反
									}
							}
					}
					
			}
	}

	public class DeadLockTest {

			/**
			 * 用于死锁的测试
			 */
			public static void main(String[] args) {
					// TODO Auto-generated method stub
					//建立资源对象
					DeadLock d1 = new DeadLock();
					//新建两个进程
					new Thread(d1,"线程1").start();
					new Thread(d1,"线程2").start();
			}

	}
	运行结果:
	else A...线程1         //线程1 进入else,获取锁B
	else B...线程1     
	if A...线程1     //线程1执行完else,释放B锁,进入if,获取锁A,在等待获取B锁
	else A...线程2        //线程2 持有B锁,在等待A锁
	//此时出现互相等待,即死锁现象,程序无法继续运行

————————————————————————————————————————————————————————————————————
	import java.util.concurrent.locks.Lock;
	import java.util.concurrent.locks.ReentrantLock;

	//死锁演示,出现嵌套锁时会出现互相等待对方释放锁资源现象
	public class DeadLockDemo {
	public static void main(String[] args) {
		final DeadLock dl = new DeadLock();
		new Thread() {
			public void run() {
				while (true)
					dl.show();
			};
		}.start();
		new Thread() {
			public void run() {
				while (true)
					dl.print();
			};
		}.start();
	}
	}

	class DeadLock {
		private Lock lock1 = new ReentrantLock();
		private Lock lock2 = new ReentrantLock();

		public void show() {
			lock1.lock();
			System.out.println(Thread.currentThread().getName()+"..show...lock1.lock();");
			lock2.lock();
			System.out.println(Thread.currentThread().getName()+"..show...lock2.lock();");
			lock2.unlock();
			lock1.unlock();
		}

		public void print() {
			lock2.lock();
			System.out.println(Thread.currentThread().getName()+"..print...lock2.lock();");
			lock1.lock();
			System.out.println(Thread.currentThread().getName()+"..ptint...lock1.lock();");
			lock1.unlock();
			lock2.unlock();
		}
	}

	运行:
	Thread-0..show...lock1.lock();
	Thread-0..show...lock2.lock();
	Thread-0..show...lock1.lock();
	Thread-1..print...lock2.lock();
	————————————————————————————————————————————————————————————————————
7、线程间通讯
	多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。

	等待/唤醒机制涉及的方法:
		1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
		2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
		3. notifyAll():唤醒线程池中的所有线程。

	P.S.
		1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
		2、必须要明确到底操作的是哪个锁上的线程!
		3、wait和sleep区别?
			1)wait可以指定时间也可以不指定。sleep必须指定时间。
			2)在同步中时,对CPU的执行权和锁的处理不同。
			wait:释放执行权,释放锁。
			sleep:释放执行权,不释放锁。
		
	同步代码块就是对于锁的操作是隐式的。
		JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了
	显示动作。
		Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵
	活,可以一个锁上加上多组监视器。
	lock():获取锁。
	unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
	
	Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了
	封装,变成Condition监视器对象,可以任意锁进行组合。
	
	Condition接口中的await方法对应于Object中的wait方法。
	Condition接口中的signal方法对应于Object中的notify方法。
	Condition接口中的signalAll方法对应于Object中的notifyAll方法。

8、停止线程
	怎么控制线程的任务结束呢?
	任务中都会有循环结构,只要控制住循环就可以结束任务。
	控制循环通常就用定义标记来完成

	P.S.
	也可以使用stop方法停止线程,不过已经过时,不再使用。

		但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
		答:可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。
	强制动作会发生InterruptedException,一定要记得处理。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值