参考资料
概念
http://www.runoob.com/java/java-multithreading.html
上下文切换
https://www.cnblogs.com/xrq730/p/5186609.html
https://blog.csdn.net/u014527912/article/details/78939781
java多线程的同步锁机制
https://www.cnblogs.com/gavin110-lgy/p/5716421.html
http://www.cnblogs.com/hapjin/p/5452663.html
https://www.cnblogs.com/lwbqqyumidi/p/3821389.html
线程生命周期
https://www.cnblogs.com/lwbqqyumidi/p/3804883.html
https://www.cnblogs.com/lwbqqyumidi/p/3817517.html
https://blog.csdn.net/Yoryky/article/details/78515818
https://www.cnblogs.com/sunddenly/p/4106562.html
知识点
- 线程和进程的基本概念
- 线程的生命周期
- 线程的创建和启动
- 守护线程和用户线程
- 线程的调度和优先级
- 上下文切换
- 线程同步
线程同步
概念
举一个例子:某餐厅的卫生间很小,几乎只能容纳一个人如厕。为了保证不受干扰,如厕的人进入卫生间,就要锁上房门。我们可以把卫生间想 象成是共享的资源,而众多需要如厕的人可以被视作多个线程。假如卫生间当前有人占用,那么其他人必须等待,直到这个人如厕完毕,打开房门走出来为止。这就 好比多个线程共享一个资源的时候,是一定要分出先来后到的。有人说:那如果我没有这道门会怎样呢?让两个线程相互竞争,谁抢先了,谁就 可以先干活,这样多好阿?但是我们知道:如果厕所没有门的话,如厕的人一起涌向 厕所,那么必然会发生争执,正常的如厕步骤就会被打乱,很有可能会发生意想不到的结果,例如某些人可能只好被迫在不正确的地方施肥……
由此可知,正是因为有这道门,任何一个单独进入如厕的人都可以顺利的完成他们的如厕过程,而不会被干扰,甚至发生以外的结果。这就是说,如厕的时候要讲究先来后到。
上面这个例子,就是典型的 同步案例,一旦第一位开始如厕,则第二位必须等待第一位结束,才能开始他的如厕过程。一个线程,一旦占用某一资源,其他线程必须等待其使用完资源并释放,才能开始占用。这里,最关键的就是卫生间的门。其实,卫生间的门担任的是资源锁的角色,只要如厕的人锁上门,就相当于获得了这个锁,而当他打开锁出来以后,就相当于释放了这个锁。
由此可知,多线程的线程同步机制实际上是靠锁的概念来控制的。而java的锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。
同步锁synchronized
java引入了对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在一段时间内,只能有一个线程访问该对象。
关键字synchronized与对象的互斥锁联系。当一个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问。在一个线程访问完后,另一个线程才能访问。
synchronized用法
写法一
synchronized(Obj){
......
}
写法二
synchronized void functionName(){
......
}
synchronized锁住的是对象。那么这里的对象指的是哪个对象呢?我们可以通过如下示例体现。
- 示例1
public class ThreadTest0201 extends Thread {
private String threadName;
public ThreadTest0201(String threadName) {
super(threadName);
this.threadName = threadName;
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
for (int idx = 0; idx < 10; idx++) {
new ThreadTest0201(idx + "").start();
Thread.sleep(1);
}
}
public synchronized void run() {
for (int idx_i = 0; idx_i < 100; idx_i++) {
System.out.println("线程名:" + threadName + ";序号" + idx_i);
}
}
}
这个程序是让10个线程在控制台上数数,我们希望前一个线程数完后,下一个线程才能继续数,但经过运行发现,虽然ThreadTest0201的run()上加了同步锁,但结果还是乱的。这是因为synchronized加在成员方法上,这实际上是以这个成员方法所在的对象本身作为对象锁。而例程中每个线程都新建了对象并持有相应的对象锁。这必然不能产生同步的效果。
如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的!要达到希望的效果,可以看如下示例。
- 示例2
public class ThreadTest0202 extends Thread {
private String threadName;
private Object lock;
public ThreadTest0202(String threadName, Object lock) {
super(threadName);
this.threadName = threadName;
this.lock = lock;
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Object lock = new Object();
for (int idx = 0; idx < 10; idx++) {
new ThreadTest0202((idx + ""), lock).start();
Thread.sleep(1);
}
}
public void run() {
synchronized (lock) {
for (int idx_i = 0; idx_i < 100; idx_i++) {
System.out.println("线程名:" + threadName + ";序号" + idx_i);
}
}
}
}
在启动线程之前,创建了一个Object类实例,并把这个实例通过构造方法传给线程类的私有变量lock。将原来run方法前的synchronized关键字去掉,换用了run方法中的一个synchronized块来实现。这个同步块的对象锁,就是main方法中创建的那个Object对象。由此可知,这10个线程类的lock指向的是同一个对象。运行后我们达到了希望的效果。
由示例3可知,对于同步静态方法,对象锁就是该静态方法所在的类的Class实例。
- 示例3
public class ThreadTest0203 extends Thread {
private String threadName;
public ThreadTest0203(String threadName){
this.threadName = threadName;
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int idx = 0;idx < 10;idx++){
(new ThreadTest0203(idx + "")).start();
}
}
public synchronized static void counter(String threadName){
for (int idx_i = 0; idx_i < 100; idx_i++) {
System.out.println("线程名:" + threadName + ";序号" + idx_i);
}
}
public void run(){
counter(threadName);
}
}
示例4说明了不同线程调用同一类的不同加有synchronized锁的方法。因为synchronized关键字锁住的是当前对象,所以线程a和b尽管调用的MyTask的不同方法,但仍然是同步的,一个线程执行完后,另一个线程才能执行。
- 示例4
public class ThreadTest0204 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MyTask task1 = new MyTask();
MyTask task2 = new MyTask();
Thread a = new ThreadMana1(task1);
// Thread b = new ThreadMana1(task1);
Thread b = new ThreadMana1(task2);
a.start();
b.start();
}
}
class MyTask{
public synchronized void methodA() throws Exception{
for(int i = 0;i < 50;i++){
Thread.sleep(100);
System.out.println("方法:A;序号:" + i);
}
}
public synchronized void methodB() throws Exception{
for(int i = 0;i < 50;i++){
Thread.sleep(100);
System.out.println("方法:B;序号:" + i);
}
}
}
class ThreadMana1 extends Thread{
private MyTask task;
public ThreadMana1(MyTask task){
this.task = task;
}
public void run(){
try {
super.run();
task.methodA();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class ThreadMana2 extends Thread{
private MyTask task;
public ThreadMana2(MyTask task){
this.task = task;
}
public void run(){
try {
super.run();
task.methodB();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
synchronized小结
- 如果采用method级别的同步,则对象锁即为method所在的对象,如果是静态方法,对象锁即指method所在的Class对象(唯一);
- 对于代码块,对象锁即指synchronized(abc)中的abc;
- 在示例1中,对象锁即为每一个线程对象,因此有多个,所以同步失效,示例2共用同一个对象锁lock,因此同步生效,示例3因为是static因此对象锁为ThreadTest0203的class 对象,因此同步生效。
- 如果是同步代码块,则对象锁需要编程人员自己指定,一般有些代码为synchronized(this)只有在单态模式才生效;(本类的实例有且只有一个);如果是同步方法,则分静态和非静态两种 。静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。
- 如果一个类中有多个synchronized修饰的同步方法,且多个线程持有该类的同一个对象(该类的相同的对象),尽管它们调用不同的方法,各个方法的执行也是同步的。
死锁
以下示例是产生死锁的例程
- 示例
package indi.thread.part01;
public class MainTest05 implements Runnable {
public int flag = 1;
static Object o1 = new Object(), o2 = new Object();
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
MainTest05 td1 = new MainTest05();
MainTest05 td2 = new MainTest05();
td1.flag = 1;
td2.flag = 0;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
}
生产者消费者问题
问题描述
以生产消费馒头为例:有n个人在做馒头,有m个人在吃馒头,有个容量为c的篓子,做好的馒头放进篓子,要吃馒头从篓子里拿,篓子满了就不做了,篓子空了就不吃了。以下是实现的示例
示例
IStorage
package indi.thread.part03;
public interface IStorage {
// 生产
void push(Commodity comm);
// 消费
Commodity pop();
}
Storage
package indi.thread.part03;
import java.util.LinkedList;
public class Storage implements IStorage {
private LinkedList<Commodity> container = new LinkedList();
private int capacity = 5;
//生产
public synchronized void push(Commodity comm){
int size;
while((size = container.size()) >= capacity){
try {
System.out.println("【要生产的产品数量】:1;\t【库存量】:"
+ container.size() + "\t暂时不能执行生产任务!");
System.out.println("size:" + size);
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
container.add(comm);
System.out.println("【新增产品数】:1;产品ID:" + comm.getComID() + "\t【现仓储量为】:" + container.size());
this.notifyAll();
}
//消费
public synchronized Commodity pop(){
Commodity output = null;
int size;
while((size = container.size()) <= 0){
try {
System.out.println("【要消费的产品数量】:1;\t【库存量】:"
+ container.size() + "\t暂时不能执行消费任务!");
System.out.println("size:" + size);
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
output = container.remove((size - 1));
System.out.println("【消费产品数】:1;产品ID:" + output.getComID() + "\t【现仓储量为】:" + container.size());
this.notifyAll();
return output;
}
}
Commodity
package indi.thread.part03;
/**
* 商品类
* @author 61940
*
*/
public class Commodity {
private int comID = 0;
private static int maxComID = 0;
public int getComID() {
return comID;
}
public void setComID(int comID) {
this.comID = comID;
}
public Commodity(){
maxComID++;
this.comID = maxComID;
}
}
Producer(生产者)
package indi.thread.part03;
public class Producer extends Thread {
private Storage currentStorage = null;
public Producer(Storage currentStorage){
this.currentStorage = currentStorage;
}
public void run(){
try {
currentStorage.push(new Commodity());
Thread.sleep((long) (Math.random() * 200));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Consumer (消费者)
package indi.thread.part03;
/**
* 消费者
* @author 61940
*
*/
public class Consumer extends Thread{
private Storage currentStorage = null;
public Consumer(Storage currentStorage){
this.currentStorage = currentStorage;
}
public void run(){
try {
currentStorage.pop();
Thread.sleep((long) (Math.random() * 200));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
测试类
package indi.thread.part03;
public class ProducerCustomerTest {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Storage storage9100 = new Storage();
for(int i = 0;i < 10;i++){
(new Producer(storage9100)).start();
}
for(int i = 0;i < 20;i++){
(new Consumer(storage9100)).start();
}
}
}