java学习笔记(6)-多线程(1)

标签(空格分隔): 笔记


一、基本概念###

1.1 程序、进程、线程

  • 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
  • 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
  1. 如运行中的QQ,运行中的MP3播放器
  2. 程序是静态的,进程是动态的
  3. 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
  • 线程(thread):,进程可进一步细化为线程,是一个程序内部的一条执行路径。
  1. 若一个进程同一时间并行执行多个线程,就是支持多线程的
  2. 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
  3. 一个进程中的多个线程共享相同的内存单元/内存地址空间--->它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。

1.2 并行与并发

  • 并行:许多cpu同时执行多个任务
  • 并发:一个cpu同时执行多个任务

1.3 使用多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  1. 提高计算机系统CPU的利用率
  2. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

1.4 何时需要多线程

  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。

二、线程的创建和使用###

1.继承Thread类,然后重写run函数,不需要直接调用他,这个函数是JVM自动调用的。

public class MyThread1Test extends Thread{
	@Override
	public void run() {
		for(int i=0;i<2000;i++) {
			System.out.println("Threa666666666666");
		}
	}
}

2.实现Runnable接口,重写run函数,同理也不需要调用这个run函数,如果调用了系统就会把它当成普通函数。

public class MyRunnableTest implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int j=0;j<2000;j++) {
			System.out.println("Runnable********执行");
		}
	}
}

3.调用上述两个方法(普通外部类),启动多线程

	Thread t1=new MyThread1Test();
	t1.start();//
	Thread t2=new Thread(new MyRunnableTest()); 
	t2.start();

4.通过匿名内部类实现

new Thread() {
			public void run() {
				for(int i=0;i<500;i++) {
					System.out.println("Threa666666666666");
				}
			}
		}.start();
		
new Thread(new Runnable() {
			
		@Override
		public void run() {
			// TODO Auto-generated method stub
			for(int j=0;j<500;j++) {
				Thread.yield();
				System.out.println("Runnable********执行");
			}
		}
	}).start();

5.通过成员内部类实现

public class th3 extends Thread{
		@Override
		public void run() {
			// TODO Auto-generated method stub
			for(int i=0;i<2000;i++) {
				System.out.println("Threa666666666666");
			}
		}
		
	}//把这个类放在一个类的内部,然后调用,调用过程略,之后直接start就行。

6.注意点:

  • 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  • run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。
  • 想要启动多线程,必须调用start方法。
  • 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。

7.线程的调度
调度策略

  • 时间片策略
  • 抢占式:高优先级的线程抢占CPU ,可以设置优先级,默认是5,自己可以设置1-10,数字越大优先级越高,但是并不意味着优先级越高就一定会先执行,只是先执行的概率要大一点

Java的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略

8.线程的分类

  • 用户线程
  • 守护线程,(例如java的垃圾回收)是伴随着用户线程的存活而存活 当用用户线程死亡的时候 守护线程不论是否执行结束 都会死亡(不会立即死亡,而是运行一段时间再死亡),调用的时候在start前声明setDaemon(true)
  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。

三、线程的生命周期###

  • 新建:当创建了一个线程之后 new Thread 则当前线程就处于新建状态
  • 就绪:当调用了线程的start方法 则线程就进入到就绪状态 准备着与其他线程抢夺CPU的执行权
  • 运行:当线程获得CPU的执行全 处于执行中的线程 就是运行状态
  • 阻塞:线程在执行过程中,因为某些原因 失去了CPU的执行权 暂时被挂起 但是线程还没有结束 则线程就处于阻塞状态
  • 死亡:线程任务执行完成 或者被 强制终止 则线程进入到死亡状态

线程的五种状态时可以相互转换的

四、线程的同步###

例如:使用多线程模拟火车站售票程序,开启三个窗口售票。

public class Tick implements Runnable{
	private  static int  num = 100;
	@Override
	public void run() {
		 while(true) {
			 if(num > 0) {
				 try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				 System.out.println(Thread.currentThread().getName()+"销售车票座位号为:" + num--); 
			 }else {
				 break;
			 }
			
		 }
	}

}
class TicketDemo { 
    public static void main(String[] args) { 
        Ticket t = new Ticket(); 
        Thread t1 = new Thread(t); 
        Thread t2 = new Thread(t); 
        Thread t3 = new Thread(t); 
        t1.setName("t1窗口"); 
        t2.setName("t2窗口"); 
        t3.setName("t3窗口"); 
        t1.start(); 
        t2.start(); 
        t3.start(); 
    } 
}

备注:上述代码会出现很多问题,比如会出现卖的票数编号重复,甚至出现负数编号的车票,例如当票数只剩一张的时候,一个线程执行,判断票数大于零之后进入了休眠状态,此时二号线程也进入了,因为一号线程休眠中,并未对票数进行修改,所以二号线程判断票数也是大于0,依次类推,当这些线程依次休眠结束后,自然会产生0,-1,-2这些不符合常理的票数编号。

  • 上述问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
  • 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。这就叫线程同步

1.线程同步实现
1.1同步代码块
同步代码块用synchronized这个关键字去实现,注意实现的时候不要和static搭配。同步代码块可以放在静态方法中和非静态方法中。

放在非静态方法中,此时的锁对象可以是任意对象 只需要保证所有的线程使用的是同一个锁对象 this也可以充当锁对象,但是必须提前创建好这个对象,如下所示

//------------------------------------------------------------
public class PrintTest {
	public static void main(String[] args) {
		PrintTest p1=new PrintTest();//如果可以将此行注释掉,然后把下面的两行注释解除,在看看执行效果。
		new Thread() {
			@Override
			public void run() {
			//	PrintTest p1=new PrintTest();//解除此行注释看看效果
			
				p1.print1();
			}
		}.start();
		//----------------------------
		new Thread() {
			@Override
			public void run() {
			//	PrintTest p2=new PrintTest();////解除此行注释看看效果,但要把下面的p1换成p2.
				p1.print2();
			}
		}.start();
	}
	//----------------------------------
	public  void print1() {
		while(true) {
			synchronized(this) {
		System.out.print("中");
		System.out.print("国");
		System.out.print("科");
		System.out.print("技");
		System.out.print("大");
		System.out.print("学");
		System.out.println();
		}
		}
	}
	//------------------------------------
	public  void print2() {
		while(true) {
		synchronized(this) {
		System.out.print("北");
		System.out.print("大");
		System.out.print("青");
		System.out.print("鸟");
		System.out.println();
	}
	}
	}
}

静态代码块放在静态方法中 可以使用静态对象来作为锁对象,例如字符串,本类的class对象,保证所有的线程使用的是同一个锁对象

public class PrintTest {
	public static void main(String[] args) {
		new Thread() {
			@Override
			public void run() {
				print1();
			}
		}.start();
		new Thread() {
			@Override
			public void run() {
				
				print2();
			}
		}.start();
	}
	
	
	public  static void print1() {
		while(true) {
			synchronized(PrintTest.class) {
		System.out.print("中");
		System.out.print("国");
		System.out.print("科");
		System.out.print("技");
		System.out.print("大");
		System.out.print("学");
		System.out.println();
		}
		}
	}
	
	public static void print2() {
		while(true) {
		synchronized(PrintTest.class) {
		System.out.print("北");
		System.out.print("大");
		System.out.print("青");
		System.out.print("鸟");
		System.out.println();
	}
	}
	}
}

对于非静态同步方法 锁对象为this,可以通过下面的同步代码块和非静态同步方法证明

public class PrintTest {
	
	public static void main(String[] args) {
		PrintTest p1=new PrintTest();
		new Thread() {
			@Override
			public void run() {
				p1.print1();
			}
		}.start();
		new Thread() {
			@Override
			public void run() {
				while(true) {
				p1.print2();
				}
			}
		}.start();
	}
	
//处于非静态方法中的同步代码块	
	public  void print1() {
		while(true) {
			synchronized(this) {
		System.out.print("中");
		System.out.print("国");
		System.out.print("科");
		System.out.print("技");
		System.out.print("大");
		System.out.print("学");
		System.out.println();
		}
		}
	}
	//非静态同步方法
	public synchronized void print2() {
		System.out.print("北");
		System.out.print("大");
		System.out.print("青");
		System.out.print("鸟");
		System.out.println();
	}
	
}

对于静态同步方法 锁对象为字节码对象 ,本类的class对象,可以通过下面来验证:

public class PrintTest02 {
	
	

	public static void main(String[] args) {
		PrintTest02 p2=new PrintTest02();
		Thread t1=new Thread() {
			@Override
			public void run() {
				while(true) {
				print1();
				}
			}
		};
		Thread t2=new Thread() {
			public void run() {
				while(true) {
				p2.print2();
			}
			}
		};
		
		t2.start();
		t1.start();

	}
	//静态同步方法
	public static synchronized void print1() {
		System.out.print("中");
		System.out.print("国");
		System.out.print("科");
		System.out.print("技");
		System.out.print("大");
		System.out.print("学");
		System.out.println();	
	}
	//下面这个函数加不加staic结果都一样
	public   void print2() {
		while(true) {
		synchronized(PrintTest02.class) {
		System.out.print("北");
		System.out.print("大");
		System.out.print("青");
		System.out.print("鸟");
		System.out.println();
	}
	}
	}
	}
posted @ 2019-11-28 18:07  夜空守望者Z  阅读(174)  评论(0编辑  收藏  举报