进程:CPU为每个应用程序分配的独立空间,一个进程可能有多个Thread。进程为Thread中的摸个Execute任务程序,多个进程之间可以进行共享Data。而JAVA的Thread则是由JVM进程分配的,mainMethod所在Thread,则成为主Thread。

〇、线程状态

正常情况ThreadExecute步骤:新建-》Run-》死亡

当CPU资源不够时,CUP分配给各个Thread的资源可能不同(貌似有点像是Thread在抢资源,实际是CPU分配资源给每个Thread)。 因此就会出现Thread的阻塞、休眠、等待3个状态。其中阻塞状态,当cpu资源够时,阻塞状态的Thread可能恢复到Run状态,而休眠、等待的Thread也可能进入Run状态。休眠、等待状态可能转换成阻塞状态,但是阻塞状态不会变成休眠、等待状态。

Thread状态图.png

一、多线程的实现

0、线程常用方法
package learn.javase.threads;
/**
 * 继承Thread创建线程,设置线程名称、获取线程名称
 * @author Jole
 * */
public class ThreadDemo01 {

	public static void main(String[] args) {
		MyThreadsDemo mt = new MyThreadsDemo();
		mt.setName("Hi");
		mt.start();
		//获取当前线程
		Thread t = Thread.currentThread();
		System.out.println(t.getName());

	}

}

ThreadImplementation Method主Test类:

package learn.javase.threads;

public class MyThreadsDemo extends Thread{
	public MyThreadsDemo() {
		super("Google");
	}
	
	public void run() {
		for(int i=0;i<5;i++) {
			try {
				Thread.sleep(1000);
				System.out.println(i);
			}catch(Exception e) {
				System.out.println(e.getMessage());
			}
			
		}
	}
}

ThreadTest主类:

package learn.javase.threads;
/**
 * 实现多线程的4中方式:
 * 1、继承Thread类
 * 2、实现Runnable接口
 * 3、匿名内部类
 * 4、匿名接口
 * 5、实现Callable接口,与Runnable的区别:
 * 可以有返回值
 * 可以抛异常
 * @author Jole
 * */
public class ThreadNumberDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//方式1,继承Thread
		new ThreadThread1().start();
		
		//方式2,实现Runnable接口
		new Thread(new ThreadRunnables2()).start();
		
		//方式3,匿名内部类
		new Thread() {
			public void run() {
				System.out.println("匿名内部类,实现多线程");
			}
		}.start();
		
		//方式4,匿名接口
		Runnable r = new Runnable() {
			public void run() {
				System.out.println("匿名接口,实现多线程");
			}
		};
		new Thread(r).start();
		
		//方式4的简洁版
		new Thread(new Runnable() {
			public void run() {
				System.out.println("匿名接口简洁版,实现多线程");
			}
		}).start();
	}

}
1、继承Thread
package learn.javase.threads;
/**
 * 方式一继承Thread实现多线程
 * @author Jole
 * */
public class ThreadThread1 extends Thread{

	public void run() {
		System.out.println("extends ....thread");
	}
}
2、实现Runnable接口
package learn.javase.threads;
/**
 * 实现Runnable接口
 * @author Jole
 * */
public class ThreadRunnables2 implements Runnable{
	public void run() {
		System.out.println("runnable .. threads");
	}

}
3、实现Callable

通过UsageThread Pool,ImplementationCallable,主要用于有Return值和抛出异常的。如下的Thread PoolImplementation。

4、区别

继承ThreadInterface与ImplementationRunnableInterface,Implementation多Thread区别:

  • 1、单继承、多Implementation
  • 2、ImplementationRunnable可以将Thread与任务(run())解耦
package learn.javase.threads;
/**
 * 实现Runnable接口,创建线程。
 * 继承Thread接口与实现Runnable接口,实现多线程区别:
 * 1、单继承、多实现
 * 2、将线程与任务(run())解耦
 * @author Jole
 * */
public class RunnableDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Thread(new RunnableThreadDemo()).start();
		for(int i=0;i<5;i++) {
			System.out.println("main..."+i);
		}
	}

}

ThreadImplementation类:

package learn.javase.threads;

public class RunnableThreadDemo implements Runnable{

	public void run() {
		for(int i=0;i<5;i++) {
			System.out.println("run..."+i);
		}
	}
}

二、线程池

1、线程池使用
package learn.javase.threads;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 线程池
 * @author Jole
 * */
public class ThreadPoolDemo {

	public static void main(String[] args) {
		//使用线程池工场Executors,创建线程池
		ExecutorService es = Executors.newFixedThreadPool(2);
		
		//使用线程池,构造器为实现Runnable的接口,使用完后自动放回线程池
		es.submit(new ThreadPoolThreas());
		es.submit(new ThreadPoolThreas());
		
		//如果线程池里面线程不够了,只有等待其它线程执行完后,在使用
		es.submit(new ThreadPoolThreas());
		
		//一般不用关闭线程池,特殊情况关闭线程池,可以使用
//		es.shutdown();
	}

}

Thread PoolThreadImplementationRunnableInterface:

package learn.javase.threads;

public class ThreadPoolThreas implements Runnable{

	public void run() {
		System.out.println(Thread.currentThread().getName()+"线程池的使用");
	}
}
2、线程池-实现多线程
package learn.javase.threads;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
 * 通过线程池,实现Callable接口,实现多线程,并获取任务(run)返回值,和异常
 * @author Jole
 * */
public class ThreadPoolDemo2 {

	public static void main(String[] args) throws Exception{
		//使用线程池工场Executors创建线程池工场
		ExecutorService es = Executors.newFixedThreadPool(2);
		
		//通过实现Callable实现线程,提交并返回Future对象
		Future t = es.submit(new ThreadCallableDemo());
		//通过get获取返回值
		System.out.println(t.get());
	}

}

Implementation类CallableInterface类:

package learn.javase.threads;

import java.util.concurrent.Callable;
/**
 * * @author Jole
 * 使用继承Callable<T>泛型接口,实现多线程,并使用线程池
 */
public class ThreadCallableDemo implements Callable<String>{

	public String call() {
		return "实现Callable接口";
	}
}
3、实例

Example1

package learn.javase.threads;

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

/**
 * 使用线程池实现异步提交求和:1+...100=   1+... 200=
 * 注意:
 * 有返回值
 * 计算不同的值
 * @author Jole
 * */
public class ThreadMainDemo1 {

	public static void main(String[] args) throws Exception{
		//使用线程池工场Executors创建线程池
		ExecutorService es = Executors.newFixedThreadPool(3);
		//使用线程池中的线程,并使用构造方法传参,求和并返回
		Future<Integer> f =es.submit(new CallableDemo(100));
		
		Future<Integer> f2 =es.submit(new CallableDemo(2100));
		
		System.out.println("1+...100="+f.get());
		System.out.println("1+...200="+f2.get());
		
		//关闭线程池
		es.shutdown();
	}

}

Implementation类:

package learn.javase.threads;

import java.util.concurrent.Callable;
/**
 * 通过实现Callable接口,及构造方法传参,并通过call返回值
 * @author Jole
 * */
public class CallableDemo implements Callable<Integer>{
	
	private int number;
	
	//创建构造方法,为了传参
	public CallableDemo(int number) {
		this.number = number;
	}
	
	public Integer call() {
		int sum = 0;
		//使用构造方法传的参数求和并返回
		for(int i=0;i<=number;i++) {
			sum=sum+i;
		}
		return sum;
	}
}

Example2:

package learn.javase.threads;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
 * 通过线程池,实现Callable接口,实现多线程,并获取任务(run)返回值,和异常
 * @author Jole
 * */
public class ThreadPoolDemo2 {

	public static void main(String[] args) throws Exception{
		//使用线程池工场Executors创建线程池工场
		ExecutorService es = Executors.newFixedThreadPool(2);
		
		//通过实现Callable实现线程,提交并返回Future对象
		Future t = es.submit(new ThreadCallableDemo());
		//通过get获取返回值
		System.out.println(t.get());
	}

}

Implementation类:

package learn.javase.threads;

import java.util.concurrent.Callable;
/**
 * * @author Jole
 * 使用继承Callable<T>泛型接口,实现多线程,并使用线程池
 */
public class ThreadCallableDemo implements Callable<String>{

	public String call() {
		return "实现Callable接口";
	}
}

三、线程安全

1、线程安全
package learn.javase.threads;
/**
 * 可能出现安全问题的情况:多线程,共享一个数据
 * 线程安全问题,及解决办法加synchronized,实例:卖票
 * @author Jole
 * synchronized解决线程安全问题,使用公式:
 * synchronized(锁(任意对象)){
 * 代码块
 * }
 * * 注意:因为加了锁,所以效率会降低,但是安全性得到了保证,效率低的原因如下:
 * 1、每次线程都会先判断是否有锁
 * 2、获取锁
 * 3、执行完代码块
 * 4、执行完后,还会锁
 * 5、在等下一个锁执行相同操作,相当于只能排队上一个厕所。
 */
public class ThreadSeaerfDemo {

	public static void main(String[] args) {
		
		//多线程出现的安全问题,票有负数还在卖
//		Tickets00 t = new Tickets00();
//		Thread t0 = new Thread(t);
//		Thread t1 = new Thread(t);
//		Thread t2 = new Thread(t);
//		t0.start();t1.start();t2.start();

		//原始代码
//		Tickets01 t = new Tickets01();
//		Thread t0 = new Thread(t);
//		Thread t1 = new Thread(t);
//		Thread t2 = new Thread(t);
//		t0.start();t1.start();t2.start();
		
		//第一次优化代码:同步方法,同步锁对象为:this
//		Tickets02 t = new Tickets02();
//		Thread t0 = new Thread(t);
//		Thread t1 = new Thread(t);
//		Thread t2 = new Thread(t);
//		t0.start();t1.start();t2.start();
		
		
		//第一次优化代码:同步静态方法,同步锁对象为:类名.class
		Tickets03 t = new Tickets03();
		Thread t0 = new Thread(t);
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t0.start();t1.start();t2.start();
		
		
	}

}

多线Thread出现的Problem

package learn.javase.threads;
/** 〇、原始多线程,会出现安全问题,会出现票卖完了还在卖,也就是会出现票为负数的情况
 * @author Jole
 * */
public class Tickets00 implements Runnable{
	//票的数量
	private int ticket = 100;
	public void run() {
		while(true) {
			if(ticket>0) {
				//为了展示出现线程安全问题,此处让线程修改20毫秒
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//每次票减1
				System.out.println(Thread.currentThread().getName()+ " " + ticket--);
			}
		}
	}
}
2、线程安全解决方案

解决多ThreadProblem方案UsageSynchronizationLock,1、Usagesynchronized 2、UsageLockInterface

SynchronizationLockImplementation Principle图: SynchronizationLockPrinciple.png

1、synchronized

Usagesynchronized解决ThreadSafetyProblem,Usage公式:

 synchronized解决线程安全问题,使用公式:
 synchronized(锁(任意对象)){
       代码块
 }

1.1、synchronized加在外面:SynchronizationLock对象为obj

package learn.javase.threads;
/** 一、原始线程同步解决,同步锁为obj
 * * synchronized解决线程安全问题,使用公式:
 * synchronized(锁(任意对象)){
 * 代码块
 * }
 * @author Jole
 * */
public class Tickets01 implements Runnable{
	//票的数量
	private int ticket = 100;
	Object obj = new Object();
	public void run() {
		while(true) {
			//为了解决安全问题加入了synchronized关键字
			synchronized (obj) {
				if(ticket>0) {
					//为了展示出现线程安全问题,此处让线程修改20毫秒
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					//每次票减1
					System.out.println(Thread.currentThread().getName()+ " " + ticket--);
				}
			}
		}
	}
}

1.2、synchronized加在非静态Method上:此时SynchronizationLock对象为this

package learn.javase.threads;
/**
 * 二、优化原始的线程安全解决办法,同步锁方法,同步锁对象为this
 * @author Jole
 * */
public class Tickets02 implements Runnable{
	//总票数
	private int tickets =100;
	//原始同步锁
//	Object obj = new Object();

	public void run() {
		bay();
	}
	
	//第一次优化,将锁和代码块抽为一个方法,测试同步锁为obj
//	public void bay() {
//		while(true) {
//			synchronized (obj) {
//				if(tickets > 0) {
//					try {
//						Thread.sleep(20);
//					}catch(Exception e) {
//						e.printStackTrace();
//					}
//					System.out.println(Thread.currentThread().getName()+" " +tickets-- );
//				}
//			}
//		}
//	}
	
	//第二次进一步优化为,同步方法,此时同步锁为:this
	//测试就可以省去以前的obj对象的创建了,代码以前更占用少一点内存了
	public synchronized void bay() {
		while(true) {
			if(tickets > 0) {
				try {
					Thread.sleep(20);
				}catch(Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" " + tickets--);
			}
		}
	}
	
	
}

1.3、synchronized加在静态Method上:此时SynchronizationLock对象为:类名.class

package learn.javase.threads;
/**
 * 静态同步方法,此时同步锁为:类名.class
 * 如此类中的同步锁对象是:Tickets03.class
 * @author Jole
 * */
public class Tickets03 implements Runnable{

	//总票数
	private static int tickets =100;
	public void run() {
		buy();
	}
	
	//静态方法同步锁,此时代码的锁为:Tickets03.class
	public static synchronized void buy() {
		while(true) {
			if(tickets > 0) {
				try {
					Thread.sleep(20);
				}catch(Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" " + tickets--);
			}
		}
	}
}
2、Lock

Usagesynchronized时,GetLock,释放Lock都是JVM自动完成的,而通过Lock可以手动GetLock,释放Lock。

package learn.javase.threads;
/**
 * 手动获取锁,释放锁
 * @author Jole
 * */
public class ThreadShouDong {

	public static void main(String[] args) {
		
		ThreadRunnableDemo01 t = new ThreadRunnableDemo01();
		Thread t0 = new Thread(t);
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t0.start();
		t1.start();
		t2.start();
	}

}

LockInterfaceImplementation类:

package learn.javase.threads;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 手动获取锁,释放锁
 * @author Jole
 * */
public class ThreadRunnableDemo01 implements Runnable{

	//票总数
	private int tickets = 100;
	
	//锁 Lock接口的实现类ReentrantLock
	private Lock lock = new ReentrantLock();
	
	public void run() {
		while(true) {
			//获取锁
			lock.lock();
			try {
				if(tickets > 0) {
					Thread.sleep(20);
					System.out.println(Thread.currentThread().getName()+" "+tickets--);
				}
			}catch(Exception e) {
				e.printStackTrace();
			}finally {
				//释放锁
				lock.unlock();
			}
		}
	}
}
3、注意点

Usage多Thread为了保证Data的Safety性,加了synchronized或Lock保持Synchronization,因此效率上就降低了。

可能出现SafetyProblem的情况:多Thread,共享一个Data。

Usagesynchronized时,SynchronizationLock必须保证为同一个SynchronizationLock对象才行。

四、死锁

ThreadDeadlock.png Deadlock:多个Thread,对方互相等待Get对方的Lock。双方一直处于等待Get对方Lock对象状态,也就是Deadlock。

package learn.javase.threads;
/**
 * 死锁:多个线程,对方互相等待获取对方的锁。双方一直处于等待获取对方锁对象状态,也就是死锁。
 * @author Jole
 * */
public class DealThreadMainDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		DetalThread death = new DetalThread();
		Thread t0 = new Thread(death);
		Thread t1 = new Thread(death);
		t0.start();
		t1.start();
	}

}

SynchronizationLock对象A:

package learn.javase.threads;
/**
 * 同步锁A,对象
 * @author Jole
 * */
public class LockA {
	private LockA() {
		
	};

	public static final LockA lockA = new LockA();
}

SynchronizationLock对象B:

package learn.javase.threads;
/**
 * 同步锁B,对象
 * @author Jole
 * */
public class LockB {

	public LockB() {
		
	}
	
	public static final LockB lockB = new LockB();
}

模拟出现Deadlock情况,Implementation类:

package learn.javase.threads;

public class DetalThread implements Runnable{

	private int number = 0;
	public void run() {
		while(true) {
			if(number % 2 ==0) {
				synchronized (LockA.lockA) {
					System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"if抢到资源--获取到--同步锁A对象");
					synchronized (LockB.lockB) {
						System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"if抢到资源--获取到--同步锁B对象");
					}
				}
			}else {
				synchronized (LockB.lockB) {
					System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"else抢到资源--获取到--同步锁B对象");
					synchronized (LockA.lockA) {
						System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"else抢到资源--获取到--同步锁A对象");
					}
				}
			}
			number++;
		}
	}
}

注意Get的Lock是那个。

五、线程等待、唤醒

多Thread的等待与唤醒.png

Example:input拷贝Data对象,output输入对象:如果不是用Thread等待与唤醒,出现Problem:拷贝的Data太快,还来不及输出,可能出现输出的Data错乱。UsageThread等待和唤醒,可以控制拷贝一个后,拷贝Thread等待,然后输出Thread输出,输出后,输出Thread等待,然后拷贝Thread Wakeup,进行拷贝Data,依次循环。从而保证拷贝一个Data,输出一个Data。

package learn.javase.threads;
/**
 * 线程等待、唤醒:
 * 涉及同步锁是否是同一个锁,只有锁调用wait和notify才有效,不然抛出异常。
 * 正常情况是交替出现,不会出现数据错乱情况。
 * @author Jole
 * 本类是主测试类
 * 该实例:input拷贝数据对象,output输入对象:
 * input可能出现拷贝对象拷贝很快,output输出对象很慢,导致output输出数据错乱,因此只能单个拷贝输出,且每次拷贝完后需wait等待,然后等输出完成后在拷贝。
 * 因此需要拷贝完成后,等待,并唤醒输出,等输出完成前唤醒拷贝,然后自己在等待,然后拷贝又拷贝,然后又唤醒输出,如此循环。
 * 实现方法加一个flag标记,是该拷贝还是输出
 */
public class WatiNotifyThreadMainDemo {

	public static void main(String[] args) {
		//操作的是同一个对象锁,不然拷贝和输出都是用自己的锁this,也会发生数据错乱
		ResourceData resource = new ResourceData();
		
		//保证是同一个锁对象,所以都写了构造方法传入同一个锁对象
		InputDemo input = new InputDemo(resource);
		OutputDemo output = new OutputDemo(resource);
		
		Thread t0 = new Thread(input);
		Thread t1 = new Thread(output);
		
		t0.start();
		t1.start();
	}

}

要拷贝的Data类:

package learn.javase.threads;
/**
 * 操作数据
 * @author Jole
 * */
public class ResourceData {

	public String name;
	public String sex;

	public  boolean flag;
}

拷贝Thread:

package learn.javase.threads;
/**
 * 拷贝线程:
 * 当标记flag=true时,拷贝完成,需等待
 * @author Jole
 * */
public class InputDemo implements Runnable {
	private ResourceData resourceData;
	public InputDemo(ResourceData resourceData) {
		this.resourceData = resourceData;
	}

	@Override
	public void run() {
		int  number=0;
		while(true) {
			synchronized (resourceData) {
				//如果为ture拷贝完成,需等待
				if(resourceData.flag) {
					try {
						resourceData.wait();
					}catch(Exception e) {
						e.printStackTrace();
					}
				}
				//拷贝值,比如写死拷贝张三,30岁,lishi,20岁
				if(number % 2==0) {
					resourceData.name = "张三";
					resourceData.sex = "男";
				}else {
					resourceData.name = "Lishi";
					resourceData.sex = "nv";
				}
				
				//设置true,以便下次进入等待wait
				resourceData.flag = true;
				
				//唤醒输出线程
				resourceData.notify();
			}
			
			number++;
		}

	}

}

输出Thread:

package learn.javase.threads;
/**
 * 输出线程:
 * 当标记flag=false时,输出完成,需等待
 * @author Jole
 * */
public class OutputDemo implements Runnable {
	
	private ResourceData resourceData;
	public OutputDemo(ResourceData resourceData) {
		this.resourceData = resourceData;
	}

	@Override
	public void run() {
		while(true) {
			synchronized (resourceData) {
				//如果为false,则输出完成,等待
				if(!resourceData.flag) {
					try {
						resourceData.wait();
					}catch(Exception e) {
						e.printStackTrace();
					}
				}
				//否则输出,并唤醒拷贝
				System.out.println(resourceData.name+" .. "+ resourceData.sex);
				resourceData.flag = false;
				resourceData.notify();
			}
		}
	}

}