多线程学习笔记

114 阅读5分钟

文章转载自:多线程学习笔记

线程与进程

进程的概念

进程是操作系统结构的基础;是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上执行的过程,它是系统进行资源分配和调度的一个独立单位。 ——百度百科 *简单地说,进程可以理解为正在运行的程序,完全可以将运行在内存中的exe文件理解成进程,进程是受操作系统管理的基本运行单元。*如通过 Ctrl + Alt + .的快捷键 ->启动任务管理器 -> 进程,就可以看到进程,基本上在运行中的exe程序都可以看成进程。 这里写图片描述

线程的概念

**线程可以理解成是在进程中独立运行的子任务。**比如,QQ.exe运行时就有很多的子任务在同时运行。再如,在使用word编辑文档时,可以一边听音乐一边下载电影一边给好友传输资料等,这些不同的任务或者说功能都可以同时运行,其中每一项任务完全可以理解成是“线程”在工作,编辑文档、听音乐、下载电影、发送图片等功能都有对应的线程在后台摸摸地运行。

下图是单任务与多任务运行的方式,从图中可以看出:在单任务运行环境中,后面的任务必须在前面的任务运行完之后才能进行,也就是说单任务的特点是排队执行,也就是同步。而在多任务环境中,不同的任务可以同时进行,即使用多线程后可以在同一时间内运行更多不同种类的任务。由此可以得出:但任务环境的缺点是,CPU利用率很低。多线程技术的优点是异步执行多个任务,大大地提高CPU利用率。 单任务与多任务 注意:多线程是异步的,所以千万不要把eclipse中代码的顺序当成线程执行的顺序,线程被调用的时机是随机的

多线程的使用

一个进程在运行时至少会有一个线程在运行,比如调用public static void main(String[] args)方法时,JVM就会创建一个相应的线程在后台摸摸地执行。 实现多线程编程的方式主要有两种:继承Thread类和实现Runnable接口。

线程安全和数据共享的两个示例:

package org.sym.threadsafe;

/**
 * 本程序使用synchronized关键字修饰方法,保证多线程在对类对象的同一个变量进行修改时,能够按顺序获取该同步锁,保证值修改的一致性
 * @author sym
 * @since 2018.3.11 21:46
 *
 */


class MyThread01 extends Thread{
	int count = 5;
	/* (non-Javadoc)
	 * @see java.lang.Thread#run()
	 */
	@Override 
	synchronized public void run(){     
		//要想实现多个线程对同一个变量count进行同步数据共享(修改的结果不重复且依次递减),需要在方法前加上关键字synchronized
		count--;
		//此示例中不要使用for循环,因为使用同步后其他线程就得不到运行的机会了。可以一直由一个线程进行减法运算
		System.out.println("由 " + Thread.currentThread().getName() + " 计算, count=" + count);
		//虽然使用synchronized关键字修饰方法,可以保证对其中变量修改的一致性,但并不能保证线程调用顺序按照线程实例化的顺序执行
	}
}

public class SynchronizedThread01 {
	public static void main(String[] args){
		MyThread01 mythread = new MyThread01();
		Thread a = new Thread(mythread, "A");	
		//原型为Thread(Runnable target, String name),由于Thread类实现了Runnable,所以在向该类中传入Thread对象时,相当于Thread => Runnable向上转型
		Thread b = new Thread(mythread, "B");
		Thread c = new Thread(mythread, "C");
		Thread d = new Thread(mythread, "D");
		Thread e = new Thread(mythread, "E");
		a.start();					//启动线程
		b.start();
		c.start();
		d.start();
		e.start();
	}
}


其中一次的运行结果如下。从结果中可以看到:虽然使用关键字synchronized修饰方法,可以保证方法中变量修改的一致性,但是由于线程被调用的时机是随机的,所以使用多线程技术时,代码的运行结果与代码的执行顺序或调用顺序是无关的(本例中从前面的线程名的顺序与调用的线程顺序不一致可以看出)。 这里写图片描述

另一个线程安全的实例:

package org.sym.threadsafe;

/**
 * 本程序使用synchronized关键字修饰方法,表示要想执行该同步方法必须先要拿到这把锁,
 * 这样当有多个线程对该类的同一个对象中的同一个变量进行操作时,就不会出现只修改后不同步的“非线程安全”现象
 * @author sym
 * @version v1.0
 * @since 2018.3.11 21:26
 *
 */

//本类模拟成一个Servlet组件
class LoginServlet {
	private static String usernameRef;
	private static String passwordRef;
	synchronized public static void doPost(String username, String password){//使用synchronized关键字可以保证多线程修改变量的一致性
		try{
			usernameRef = username;
			if(username.equals("a")){
				Thread.sleep(1000);   //休眠1s
			}
			passwordRef = password;
			System.out.println("username = " + usernameRef + " password = " + password);
			//虽然使用synchronized关键字修饰方法,可以保证对其中变量修改的一致性,但并不能保证线程调用顺序按照线程实例化的顺序执行
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

class ALogin extends Thread{
	@Override
	public void run(){
		LoginServlet.doPost("a", "aa");		//静态方法可以直接通过其类名调用,而不需要使用实例对象调用
	}
}

class BLogin extends Thread{
	@Override
	public void run(){
		LoginServlet.doPost("b", "bb");
	}
}
public class SynchronizedThread02 {
	public static void main(String[] args){
		ALogin a = new ALogin();
		a.start();
		BLogin b = new BLogin();
		b.start();
	}
}

其中一次运行的结果: 这里写图片描述

注意:虽然使用synchronized关键字修饰方法,可以保证对其中变量修改的一致性,但并不能保证线程调用顺序按照线程实例化的顺序执行