java多线程基础(synchronize关键字)

[toc] 基础知识

线程:进程(process)就是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。
线程:进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。
线程和进程的区别如下:
1)一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
2)线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
3)从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配。

2 简述线程的状态及其转换
1)New,创建一个线程,但是线程并没有进行任何的操作。
2)Runnable,新线程从New状态,调用start方法转换到Runnable状态。线程调用start方法向线程调度程序(JVM或者是操作系统)注册一个线程,这个时候一切就绪只等CPU的时间。
3)Running,从Runnable状态到Running状态,线程调度根据调度策略的不同调度不同的线程,被调度执行的线程进入Running状态,执行run方法。
4)Dead状态,从Running状态到Runnable,run方法运行完毕后,线程就会被抛弃,线程就进入Dead状态。
5)Block状态,从Running状态到Block状态,如果线程在运行的状态中因为I/O阻塞、调用了线程的sleep方法以及调用对象的wait方法则线程将进入阻塞状态,直到这些阻塞原因被结束,线程进入到Runnable状态。

3 简述线程的两种创建方式以及它们的区别
创建线程的两种方式:
1)使用Thread创建线程。Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。其中重写run方法的目的是定义该线程要执行的逻辑。启动线程时调用线程的start()方法而非直接调用run()方法。start()方法会将当前线程纳入线程调度,使当前线程可以开始并发运行。当线程获取时间片段后会自动开始执行run方法中的逻辑。
2)使用Runnable创建线程。实现Runnable接口并重写run方法来定义线程体,然后在创建线程的时候将Runnable的实例传入并启动线程。
两种创建线程方式的区别:
使用Thread创建线程,编写简单,可以直接操纵线程,无需使用Thread.currentThread(),但是不能够再继承其他类。
使用Runnable创建线程可以将线程与线程要执行的任务分离开减少耦合,同时Java是单继承的,定义一个类实现Runnable接口,这样该类还可以继承自其他类。

多线程实现方法

使用Thread创建线并启动线程

       java.lang.Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。其中 重写run方法的目的是定义该线程要执行的逻辑。启动线程时调用线程的start()方法而非直接调用run()方法。start()方法会将当前线程纳入线程调 度,使当前线程可以开始并发运行。当线程获取时间片段后会自动开始执行run方法中的逻辑。
public class TestThread extends Thread{
	@Override
	public void run() {
		for(int i=0;i<100;i++){
		System.out.println("我是线程");
		}
	}
}

创建预启动线程

	…
	Thread thread = new TestThread();//实例化线程
	thread.start();//启动线程
	…

使用Runnable创建并启动线程

实现Runnable接口并重写run方法来定义线程体,然后在创建线程的时候将Runnable的实例传入并启动线程。 这样做的好处在于可以将线程与线程要执行的任务分离开减少耦合,同时java是单继承的,定义一个类实现Runnable接口这样的做法可以更好的 去实现其他父类或接口。因为接口是多继承关系。
public class TestRunnable implements Runnable{
 @Override
 public void run() {
	 for(int i=0;i<100;i++){
		 System.out.println("我是线程");
		 }
	 }
 }

启动线程的方法:

	 …
	 Runnable runnable = new TestRunnable();
	 Thread thread = new Thread(runnable);//实例化线程并传入线程体
	 thread.start();//启动线程
	 …

使用内部类创建线程

通常我们可以通过匿名内部类的方式创建线程,使用该方式可以简化编写代码的复杂度,当一个线程仅需要一个实例时我们通常使用这种方式来
创建。
例如:
继承Thread方式:

 Thread thread = new Thread(){ //匿名类方式创建线程
 public void run(){
	 //线程体
	 }
 };
 thread.start();//启动线程

Runnable方式:

	Runnable runnable = new Runnable(){ //匿名类方式创建线程
		 public void run(){
		 }
	};
	 Thread thread = new Thread(runnable);
	 thread.start();//启动线程

线程的方法

currentThread:方法可以用于获取运行当前代码片段的线程

Thread current = Thread.currentThread();

获取线程信息
Thread提供了 获取线程信息的相关方法:
     long getId():返回该线程的标识符
     String getName():返回该线程的名称
     int getPriority():返回线程的优先级
     Thread.state getState():获取线程的状态
     boolean isAlive():测试线程是否处于活动状态
     boolean isDaemon():测试线程是否为守护线程
     boolean isInterrupted():测试线程是否已经中断

线程优先级

线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片的几率。 线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级: Thread.MIN_PRIORITY, Thread.MAX_PRIORITY, Thread.NORM_PRIORITY 设置优先级的方法为:
void setPriority(int priority)

守护线程

守护线程与普通线程在表现上没有什么区别,我们只需要通过Thread提供的方法来设定即可: **void setDaemon(boolean )** 当参数为true时该线程为守护线程。 守护线程的特点是,当进程中只剩下守护线程时,所有守护线程强制终止。 GC就是运行在一个守护线程上的。 需要注意的是,设置线程为后台线程要在该线程启动前设置。
	Thread daemonThread = new Thread();
	daemonThread.setDaemon(true);
	daemonThread.start();

sleep方法

Thread的静态方法sleep用于使当前线程进入阻塞状态: **static void sleep(long ms)** 该方法会使当前线程进入阻塞状态指定毫秒,当指定毫秒阻塞后,当前线程会重新进入Runnable状态,等待分配时间片。 该方法声明抛出一个InterruptException。所以在使用该方法时需要捕获这个异常 **注:**改程序可能会出现"跳秒"现象,因为阻塞一秒后线程并非是立刻回到running状态,而是出于runnable状态,等待获取时间片。那么这段等待 时间就是"误差"。所以以上程序并非严格意义上的每隔一秒钟执行一次输出。

yield方法:

Thread的静态方法yield: **static void yield()** 该方法用于使当前线程主动让出当次CPU时间片回到Runnable状态,等待分配时间片。

join方法

**void join()** 该方法用于等待当前线程结束。此方法是一个阻塞方法。 该方法声明抛出InterruptException。

线程同步

synchronized关键字
多个线程并发读写同一个临界资源时候会发生"线程并发安全问题“
常见的临界资源:
多线程共享实例变量
多线程共享静态公共变量
若想解决线程安全问题,需要将异步的操作变为同步操作。
所谓异步操作是指多线程并发的操作,相当于各干各的。
所谓同步操作是指有先后顺序的操作,相当于你干完我再干。

同步代码块(synchronized 关键字 ),同步代码块包含两部分:一个作为锁的对象的引用,一个作为由这个锁保护的代码块
这个比较难理解故写了下面代码帮助理解

/**
 * 多线程并发安全问题
 * 当多个线程同时操作同一资源时,由于
 * 线程切换时机不确定,导致出现逻辑混乱。
 * 严重时可能导致系统崩溃。
 * @author ylg
 *
 */
public class SyncDemo1 {
	public static void main(String[] args) {
		/*
		 * 当一个方法中的局部内部类想引用该方法
		 * 的其他局部变量时,这个变量必须被声明
		 * 为final的
		 */
		final Table table = new Table();
		Thread t1 = new Thread(){
			public void run(){
				while(true){
					int bean = table.getBean();
					Thread.yield();//模拟线程切换
					System.out.println(
						getName()+":"+bean	
					);
				}
			}
		};
		Thread t2 = new Thread(){
			public void run(){
				while(true){
					int bean = table.getBean();
					Thread.yield();//模拟线程切换
					System.out.println(
						getName()+":"+bean	
					);
				}
			}
		};
		t1.start();
		t2.start();
	}
}
class Table{
	//20个豆子
	private int beans = 20;
	/**
	 * 当一个方法被synchronized修饰后,该方法
	 * 成为"同步方法"。多个线程不能同时进入到
	 * 方法内部。
	 * @return
	 */
	public synchronized int getBean(){
		if(beans==0){
			throw new RuntimeException("没有豆子了!");
		}
		Thread.yield();//模拟线程切换
		return beans--;
	}
}
/**
 * 有效的缩小同步范围可以保证在
 * 安全的前提下提高了并发的效率
 * @author ylg
 *
 */
public class SyncDemo2 {
	public static void main(String[] args) {
		final Shop shop = new Shop();
		Thread t1 = new Thread(){
			public void run(){
				shop.buy();
			}
		};
		Thread t2 = new Thread(){
			public void run(){
				shop.buy();
			}
		};
		t1.start();
		t2.start();
	}
}
class Shop{
	/*
	 * 在方法上使用synchroinzed,同步监视器对象即当前方法所属对象:this
	 */
//	public synchronized void buy(){
	public void buy(){
		try{
			Thread t = Thread.currentThread();
			System.out.println(t+"正在挑选衣服..");
			Thread.sleep(5000);
			/*
			 * 同步块可以缩小同步范围。
			 * 但是必须保证"同步监视器"即:"上锁对象"是同一个才可以。
			 * 通常,在一个方法中使用this所谓同步监视器对象即可。
			 */
			synchronized (this) {
				System.out.println(t+"正在试衣服..");
				Thread.sleep(5000);
			}		
			
			System.out.println(t+"结账离开");
		}catch(Exception e){
		}
		
	}
}
/**
 * synchronized也成为"互斥锁"
 * 当synchronzed修饰的是两段代码,但是"锁对象"相同时,这两段代码就是互斥的。
 * @author ylg
 *
 */
public class SyncDemo4 {
	public static void main(String[] args) {
		final Boo b = new Boo();
		Thread t1 = new Thread(){
			public void run(){
				b.methodA();
			}
		};
		Thread t2 = new Thread(){
			public void run(){
				b.methodB();
			}
		};
		t1.start();
		t2.start();
	}
}

class Boo{
	public synchronized void methodA(){
		Thread t = Thread.currentThread();
		System.out.println(t+"正在调用方法A");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
		}
		System.out.println(t+"调用方法A完毕");
	}
	public synchronized void methodB(){
		Thread t = Thread.currentThread();
		System.out.println(t+"正在调用方法B");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
		}
		System.out.println(t+"调用方法B完毕");
	}
}```

**wait和notify**
多线程之间需要协调工作。
例如,浏览器的一个显示图片的 displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。如果图片还
没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,
displayThread继续执行。
以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于
wait/notify。等待机制与锁机制是密切关联的
posted @ 2017-08-20 10:05  杨洛平  阅读(809)  评论(0编辑  收藏  举报