java 多线程
1.线程初步
多线程这块多记录点实例以及重点部分,以下两篇博客讲的很清楚了
https://www.cnblogs.com/lwbqqyumidi/p/3804883.html
https://www.cnblogs.com/wxd0108/p/5479442.html
实现多线程方法:
方法一:直接从Thread类派生,重写其run方法
class MyThread extends Thread {
public void run()
{
// 将要以多线程方式运行的代码
}
}
调用Thread类的start方法启动运行
方法二:实现Runnable接口方式
将代码封装到Runable接口的run方法内:
public interface Runnable{
void run();
}
public MyRunnable implements Runnable{
public void run(){
//要以多线程方式运行的代码
}
}
之后:
创建一个Runable对象: Runnable r = new MyRunnable();
将Runable对象传给Thread对象: Thread t = new Thread(r);
启动多线程运行:t.start();
实例一:
public class TestMain {
public static void main(String[] args){
Thread th=new Thread(new Runnable(){
public void run(){
System.out.println("Runnable.Run()");
}
})
{
public void run(){
//super.run();
System.out.println("Thread.Run()");
}
};
th.start();
}
}
输出:
Thread.Run()
原因:Thread的run方法是调用传入的Runnable类的run方法,如下源代码
@Override
public void run() {
if (target != null) {
target.run();
}
}
但是,程序又重写了Thread的run方法,所以以上方法作废,直接输出Thread.Run()
https://www.cnblogs.com/lwbqqyumidi/p/3804883.html文章中还有一种使用Callable和Future接口创建线程的方式具体如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestMain {
public static void main(String[] args){
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果,call()方法未执行完,get()方法一直阻塞
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
使用工厂创建线程:
- JDK定义了一个ThreadFactory接口用于定义线程工厂
public interface ThreadFactory {
Thread newThread(Runnable r);
}
- 实现此接口,就可以在应用程序中应用“工厂”设计模式,按需创建线程,避免了到处重复书写创建线程代码的麻烦,提升了代码的可维护性
- 可以依据特定的需求,向外界返回特殊的自定义的Thread子类对象
- 在需要时,可以实现一个线程池,重用而不是新建对象
示例代码:
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ThreadFactory;
public class TestMain {
public static void main(String[] args){
MyThreadFactory factory=new MyThreadFactory("my thread factory");
factory.newThread(new Runnable() {
@Override
public void run() {
System.out.println("in runnable.");
}
}).start();
factory.printInfor();
}
}
class MyThreadFactory implements ThreadFactory {
private int counter;
private String name;
private List<String> stats;
public MyThreadFactory(String name) {
counter = 0;
this.name = name;
stats = new ArrayList<String>();
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, name + "-Thread_" + counter);
counter++;
stats.add(String.format("Created thread %d with name %s on %s\n",
t.getId(), t.getName(), new Date()));
return t;
}
public void printInfor(){
for(String s:stats){
System.out.println(s);
}
}
}
Daemon线程:
- Daemon线程是在后台执行的“背景”线程,当主线程结束时 JVM会自动结束这个线程
- 默认情况下,创建的线程均为“前台”线程,需要调用setDeamon方法设置为“背景”线程:thread.setDaemon(true);
让线程暂停的方法thread.sleep()和TimeUnit.SECONDS.sleep(),第二种方式能直接指定各种不同的时间单位:纳秒、微秒、毫秒、秒、分钟、小时、天
import java.util.concurrent.TimeUnit;
public class TestMain {
public static void main(String[] args){
Thread th=new Thread(new Runnable(){
private int counter=0;
public void run() {
for(int i=0;i<10;i++){
try {
Thread.sleep(500);//毫秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter++;
System.out.println(counter);
}
}
});
th.start();
}
}
终止线程:
外界通过调用线程的interrupt方法向线程发出中断工作的请求,这时,线程对象内部的一个“中断状态”标记被设置。线程对象应该定期检查这一标记,以合适的方式中断已经运行的工作。
import java.util.concurrent.TimeUnit;
public class TestMain {
public static void main(String[] args){
Thread th=new PrimerGenerator();
th.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
th.interrupt();//终止线程
}
}
class PrimerGenerator extends Thread{
public void run() {
long number=1l;
while(true){
if (isPrime(number)) {
System.out.printf("Number %d is Prime\n",number);
}
if (isInterrupted()) {//终止判断
System.out.printf("The Prime Generator has been Interrupted\n");
return;
}
number++;
}
}
/**
* 判断是不是primer
* @param number
* @return
*/
private boolean isPrime(long number) {
if (number <=2) {
return true;
}
for (long i=2; i<number; i++){
if ((number % i)==0) {
return false;
}
}
return true;
}
}
如果被调用线程本身需要使用sleep()方法,由于此方法在运行时会清除掉“中断状态标记”,并激发一 个InterruptedException,所以,应该采用以下代码模板:
public void run(){
try{
… while( more work to do) {
do more work
Thread.sleep(dealy);
}
} catch(InterruptedException e) {
//thread was interrupted during sleep
} finally {
// clean up, if required
} //exiting the run method terminates the thread
}
import java.util.concurrent.TimeUnit;
public class TestMain {
public static void main(String[] args){
Thread th=new Thread(new Runnable(){
public void run(){
try{
Thread.sleep(5000);
}catch(InterruptedException e){
System.out.println("Thread is interrupted");
}
}
});
th.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
th.interrupt();//终止线程
}
}
定时任务,使用Timer类的schedule方法可以定时执行一个任务。此任务是TimerTask类型的实例,注意TimerTask派生自Runnable。因此,会在另外一个线程中运行这个定时任务。
import java.util.Timer;
import java.util.TimerTask;
public class TestMain {
public static void main(String[] args){
//将要被定时执行的任务,需要派生自TimerTask抽象类,并实现其抽象方法run()
TimerTask task=new TimerTask(){
private int counter=0;
public void run() {
counter++;
System.out.println(counter+":invoked!");
}
};
Timer timer=new Timer();
//过2秒钟后首次运行,以后每隔3秒运行一次
// timer.schedule(task, 2000,3000);
//过2秒钟后运行一次,但线程并没有死掉
timer.schedule(task,2000);
}
}
线程执行框架:(Thread Executor Framework)
JDK中提供了一个Executor framework,其目的是将多线程任务的创建与执行分离开来,将要多线程执行的任务封装为一个Runnable对象,将其传给一个Executor对象,Executor从线程池中选择线程执行工作任务。使用Executor framework的好处在能简化多线程代码同时提升程序性能。
顶层接口:
public interface Executor {
void execute(Runnable command);
}
ThreadPoolExecutor类实现了Executor接口,在实际开发中,我们通常通过Executors类的一些静态方法来实例化ThreadPoolExecutor对象
- Executors.newSingleThreadExecutor:单线程,如果出了异步死掉后,自动重新创建后一个新的线程
- Executors.newFixedThreadPool:固定数目的线程池
- Executors.newCachedThreadPool:动态地按需求增加线程,重用己创建的线程,长时不用的线程被销毁
- Executors.newScheduledThreadPool:固定数目,定期执行
- Executors. newSingleThreadScheduledExecutor:单线程,定期执行
- Executors.newWorkStealingPool:可以依据CPU核数创建多个任务队列,实现线程之间的工作负荷均衡。
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestMain {
public static void main(String[] args){
Server server=new Server();
//创建100个任务,让server执行
for (int i=0; i<100; i++){
Task task=new Task("Task_"+i);
server.executeTask(task);
}
server.endServer();
}
}
class Server {
private ThreadPoolExecutor executor;
//创建一个线程池执行者
public Server(){
executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
}
//executor处理任务
public void executeTask(Task task){
System.out.printf("Server: A new task has arrived\n");
executor.execute(task);//正式处理
System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
}
public void endServer() {
System.out.println("----------------------------------------------------------------------------");
System.out.printf("Server: Finally Pool Size: %d\n",executor.getPoolSize());//由于线程被重用,最后pool size不是100
executor.shutdown();
}
}
class Task implements Runnable {
private Date initDate;
private String name;
public Task(String name){
initDate=new Date();
this.name=name;
}
@Override
public void run() {
System.out.printf("%s: Task %s: Created on: %s\n",Thread.currentThread().getName(),name,initDate);
System.out.printf("%s: Task %s: Started on: %s\n",Thread.currentThread().getName(),name,new Date());
try {
Long duration=(long)(Math.random()*10);
System.out.printf("%s: Task %s: Doing a task during %d seconds\n",Thread.currentThread().getName(),name,duration);
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s: Task %s: Finished on: %s\n",Thread.currentThread().getName(),name,new Date());
}
}
Executor Framework与Callable接口和Future接口合用,这样可以调取线程得到的结果:
import java.util.*;
import java.util.concurrent.*;
public class TestMain {
public static void main(String[] args){
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);//初始一个容量为2的线程池
List<Future<Integer>> resultList = new ArrayList<>();
Random random = new Random();
//生成10个任务
for (int i = 0; i < 10; i++) {
Integer number = new Integer(random.nextInt(10));
FactorialCalculator calculator = new FactorialCalculator(number);
Future<Integer> result = executor.submit(calculator);//提交任务,返回的result放在Future的list中
resultList.add(result);
}
do {
System.out.printf("Main: Number of Completed Tasks: %d\n", executor.getCompletedTaskCount());
for (int i = 0; i < resultList.size(); i++) {
Future<Integer> result = resultList.get(i);
System.out.printf("Main: Task %d: %s\n", i, result.isDone());
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (executor.getCompletedTaskCount() < resultList.size());//一直执行到所有task完成
System.out.printf("Main: Results\n");
for (int i = 0; i < resultList.size(); i++) {
Future<Integer> result = resultList.get(i);
Integer number = null;
try {
number = result.get();//阻塞式等待结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.printf("Core: Task %d: %d\n", i, number);
}
executor.shutdown();
System.out.println("------------done------------");
}
}
class FactorialCalculator implements Callable<Integer> {
private Integer number;
public FactorialCalculator(Integer number) {
this.number = number;
}
/**
* 计算阶乘
*/
@Override
public Integer call() throws Exception {
int num, result;
num = number.intValue();
result = 1;
if ((num == 0) || (num == 1)) {
result = 1;
} else {
for (int i = 2; i <= number; i++) {
result *= i;
Thread.sleep(10);
}
}
System.out.printf("%s: %d\n", Thread.currentThread().getName(), result);
return result;
}
}
Future还提供cancel()方法用于取消任务。
任务同步一:如果有多个并行执行的任务,其中只要有一个完成了就行,这时,可以使用invokeAny()方法
任务同步二:使用invokeAll()等待所有任务都运行结束,再输出结果
2.线程同步
- 添加了synchronized关键字的方法称为“同步方法”。
- 每次只允许一个线程执行同步方法。其余的线程阻塞,直到此方法执行完毕
- 当方法执行完毕,JVM调度下一个最高优先级的线程运行
class Sequence {
private int nextValue;
public synchronized int getNext() {
return nextValue++;
}
}
亦可使用同步代码块达到相同的目的:
synchronized(object) {
//需要同步的代码
}
上述代码中的object代表同步对象,通常我们会将要访问同步对象的代码放入到此同步块中(这并非是必须遵守的规则,完全可以放置任意的代码)。JVM会保证只有一个线程执行代码块中的这些代码
在实际开发中,通常在内部放置一个专用于同步的对象,在同步方法内部锁定它,示例如下:
public class TestMain {
public static void main(String[] args){
SynchroizedClass e = new SynchroizedClass();
TestThread1 t1 = new TestThread1(e);
TestThread2 t2 = new TestThread2(e);
//hello,world顺序输出,没有synchroized关键字就乱序输出
t1.start();
t2.start();
}
}
class SynchroizedClass
{
private Object object = new Object();
public void printHello()
{
synchronized (object)
{
for (int i = 0; i < 10; i++)
{
try{
Thread.sleep((long) (Math.random() * 1000));
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("hello: " + i);
}
}
}
public void printWorld()
{
synchronized(object)
{
for (int i = 0; i < 10; i++)
{
try{
Thread.sleep((long) (Math.random() * 1000));
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("world: " + i);
}
}
}
}
class TestThread1 extends Thread
{
private SynchroizedClass example;
public TestThread1(SynchroizedClass example)
{
this.example = example;
}
@Override
public void run()
{
this.example.printHello();
}
}
class TestThread2 extends Thread
{
private SynchroizedClass example;
public TestThread2(SynchroizedClass example)
{
this.example = example;
}
@Override
public void run()
{
this.example.printWorld();
}
}
synchronized方法是一种粗粒度的并发控制手段,某一时刻只能有一个线程执行该方法。synchroized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内synchroized块之外的代码是可以被多个线程同时访问到
线程之间的协作:
A线程正在运行,希望插入一个B线程,要求B先执行完毕,A才继续运行,可以使用join()方法,Join的含义是:将某一线程加入成为另一个线程的流程之一。
public class TestMain {
public static void main(String[] args)throws InterruptedException{
System.out.println("主线程执行");
Thread otherThread = new Thread(new Runnable() {
public void run() {
try {
System.out.println("辅助线程开始..");
for(int i = 1; i <= 5; i++) {
Thread.sleep(1000);
System.out.println(i+":辅助线程执行..");
}
System.out.println("辅助线程执行结束");
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
});
otherThread.start(); //启动线程
try {
//辅助线程加入主线程
otherThread.join();
}
catch(InterruptedException e) {
e.printStackTrace();
}
for(int i=1;i<=5;i++){
Thread.sleep(500);
System.out.println(i+": 主线程正在执行...");
}
System.out.println("主线程 执行完毕");
}
}
经典的“生产者—消费者”问题:
生产者生产5个数,消费者依次消费5个数,生产者不生产数,消费者需等待,消费者未消费数,生产者需等待,以下两个代码分别是无同步和有同步。
无同步:
public class TestMain {
public static void main(String[] args)throws InterruptedException{
NoSyncClerk clerk = new NoSyncClerk();
// 消费者线程
Thread consumerThread = new Thread(new Consumer(clerk));
// 生产者线程
Thread producerThread = new Thread(new Producer(clerk));
consumerThread.start();
producerThread.start();
}
}
class NoSyncClerk {
// -1 表示目前没有产品
private int product = -1;
// 这个方法由生产者呼叫
public void setProduct(int product) {
this.product = product;
System.out.printf("生产者设定 (%d)%n", this.product);
}
// 这个方法由消费者呼叫
public int getProduct() {
int p = this.product;
System.out.printf("消费者取走 (%d)%n", this.product);
return p;
}
}
class Consumer implements Runnable {
private NoSyncClerk clerk;
public Consumer(NoSyncClerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("消费者开始消耗整数......");
// 消耗5个整数
for(int i = 1; i <= 5; i++) {
try {
// 等待随机时间
Thread.sleep((int) (Math.random() * 1000));
}
catch(InterruptedException e) {
e.printStackTrace();
}
// 从店员处取走整数
clerk.getProduct();
}
}
}
class Producer implements Runnable {
private NoSyncClerk clerk;
public Producer(NoSyncClerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("生产者开始生产整数......");
// 生产1到5的整数
for(int product = 1; product <= 5; product++) {
try {
// 暂停随机时间
Thread.sleep((int) (Math.random() * 1000));
}
catch(InterruptedException e) {
e.printStackTrace();
}
// 将产品交给店员
clerk.setProduct(product);
}
}
}
wait方法阻塞本线程,等待其他线程调用notify方法通知自己可以继续执行。
有同步:
public class TestMain {
public static void main(String[] args)throws InterruptedException{
SyncClerk clerk = new SyncClerk();
// 消费者线程
Thread consumerThread = new Thread(new Consumer(clerk));
// 生产者线程
Thread producerThread = new Thread(new Producer(clerk));
consumerThread.start();
producerThread.start();
}
}
class SyncClerk {
// -1 表示目前没有产品
private int product = -1;
// 这个方法由生产者呼叫
public synchronized void setProduct(int product) {
if(this.product!=-1){
try{
//无空间存放,需等待取走
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.product = product;
System.out.printf("生产者设定 (%d)%n", this.product);
//通知消费者取走
notify();
}
// 这个方法由消费者呼叫
public synchronized int getProduct() {
if(this.product==-1){
try{
wait();//缺货需等待
}catch(InterruptedException e){
e.printStackTrace();
}
}
int p = this.product;
System.out.printf("消费者取走 (%d)%n", this.product);
this.product=-1;
notify();//通知生产者生产
return p;
}
}
class Consumer implements Runnable {
private SyncClerk clerk;
public Consumer(SyncClerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("消费者开始消耗整数......");
// 消耗5个整数
for(int i = 1; i <= 5; i++) {
try {
// 等待随机时间
Thread.sleep((int) (Math.random() * 1000));
}
catch(InterruptedException e) {
e.printStackTrace();
}
// 从店员处取走整数
clerk.getProduct();
}
}
}
class Producer implements Runnable {
private SyncClerk clerk;
public Producer(SyncClerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println("生产者开始生产整数......");
// 生产1到5的整数
for(int product = 1; product <= 5; product++) {
try {
// 暂停随机时间
Thread.sleep((int) (Math.random() * 1000));
}
catch(InterruptedException e) {
e.printStackTrace();
}
// 将产品交给店员
clerk.setProduct(product);
}
}
}
基于锁实现线程同步:
JDK提供了一个ReentrantLock锁:
Lock myLock =new ReentrantLock();
- 使用ReentrantLock锁可以保证一次只有一个线程执行以下代码:
myLock.lock(); // a ReentrantLock object
try{
critical section
} finally {
myLock.unlock();
}
- 一个线程可以多次申请ReentrantLock锁,但必须保证它必须释放同样多次的锁。所以称它为“可重入”的锁
读写锁:ReentrantReadWriteLock:可以同时读,不能同时写
Lock对象提供了一个Condition对象,可以取代 Object类的notify和wait方法,Condition最大的好处在于可以仅使用一个Lock对象就能支持“多 路等待”。
参考:
金旭亮Java编程系列(大部分内容和代码)