现在的位置: 首页 > 综合 > 正文

java、多线程的理解和应用

2018年05月06日 ⁄ 综合 ⁄ 共 6265字 ⁄ 字号 评论关闭

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

一、进程
     进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程  本身从产生、发展到最终消忙的过程。多进程操作系统能同时运行多个进程(程序),由于CPU具备分时机制,所以每个进程都能循环会的自己的CPU时间片,由于CPU执行速度非常快,使得所有程序好像是在"同时"运行一样。

二、线程
     进程在执行过程中,可能需要多个任务同时执行,每个任务的执行者就是线程。线程就是进程中的一个执行控制单元。程的运行时间分派由 CPU来决定,如果多有多个线程,那么CPU也不会有顺序的去执行它们,而是随机的。当一个线程运行一段时间之后,应该主动阻塞,这样CPU就会把资源分派给其他线程。如果线程不合作,自己不主动阻塞,那么CPU会在一段时间之后强行停止这个线程的运行,把资源分派给其他线程。CPU会为每个线程都指定一个时间分片,当时间片用完时,线程还不阻塞,那么就强行停止它。

三、线程的状态
      1.新状态
          Thread t = new Thread();
    刚刚new出来的线程对象,还没有调用start()方法。

      2.就绪状态(可运行状态)
          t.start()之后中,线程进入就绪状态!
    在某一个时间点上,只有一个线程是运行着的,其它的线程都没有运行,所以我们说start()之后 不是“运行状态”,而是“就绪状态”。线程什么时候从就绪运行,这由CPU来决定, CPU会给运行的线程一个时间片,这个时间用完,如果线程还没有主动阻塞,那么CPU会强行停止运行,给其他线程运行机会。

     
3.运行状态

         由CPU决定,CPU会在就绪状态的线程中随机选择一个,给其运行的时间片。运行的线程,应该主动进入阻塞状态,这样给其他线程运行的时间。

     
4.阻塞状态

         休眠:Thread.sleep(1000),当线程执行了sleep()方法,那么这个线程就进入了休眠状态。休眠的线程必须要等到指定的毫秒过完,才能返回到就绪状态。
         等待:当前线程执行了wait()方法,进入了对象的等待列表中。只能期待其他线程调用notify()或notifyAll()来唤醒这个等待的线程。
         挂起:调用了该线程的suspend()方法,那么这个线程就挂起了。这时就期待该线程的resume()被调用,才能回到就绪状态。但是,这两个方法都被作废了,你不应该使用它们!      

        
IO阻塞:
当线程正在完成IO操作,那么这个线程也就阻塞了。直到IO操作完成了!
         锁定: 当线程要使用的对象被其他线程使用时,那么这个线程进入了锁定状态。直到线程得到了要使用的对象后,那么就回到就绪状态。

     
5.死亡状态

         run()方法结束,正常死亡!
         run()中抛出了异常,因为而死亡!
         run()被杀了,有人调用这个线程的stop()方法。stop()方法被作废了!

四、多线程
      所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行形成多条执行线索。一个进程可能包含了多个同时执行的。

     1、线程的特点:
         (1)、线程是指程序的运行流程
         (2)、"多线程“的机制是指可以同时运行多个程序块,使程序运行的效率变得更高,也可以克服传统程序语言所无法解决的问题

     2.创建线程的两种方式;

       继承Thread实例1:

public class Test {
	public static void main(String[] args) {
		//创建线程
		ThreadTest tt = new ThreadTest();
		//开启线程
		tt.start();
		for (int i = 0; i < 5; i++) {
			System.out.println("主线程在运行");
		}
	}
}

//ThreadTest类继承了Thread类,此类实现了多线程
class ThreadTest extends Thread {
	//重写run方法(必须重写)
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("次线程在运行");
		}
	}
}

结果:

主线程在运行
主线程在运行
次线程在运行
次线程在运行
主线程在运行
主线程在运行
次线程在运行
次线程在运行

总结:1.此类继承了Thread类
        2.重写run方法
        3.唯一的缺点就是只能单继承

实现接口Runnable实例2:

public class Test {
	public static void main(String[] args) {
		// 运用匿名内部类来实现多线程,让外部类继承Thread类,让该内部类实现Runnable接口
		new Thread(new Runnable() {
			// 从写run方法
			public void run() {
				for (int i = 0; i < 5; i++) {
					System.out.println("002线程在运行");
				}
			}
		}).start();//线程开启
		// 这是main方法线程
		for (int i = 0; i < 5; i++) {
			System.out.println("001线程在运行");
		}
	}
}

结果:

结果:
001线程在运行
001线程在运行
002线程在运行
002线程在运行
001线程在运行
001线程在运行
002线程在运行
002线程在运行

总结 1.让外部继承了Thread类
        2.让内部类实现了Runnable接口
        3.重写run方法
        4.创建线程

五、线程同步
 同步的目的:用来处理多个线程共享同一数据时,所造成的错误。
 如何处理这种情况呢?有三种同步方式

    方式1,使用同步块:
 它的语法2 synchronized块的语法格式
   synchronized(监视器对象) {
        ……
    }
  
   注意:共享数据不可能是局部变量,因为局部变量,都是每个线程都有一份自己的拷贝!共享数据是属性。

public class ThreadDemo {

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) {
		//创建线程t110
		ThreadaAS t110 = new ThreadaAS();
		//创建线程t111
		Thread111 t111 = new Thread111();
		//开启线程
		t110.start();
		t111.start();

	}
}
//创建线程aAs,继承了Thread类
class ThreadaAS extends Thread {
	//重写Thread类的run()方法
	public void run() {
		//输出9个a
		for (int a = 0; a < 10; a++) {
			System.out.print("a");
		}
	}
}
//创建线程111,继承了Thread类
class Thread111 extends Thread {
	//重写Thread类的run()方法
	public void run() {
		//输出9个b
		for (int a = 0; a < 10; a++) {
			System.out.print("b");
		}
	}
}
//结果:aaaaaabbbbbbbbbbaaaa   (没有使用同步块效果)

有同步块的效果synchronized

public class ThreadDemo {

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) {
		//创建共享数据
		Abc a = new Abc();
		//将数据添加进线程t110
		ThreadaAS t110 = new ThreadaAS(a);
		//将数据添加进线程t111
		Thread111 t111 = new Thread111(a);
		//开启线程
		t110.start();
		t111.start();

	}
}
//创建执行线程ThreadaAs,继承了Thread类
class ThreadaAS extends Thread {
	//创建类类型引用
	private Abc abc;
	//创建有参构造函数,接受共享数据
	public ThreadaAS(Abc abc) {
		this.abc = abc;
	}
	//重写run方法
	public void run() {
		//当线程执行时,锁住CPU执行权,等执行完毕,再释放执行权
		synchronized (abc) {
			abc.fun1();
		}
	}
}

class Abc {
	//定义方法1
	public void fun() {
		for (int i = 0; i < 6; i++) {
			System.out.print("b");
		}
	}
	//定义方法2
	public void fun1() 
		for (int i = 0; i < 6; i++) {
			System.out.print("a");
		}
}

class Thread111 extends Thread {
	//创建类类型引用
	private Abc abc;
	//创建有参构造函数,接受共享数据
	public Thread111(Abc abc) {
		super();
		this.abc = abc;
	}
	//重写run方法
	public void run() {
         //当线程执行时,锁住CPU执行权,等执行完毕,再释放执行权
	    synchronized (abc) {
		abc.fun();
	   }
	}
}

结果:aaaaaabbbbbb

总结:synchronized块的作用,a和b两个线程在同步块内容上同步互斥了,当a进入同步块后,b需要等待a退出后才能进入同步块。

六、单列设计模式的安全问题
 相信大家在学单列的时候,都知道饿汉式相对于懒汉式来说安全些,那是什么原因造成懒汉式不安全呢?
    下面我写出代码并作出相应的回答,相信大家一看便知
                     注意:
                              懒汉式与饿汉式有什么不同?
                              实例延迟加载,
                              懒汉式的延迟加载有没有问题?
                              有,如果多线程访问的时候,会出现安全问题。
                              怎么解决?
                              使用同步代码块解决,但是会有些低效。可以使用双重判断解决效率问题。
                              加同步的时候使用的锁是哪一个?
                              使用的是该类的字节码文件对象。
实例:

class SingleDemo {
	// 私有化构造函数,让外界无法new新的对象
	private SingleDemo() {
	}

	// 创建自己类的对象
	private static SingleDemo s = null;

	// 创建一个新的对象
	public void show() {
		System.out.println("这是懒汉式");
	}

	// 对外定义一个访问的的方法,返回一个对象
	public static SingleDemo getSingle() {
	    if (s == null) 
			synchronized (SingleDemo.class) {
				if (s == null)
					s = new SingleDemo();// 如果不将new的对象赋给S,这样就编程了2个对象
			}
		return s;// 返回类类型对象
	  }

	}

总结:双重判断的原理://判断如果为空执行下一步,进入同步后,只能进一个线程,其他的线程,在外面等候,当第一个线程创建了一个后,第二个进去的线程再进去判断的后,就有值,这样就不会出现安全问题了

七、线程间的通讯
   是指多个线程之间需要协调工作,通讯方法被调用的前提是:同步环境!
 线程的思路:
    A1说:--去玩会吧--
    B说:--我去拿篮球--
         A1说:--Go!Go!Go!--

实例: 

public class ThreadDemo7 {

	/**
	 * @param args
	 *            演示线程间的通讯 需求: 1号线程进入同步块,然后被wait()
	 *            2号线程进入同步块,休眠10秒,然后调用notify(),唤醒1号 1号线程被唤醒,说声GO!GO!GO!
	 */
	public static void main(String[] args) {
		A1 a1 = new A1();
		B b = new B();
		a1.start();
		b.start();
	}

}

/*
 * 静态同步块:静态同步方法互斥,在类中有一个静态同步方法,其他的都必须等它执行完毕后,才能执行
 */

class A1 extends Thread {
	//重写run方法
	public void run() {
		//使线程同步,共享数据是Object类文件
		synchronized (Object.class) {
			try {
				System.out.println("A1说:--去玩会吧--");
				//让该线程进入阻塞状态,并释放CPU执行权,没人叫它,它下面的代码永远不会执行
				Object.class.wait();
				System.out.println("A1说:--Go!Go!Go!--");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class B extends Thread {

	public void run() {
		synchronized (Object.class) {
			System.out.println("B说:--我去拿篮球--");
			//唤醒线程池中的沉睡线程
			Object.class.notify();
			try {
			//让该线程进入阻塞状态,2000毫秒,并释放CPU执行权,2000毫秒后,执行权可以回来执行
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}
}

总结:线程间通讯小结
使用wait()、notify()、notifyAll()方法可以完成线程间的通讯,可叫它们通讯方法;
只能在同步环境下调用通讯方法;
只能使用共享数据调用通讯方法;
每个共享数据都有一个线程监狱:执行a.wait()的线程会被关押到a对象的线程监狱中;
若想释放出a对象的线程监狱中的线程,那么需要调用a.notify()文法,该方法只能保证在a对象的线程监狱中释放出一个线程,但不能保证释放的是哪一个;
还可以使用a.notifyAll()方法释放出a对象的监狱中关押的所有线程。
被wait()了的线程不能自己恢复到就绪状态,只能等待其他线程调用同一监视器对象上的notify()或notifyAll()方法来唤醒。
被wait()了的线程会释放共享数据的数据锁,这样其他线程就可以进入他占用的同步环境。
被唤醒的线程恢复到了就绪状态,当再次获取监听器对象的锁后会在wait()处向下运行。

----------- android培训java培训、java学习型技术博客、期待与您交流! ------------

抱歉!评论已关闭.