Java小白修炼手册--第二阶段--Java SE--多线程

目录

多线程基础

进程与线程:

什么是进程:

什么是线程:

进程与线程的区别:

线程使用的场合:

并发原理:

线程的状态:

创建线程:

使用Thread创建并启动线程:

使用Runnable创建并启动线程:

使用内部类创建线程:

线程操作API:

获取线程信息:

线程优先级:

守护线程:

sleep方法:

yield方法:

线程同步:

Synchronized关键字:

锁机制:

选择合适的锁对象:

选择合适的锁范围:

静态方法锁:

线程池:

ExecutorService:


多线程基础

进程与线程:

什么是进程:

进程是操作系统中运行的一个任务(一个应用程序运行在个进程中)。进程( process)是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。进程中所包含的一个或多个执行单元称为线程( thread )。进程还拥有一个私有的虚拟地址空间 ,该空间仅能被它所包含的线程访问。线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程后, 该进程会 自动申请一个名为主线程或首要线程的线程。


什么是线程:

一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一-组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。-个进程中可以包含多个线程。


进程与线程的区别:

一个进程至少有一个线程。线程的划分尺度小于进程,使得多线程程序的并发性高。另外, 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。从逻辑角度来看,多线程的意义在于一个应用程序中有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配。


线程使用的场合:

线程通常用于在一个程序中需要同时完成多个任务的情况。我们可以将每个任务定义为一个线程,使他们得以一同工作。也可以用于在单一线程中可以完成,但是使用多线程可以更快的情况。比如下载文件。


并发原理:

多个线程”同时”运行只是我们感官上的一种表现。事实上线程是并发运行的, OS将时间划分为很多时间片段(时间片) , 尽可能均匀分配给每一个线程,获取时间片段的线程被CPU运行,而其他线程全部等待。所以微观上 是走走停停的 ,宏观 上都在运行。这种现象叫并发,但是不是绝对意义上的“同时发生”。


线程的状态:

  1. 新建状态(New):
    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  2. 就绪状态(Runnable):
    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

  3. 运行状态(Running):
    当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  4. 阻塞状态(Blocked):
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
    • 同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
    • 所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
  5. 死亡状态(Dead):
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
  • 有两个原因会导致线程死亡:
  1. run方法正常退出而自然死亡,
  2. 个未捕获的异常终止了run方法而使线程猝死。为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

 

各种状态下可能出现的问题

sleepwait的区别

我们都知道的是对于sleepwait都是会让线程出现暂停执行的状态,下面从几个方面进行剖析个体区别

对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。

sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。

在调用 sleep()方法的过程中,线程不会释放对象锁。

而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

使用的位置不同:对于wait来说使用之前要获取到锁的存在,所以必须放在同步代码,或者同步中进行执行 但是 sleep来说可以放在任何的地方执行 。

sleep需要捕获异常 。wait notify 等不需要这些。

startrun的区别

start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。

通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。对于多线程来说只有真正意义上调用了start方法才算是对于线程的一个启动。

方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。

join()

join()方法使调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行。注意该方法也需要捕捉异常。

就是说让该线程在执行完RUN()方法以后再执行join方法后面的代码,就是说可以让两个线程合并起来,用于实现同步功能

yield()

该方法与sleep() 类似 只不过不能够由用户指定暂停多长的时间,并且yield ()方法只能让同优先级的线程有执行的机会。 前面提到了 sleep不会释放锁标识yield也不会释放锁标识。

实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程。

sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会。

wait()和notify()、notifyAll()

这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。wait() 方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。notifyAll() 从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

wait,notify阻塞唤醒确切过程?在哪阻塞,在哪唤醒?为什么要出现在同步代码块中,为什么要处于while循环中?

常见的 void wait 方法有

wait( long timeout )wait()。对于 无参的方法来说 : 在其他线程调用 此对象的notify 方法或者 nofifyall方法前 导致当前的线程处于等待的状态。

对于有参的函数来说 以上的两条成立的情况下 还会在时间超时之前也是处于等待的状态。

对于在执行完 wait方法以后。线程会释放掉所占用的锁标识 从而使线程所在的对象中的其他synchronized数据可被别的线程使用。 因为在执行wait notify() 时候需要对锁标志进程处理和操作 一个是释放锁 一个是加锁 所以 就是来说 需要要在 synchronized函数中或者 函数块中进行调用,如果不在函数中 或是函数块中进行调用 虽然说可以编译通过。但是会出现 IllegalMonitorStateException异常。

wait,notify 和notifyAll 这些方法为什么不在 thread类里面

一个很明显的原因是Java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程来获得 由于 wait notifynotifyAll 都是锁级别的的操作,所以把他们定义在Object类中因为锁属于对象。

 

创建线程:

使用Thread创建并启动线程:

Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。其中重写run方法的目的是定义该线程要执行的逻辑。启动线程时调用线程的start(方法而非直接调用run()方法。start()方法会将 当前线程纳入线程调度使当前线程可以开始并发运行。当线程获取时间片段后会自动开始执行run方法中的逻辑。

public class ThreadDemo1 {
	public static void main(String[] args) {		
		Thread t1 = new MyThread1();
		Thread t2 = new MyThread2();
		/*
		 * 启动线程要调用start方法,而不是run方法
		 */
		t1.start();
		t2.start();
	}
}
/**
 * 第一种创建线程的方式的优点是定义简单,适合匿名内部类形式创建。
 * 缺点主要有两个:
 * 1:java是单继承的,这导致继承了Thread就无法再继承其他类去复用方法了,这在
 *   实际开发时很不方便。
 * 2:将任务定义在线程中,会导致线程和任务存在必然的耦合关系,不利于线程的重用  
 * @author Xiloer
 *
 */
class MyThread1 extends Thread{
	public void run() {
		for(int i=0;i<1000;i++) {
			System.out.println("你是谁啊?");
		}
	}
}
class MyThread2 extends Thread{
	public void run() {
		for(int i=0;i<1000;i++) {
			System.out.println("我是查水表的!");
		}
	}
}


使用Runnable创建并启动线程:

实现Runnable接口]并重写run方法来定义线程体,然后在创建线程的时候将Runnable的实例传,入并启动线程。这样做的好处在于可以将线程与线程要执行的任务分离开减少耦合,同时java是单继承的,定义一个类实现Runnable接口这样的做法可以更好的去实现其他父类或接口。因为接口是多继承关系。

 

package thread;
/**
 * 第二种创建线程的方式:
 * 实现Runnable接口单独定义线程任务
 * @author Xiloer
 *
 */
public class ThreadDemo2 {
	public static void main(String[] args) {
		//创建任务
		Runnable r1 = new MyRunnable1();
		Runnable r2 = new MyRunnable2();
		
		//创建线程
		Thread t1 = new Thread(r1);
		Thread t2 = new Thread(r2);
		
		t1.start();
		t2.start();
	}
}

class MyRunnable1 implements Runnable{
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println("hello~姐~");
		}
	}	
}
class MyRunnable2 implements Runnable{
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println("来了老弟!");
		}
	}	
}


使用内部类创建线程:

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

package thread;
/**
 * 使用匿名内部类形式创建线程
 * @author Xiloer
 *
 */
public class ThreadDemo3 {
	public static void main(String[] args) {
		Thread t1 = new Thread() {
			public void run() {
				for(int i=0;i<1000;i++) {
					System.out.println("你是谁啊?");
				}
			}
		};	
		
		Runnable r2 = ()->{
			for(int i=0;i<1000;i++) {
				System.out.println("我是查水表的!");
			}
		};
		Thread t2 = new Thread(r2);
		
		
		t1.start();
		t2.start();
		
	}
}

线程操作API:

获取线程信息:

Thread提供了获取线程信息的相关方法:

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


 

 

 

 

 

 

 

 

public class CurrentThreadDemo {
	public static void main(String[] args) {
		/*
		 * static Thread currentThread()
		 * 线程的静态方法currentThread可以获取运行该方法的线程 
		 */
		Thread t = Thread.currentThread();//获取运行main方法的线程
		System.out.println("运行main方法的线程是:"+t);
		dosome();//main方法调用dosome方法		
		Thread t2 = new Thread() {
			public void run() {
				Thread t = Thread.currentThread();
				System.out.println("自定义线程:"+t);
				dosome();//定义线程t2调用dosome方法
			}
		};
		t2.start();	
	}	
	public static void dosome() {
		//获取运行dosome方法的线程
		Thread t = Thread.currentThread();
		System.out.println("运行dosome方法的线程:"+t);
	}
}
public class ThreadInfoDemo {
	public static void main(String[] args) {
		Thread t = Thread.currentThread();
		
		String name = t.getName();
		System.out.println("线程名字:"+name);
		
		long id = t.getId();
		System.out.println("唯一标识:"+id);
		
		int priority = t.getPriority();
		System.out.println("优先级:"+priority);
		
		//查看当前线程是否还活着
		boolean isAlive = t.isAlive();
		System.out.println("isAlive:"+isAlive);
		//查看线程是否为守护线程
		boolean isDaemon = t.isDaemon();
		System.out.println("isDaemon:"+isDaemon);
		//查看线程是否被中断了
		boolean isInterrupted = t.isInterrupted();
		System.out.println("isInterrupted:"+isInterrupted);
		
		
	}
}

 

线程优先级:

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

public class PriorityDemo {
	public static void main(String[] args) {
		Thread max = new Thread() {
			public void run() {
				for(int i=0;i<10000;i++) {
					System.out.println("max");
				}
			}
		};
		Thread norm = new Thread() {
			public void run() {
				for(int i=0;i<10000;i++) {
					System.out.println("nor");
				}
			}
		};
		Thread min = new Thread() {
			public void run() {
				for(int i=0;i<10000;i++) {
					System.out.println("min");
				}
			}
		};
		//线程优先级1最低,5默认,10最高
		max.setPriority(Thread.MAX_PRIORITY);
		min.setPriority(Thread.MIN_PRIORITY);
		
		min.start();
		norm.start();
		max.start();
		
	}
}


守护线程:

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

public class DaemonThreadDemo {
	public static void main(String[] args) {
		Thread rose = new Thread() {
			public void run() {
				for(int i=0;i<5;i++) {
					System.out.println("rose:let me go!");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
					}
				}
				System.out.println("rose:啊啊啊啊啊AAAAAaaaa....");
				System.out.println("噗通!");
			}
		};
		
		Thread jack = new Thread() {
			public void run() {
				while(true) {
					System.out.println("jack:you jump!i jump!");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
					}
				}
			}
		};
		rose.start();
		//设置守护线程必须在该线程启动前进行!
		jack.setDaemon(true);
		jack.start();
		
		
	}
}


sleep方法:

Thread的静态方法sleep用于使当前线程进入阻塞状态:
       static void sleep(long ms);
该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前线程会重新进入Runnable状态,等待分配时间片。该方法声明抛出一个InterruptException。所以在使用该方法时需要捕获这个异常。

public class SleepDemo {
	public static void main(String[] args) {
		System.out.println("程序开始了...");	
		/*
		 * 实现一个倒计时程序,程序启动后要求输入一个整数,然后每秒递减,到0时
		 * 输出时间到。
		 */
		Scanner scanner = new Scanner(System.in);
		System.out.println("请输入一个整数:");
		int num = scanner.nextInt();
		while(num>0) {
//		for(;num>0;) {
			try {
				System.out.println(num--);
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}		
		}
		System.out.println("时间到!");
		System.out.println("程序结束了!");
	}
}
public class SleepDemo2 {
	public static void main(String[] args) {
		Thread lin = new Thread() {
			public void run() {
				System.out.println("林:刚美完容,睡一会吧...");
				try {
					Thread.sleep(50000000);
				} catch (InterruptedException e) {
					System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!");
				}
				System.out.println("林:醒了!");
			}
		};
		
		Thread huang = new Thread() {
			public void run() {
				System.out.println("黄:开始砸墙!");
				for(int i=0;i<5;i++) {
					System.out.println("黄:80!");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {}
				}
				System.out.println("咣当!");
				System.out.println("黄:搞定!");
				lin.interrupt();//中断lin线程的睡眠阻塞
			}
		};
		lin.start();
		huang.start();
	}
}


yield方法:

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

public class JoinDemo {
	static boolean isFinish = false;//标识图片是否下载完毕
	
	public static void main(String[] args) {
//		boolean isFinish = false;
		/*
		 * java中有一个语法要求:当一个方法的局部内部类中使用了这个方法的其他局部
		 * 变量时,该变量必须声明为final的。
		 * 以当前案例为例:
		 * main方法的局部内部类download中使用main方法的其他局部变量isFinish,
		 * 那么isFinish就必须声明为final的。
		 * 在JDK8之后,final可以不写,但是该特性依然存在,因此不可以在局部内部类
		 * 中对该变量赋值。
		 */
		Thread download = new Thread() {
			public void run() {
				System.out.println("开始下载图片...");
				for(int i=1;i<=100;i++) {
					System.out.println("down:"+i+"%");
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
					}
				}
				System.out.println("图片下载完毕!");
				isFinish = true;//下载完毕!
			}
		};
		
		Thread show = new Thread() {
			public void run() {
				try {
					System.out.println("show:开始显示文字...");
					Thread.sleep(2000);
					System.out.println("show:文字显示完毕!");
					
					System.out.println("show:开始显示图片...");
					
					System.out.println("show:等待down...");
					//download.join();//show线程会进入阻塞状态,直到download结束为止
					System.out.println("show:等待down完毕!");
					
					if(!isFinish) {
						throw new RuntimeException("图片加载失败!");
					}
					System.out.println("show:图片显示完毕!");
				} catch (Exception e) {
					e.printStackTrace();
				}			
			}
		};
		download.start();
		show.start();
	}
}

线程同步:

Synchronized关键字:

多个线程并发读写同一个临界资源时会发生”线程并发安”全问题。
常见的临界资源:

  1. 多线程共享实例变量
  2. 多线程共享静态公共变量

若想解决线程安全问题,需要将异步的操作变为同步操作。
异步操作:多线程并发的操作,相当于各干各的。
同步操作:有先后顺序的操作,相当于你干完我再干。
synchronized关键字是java中的同步锁。
 

public class SyncDemo1 {
	public static void main(String[] args) {
		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{
	private int beans = 20;//桌子上有20个豆子
	/**
	 * 当一个方法被synchronized修饰后,该方法称为同步方法,即:多个线程不能同时
	 * 在方法内部执行。将异步调用方法改为同步调用可以解决并发安全问题。
	 * @return
	 */
	public synchronized int getBean() {
		if(beans==0) {
			throw new RuntimeException("没有豆子了!");
		}
		Thread.yield();
		return beans--;
	}
}

锁机制:

Java提供了一种内置的锁机制来支持原子性:同步代码块(synchronized关键字) ,同步代码块包含两部分:一个作为锁的对象的引用,一个作为由这个锁保护的代码块。
synchronized (同步监视器一锁对象引用){
          //代码块

}
若方法所有代码都需要同步也可以给方法直接加锁。每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而且无论是通过正常途径退出还是通过抛异常退出都样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
 

public class SyncDemo2 {
	public static void main(String[] args) {
		Shop shop1 = new Shop();
		Shop shop2 = new Shop();
//		Shop shop = new Shop();
		Thread t1 = new Thread() {
			public void run() {
				shop1.buy();//两个线程不存在"抢"的现象,没有并发安全问题
//				shop.buy();
			}
		};
		Thread t2 = new Thread() {
			public void run() {
				shop2.buy();
//				shop.buy();
			}
		};
		t1.start();
		t2.start();
	}
}

class Shop{
//	public void buy() {//没有任何同步约束时性能最好,但是存在并发安全问题
	/*
	 * 在方法上使用synchronized,那么指定的同步监视器对象就是this
	 */
	public synchronized void buy() {//解决了并发安全问题,但是性能不好。
//	public void buy() {
		try {
			Thread t = Thread.currentThread();
			System.out.println(t.getName()+":正在挑衣服...");
			Thread.sleep(5000);
			/*
			 * 同步块使用时要求必须指定同步监视器对象:()中的内容
			 * 该对象可以是java中任何类型的实例,只需要保证一点:多个需要同步执行
			 * 的线程看到的这个对象必须是同一个!
			 */
//			synchronized (new Object()) {//没有同步效果的!
//			synchronized (this) {
				System.out.println(t.getName()+":正在试衣服...");
				Thread.sleep(5000);
//			}
			
			
			System.out.println(t.getName()+":结账离开!");
		} catch (Exception e) {
		}
	}
}

选择合适的锁对象:

使用synchroinzed需要对一个对象上锁以保证线程同步。那么这个所对象应当注意:多个需要同步的线程在访问该同步块时,看到的应该是同一个所对象引用。否则达不到同步效果。通常我们会使用this来作为锁对象。

选择合适的锁范围:

在使用同步块时,应当尽量在允许的情况下减少同步范围以提高并发的执行效率。
 

静态方法锁:

当我们对一个静态方法加锁,如:
public synchronized static void xxx...}
那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。静态方法与非静态方法同时声明了synchronized ,他们之间是非互斥关系的。原因在于,静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。
 

/**
 * 成员方法上使用synchronized修饰后,锁对象为该方法所属对象this
 * 但是静态方法不同,静态方法所属类,全局就一份,因为静态方法上使用synchronized后
 * 该方法一定具有同步效果。而它指定的锁对象为当前类的类对象(Class的一个实例。)
 * 

 *
 */
public class SyncDemo3 {
	public static void main(String[] args) {
		Thread t1 = new Thread() {
			public void run() {
				Boo.dosome();
			}
		};
		Thread t2 = new Thread() {
			public void run() {
				Boo.dosome();
			}
		};
		t1.start();
		t2.start();
	}
}

class Boo{
	public synchronized static void dosome() {
		try {
			Thread t = Thread.currentThread();
			System.out.println(t.getName()+":正在执行dosome方法...");
			Thread.sleep(5000);
			System.out.println(t.getName()+":执行dosome方法完毕!");
		} catch (Exception e) {
		}
	}
}

互斥锁:

互斥锁
 当使用多个synchronized锁定多个代码片段时,这些synchronized指定的同步监视器对象
 是同一个时,那么这些代码片段就是互斥的,多个线程不能同时执行写几个代码片段。

public class SyncDemo4 {
	public static void main(String[] args) {
//		Foo f1 = new Foo();
//		Foo f2 = new Foo();
		Foo foo = new Foo();
		Thread t1 = new Thread() {
			public void run() {
//				f1.methodA();//与下面线程的f2.methodB没有互斥,锁对象不同
				foo.methodA();
			}
		};
		Thread t2 = new Thread() {
			public void run() {
//				f2.methodB();
				foo.methodB();
			}
		};
		t1.start();
		t2.start();
	}
}

class Foo{
	public void methodA() {
		/*
		 * 这样写仍然与下面的methodB方法有互斥效果,因为这里指定的同步监视器对象
		 * 是this,也就是methodA方法所属对象,而methodB上直接写synchronized时
		 * 指定的也是this。
		 * 那么此时两个线程分别调用同一个Foo对象的methodA和methodB方法时就是互斥的。
		 */
		synchronized (this) {
			try {
				Thread t = Thread.currentThread();
				System.out.println(t.getName()+":正在执行A方法...");
				Thread.sleep(5000);
				System.out.println(t.getName()+":执行A方法完毕!");
			} catch (Exception e) {
			}
		}		
	}
	public synchronized void methodB() {
		try {
			Thread t = Thread.currentThread();
			System.out.println(t.getName()+":正在执行B方法...");
			Thread.sleep(5000);
			System.out.println(t.getName()+":执行B方法完毕!");
		} catch (Exception e) {
		}
	}
}

 

线程池:

ExecutorService:

使用ExecutorService实现线程池:

ExecutorService是java提供的用于管理线程池的类。
线程池有两个主要作用:

  1. 控制线程数量
  2. 重用线程

当一个程序中若创建大量线程,并在任务结束后销毁,会给系统带来过度消耗资源,以及过度切换线程的危险,从而可能导致系统崩溃。为此我们应使用线程池来解决这个问题。
 

线程池的概念:首先创建一-些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程而是将该线程还回到线程池中。在线程池的编程模式下,任务是提交给整个线程池而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
 

package thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池
 * 线程池:是管理线程的一套解决方案,主要工作:
 *  1:控制线程数量:
 *  	线程的数量过多会消耗大量的内存,有可能引发内存溢出崩溃,并且线程的数量
 *  过多会导致CPU的过度切换,从而降低整体并发行性能
 * 	2:重用线程:
 * 	线程不应当与任务的生命周期一致,重复使用线程可以减少线程调度器的不必要开销
 * @author 
 *
 */
public class ThreadPoolDemo {
	public static void main(String[] args){
		ExecutorService threadPool = Executors.newFixedThreadPool(2);
		
		for(int i=0;i<5;i++){
			Runnable r = new Runnable(){
					public void run() {
						
						try {
							Thread t = Thread.currentThread();
							System.out.println(t.getName()+"正在执行任务.....");
							Thread.sleep(5000);
							System.out.println("任务执行完毕!"+t.getName());
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
			};
			threadPool.execute(r);//将任务交给线程池
			System.out.println("指派了一个任务给线程...");
		}
//		threadPool.shutdownNow();//强制中断线程执行 回抛中断异常InterruptedException: sleep interrupted
		threadPool.shutdown();//任务执行完才会停止线程
		System.out.println("停止线程池");
		
	}
}

线程池有以下几种实现策略:

- Executors.newCachedThreadPool( );
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
-Executors.newFixedThreadPool(int nThreads);
创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。
- Executors.newScheduledThreadPool(int corePoolSize);
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
-Executors.newSingleThreadExecutor( );
创建一个使用单个worker线程的Executor ,以无界队列方式来运行该线程。
 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值