1 回顾


1.1 线程状态



1.2 线程的方法

获取当前正在执行的线程实例

Thread.currentThread()

当前线程暂停指定的毫秒值时长

Thread.sleep(毫秒值时长)

让步,主动放弃 cpu 时间片,让给其他线程执行

Thread.yield()

启动线程,线程启动后,并行执行run()方法中的代码

getName(), setName()
start()

打断另一个线程的暂停状态,被打断的线程,会出现InterruptedException

interrupt()

当前线程暂停,等待被调用的线程接收后,再继续执行

 join()
a线程 			-----------------
b线程 ---------|                 |------------
a.join()

后台线程、守护线程

setDaemon(true)

<mark>JVM虚拟机退出条件</mark>,是所有前台线程结束,当所有前台线程结束,虚拟机会自动退出
不会等待后台线程结束
例如:垃圾回收器是一个后台线程

优先级,1到10,默认是5

getPriority(), setPriority()


1.3 方法测试

Thread.sleep

package day13;

 

import java.text.SimpleDateFormat;

import java.util.Date;

public class Test3 {

    public static void main(String[] args) {

       T1 t1 = new T1();

       t1.start();

    }

 

    static class T1 extends Thread {

       @Override

       public void run() {

           SimpleDateFormat f =

                  new SimpleDateFormat("HH:mm:ss");     

           while(true) {

              String s = f.format(new Date());

              System.out.println(s);

              try {

                  Thread.sleep(1000);

              } catch (InterruptedException e) {

              }

           }

       }

    }

 

}



interrupt & InterruptedException

package day13;

 

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.Scanner;

public class Test3_v2 {

    public static void main(String[] args) {

       T1 t1 = new T1();

       t1.start();

 

       Thread t2 = new Thread() {

           @Override

           public void run() {

              System.out.println("按回车捅醒 t1");

              new Scanner(System.in).nextLine();

              t1.interrupt();

           }

       };

       //虚拟机不会等待后代线程结束

       //所有前台线程结束时,虚拟机会自动退出

       t2.setDaemon(true);

       t2.start();

    }

 

    static class T1 extends Thread {

       @Override

       public void run() {

           SimpleDateFormat f =

                  new SimpleDateFormat("HH:mm:ss");     

           for(int i=0; i<10; i++) {

              String s = f.format(new Date());

              System.out.println(s);

              try {

                  Thread.sleep(1000);

              } catch (InterruptedException e) {

                  System.out.println("被打断");

                  break;

              }

           }

       }

    }

}



setDaemon

package day13;

 

import java.text.SimpleDateFormat;

import java.util.Date;

import java.util.Scanner;

 

public class Test3_v3 {

    public static void main(String[] args) {

       T1 t1 = new T1();

       t1.start();

 

       Thread t2 = new Thread() {

           @Override

           public void run() {

              System.out.println("按回车捅醒 t1");

              new Scanner(System.in).nextLine();

              t1.interrupt();

           }

       };

       //虚拟机不会等待后代线程结束

       //所有前台线程结束时,虚拟机会自动退出

       t2.setDaemon(true);

       t2.start();

    }

 

    static class T1 extends Thread {

       @Override

       public void run() {

           SimpleDateFormat f =

                  new SimpleDateFormat("HH:mm:ss");     

           for(int i=0; i<10; i++) {

              String s = f.format(new Date());

              System.out.println(s);

              try {

                  Thread.sleep(1000);

              } catch (InterruptedException e) {

                  System.out.println("被打断");

                  break;

              }

           }

       }

    }

 

}



join()

package day13;

 

public class Test5 {

    public static void main(String[] args) throws InterruptedException {

       //1000万内,有多少个质数

       //2,3,5,7,11,13,17,19,23....

       System.out.println("\n--单线程-----------------");

       f1();

       System.out.println("\n--5个线程-----------------");

       f2();

    }

 

    private static void f1() throws InterruptedException {

       long t = System.currentTimeMillis();

 

       T1 t1 = new T1(0, 10000000);

       t1.start();

 

       //main线程暂停等待 t1 的执行结果

       t1.join();

       int c = t1.count;

 

       t = System.currentTimeMillis()-t;

       System.out.println("数量:"+c);

       System.out.println("时间:"+t);

    }

 

    private static void f2() throws InterruptedException {

       long t = System.currentTimeMillis();

       int n = 5;

       int m = 10000000/n;

       T1[] a = new T1[n];

       for (int i = 0; i < a.length; i++) {

           a[i] = new T1(m*i, m*(i+1));

           a[i].start();

       }

       int sum = 0;

       for (T1 t1 : a) {

           t1.join();

           sum += t1.count;

       }

       t = System.currentTimeMillis()-t;

       System.out.println("数量:"+sum);

       System.out.println("时间:"+t);

    }

    static class T1 extends Thread {

        int from;

       int to;

       int count;//计数变量

       public T1(int from, int to) {

           if(from<3) {

              from = 3;

              count = 1;

           }

           this.from = from;

           this.to = to;

       }

       @Override

       public void run() {

           for (int i = from; i < to; i++) {

              if (isPrime(i)) {//判断i是否是质数

                  count++;

              }

           }

       }

       private boolean isPrime(int i) {

           /* * 从2到 (i开方+1) * 找有能把i整除的,i不是质数 * 都不能吧i整除,i是质数 */

           double d = Math.sqrt(i) + 1;

           for (int j = 2; j < d; j++) {

              if (i%j == 0) {

                  return false;

              }

           }

           return true;

       }

 

    }

 

}







2 线程锁+ThreadLocal

2.1 数据混乱

打印一些 * - 。有规律的

package cn.edut.com.tarena;

import java.util.Arrays;

public class Test1 {
	static char[] a = {'*','*','*','*','*'};
	static char c ='-' ; 
	public static void main(String[] args) {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				while (true) {
					for (int i = 0; i < a.length; i++) {
						a[i] = c ; 
					}
					c = (c=='-'?'*':'-');
				}
			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				while (true) {
					System.out.println(Arrays.toString(a));
				}
			}
		};
		
		t1.start();  
		t2.start();
	}
	
}



2.2 synchronize 关键字

遇到 synchronized 关键字,
会在加锁的对象上,
关联一个同步监视器

<mark>由于是修改数组a的数据。因此锁这里定为数组a比较合适</mark>

2.2.1 synchronize的三种写法

synchronize 代码块

package cn.edut.com.tarena;

import java.util.Arrays;

public class Test1 {
	static char[] a = { '*', '*', '*', '*', '*' };
	static char c = '-';

	public static void main(String[] args) {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				while (true) {
					synchronized (a) {
						for (int i = 0; i < a.length; i++) {
							a[i] = c;
						}
					}
					c = (c == '-' ? '*' : '-');
				}
			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				while (true) {
					synchronized (a) {
						System.out.println(Arrays.toString(a));
					}
					
				}
			}
		};

		t1.start();
		t2.start();
	}

}

<mstyle mathcolor="&#35;ff0011"> </mstyle> \color{#ff0011}{**笔记**}

synchronize 修饰符

package cn.edut.com.tarena;

import org.junit.Test;

public class Test2 {
	@Test
	public void test001() {
		T1 t1 = new T1();
		t1.start(); 
		while (true) {
			int t = t1.get();
			System.out.println(t);
			if(t%2==1) {
				System.exit(0);
			}
		}
	}
	
	//方法前面 synchronize 锁的是当前实例
	static class T1 extends Thread{
		static int i ;
		public synchronized int  get() {
			return i;
		}
		private synchronized void add() {
			i++;
			i++;
		}
		
		@Override
		public void run() {
			while (true) {
				add();
			}
		}
		
	}
}



2.3 生产者和消费者模型

线程间通信模型

  • 生产者产生数据,放入一个数据容器
  • 消费者从容器来获取数据
  • 线程间解耦

Producer 类

package cn.edut.com.tarena.thread;

import java.util.Queue;

public class Producer<T> extends Thread {
	Queue<T> queue;

	public Producer(Queue<T> queue) {
		this.queue = queue;
	}
	/* * run在new时候再重写 */
}

Customer 类

package cn.edut.com.tarena.thread;

import java.util.Queue;

public class Customer<T>  extends Thread{
	Queue<T> queue; 
	public Customer(Queue<T> queue) {
		this.queue = queue; 
	}
	@Override
	public void run() {
		while (true) {
			synchronized (queue) {
			//poll()从头部移出数据,如果是空集合,得到null;
			//和removeFirst不同的是,removeFirst 当空集合时,抛出异常
				T c = queue.poll() ;
				System.out.println("size:"+queue.size()+" >>>>>> "+c);
			}
		}
	}
}





测试类

package cn.edut.com.tarena.thread;

import java.util.LinkedList;
import java.util.Random;

import org.junit.Test;

public class CPTest {
	@Test
	public void test_queue() {
		LinkedList<Character> ll = new LinkedList<>();;
		Producer<Character> producer = new Producer<Character>(ll) {
			@Override
			public void run() {
				while (true) {
					synchronized (queue) {
						char c =  (char) ('a' + new Random().nextInt(26)); //[0,26)
						queue.offer(c);
						System.out.println("size:"+queue.size()+" <<< " + c);
					}
				}
			}
		};
		Customer<Character> customer = new Customer<>(ll);
		producer.start();
		customer.start();
	}
}


只有进没有出,为什么?
往下看



2.4 等待和通知

Object 的方法

wait()
notify()  
notifyAll()
<mstyle mathcolor="&#35;ff0011"> </mstyle> \color{#ff0011}{**注意**}
  • 必须在 <mark>synchronized 内调用</mark>
  • <mark>等待通知的对象</mark>,必须是<mark>加锁的对象</mark>
  • <mark>wait() 外面</mark>,总应该<mark>是一个循环条件判断</mark>

测试类

package cn.edut.com.tarena.thread;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

import org.junit.Test;

public class CPTest {
	@Test
	public void test_queue() throws Exception  {
		//数据存储
		LinkedList<Character> ll = new LinkedList<>();
		
		//生产者
		Producer<Character> producer = new Producer<Character>(ll) {
			@Override
			public void run() {
				Random ran = new Random();
				// 不断生产
				while (true) {
					synchronized (queue) {
						char c =  (char) ('a' + ran.nextInt(26));
						queue.offer(c);
						System.out.println("size:"+queue.size()+" <<< " + c);
						queue.notifyAll();
					}
					try {
						Thread.sleep(ran.nextInt(1000));
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
				}
			}
		};
		
		//消费者
		Customer<Character> customer = new Customer<>(ll);
		producer.start();
		customer.start();
		
		SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
		while(true) {
			System.out.println("当前时间:"+sdf.format(new Date()) );
			Thread.sleep(1000);
		}
	}
}



Customer 类

package cn.edut.com.tarena.thread;

import java.util.Queue;

public class Customer<T>  extends Thread{
	Queue<T> queue; 
	public Customer(Queue<T> queue) {
		this.queue = queue; 
	}
	@Override
	public void run() {
		//不断消费
		while (true) {
			synchronized (queue) {
				while (queue.isEmpty()) {
					try {
						queue.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				T c = queue.poll() ;
				System.out.println("size:"+queue.size()+" >>>>>> "+c);
			}
		}
	}
}

Productor 类

package cn.edut.com.tarena.thread;

import java.util.Queue;

public abstract class Producer<T> extends Thread {
	Queue<T> queue;

	public Producer(Queue<T> queue) {
		this.queue = queue;
	}
	/* * run在new时候再重写 */
	@Override
	public abstract void run();
}




2.5 同步监视器

遇到 <mark>synchronized</mark> 关键字,会在<mark>加锁的对象</mark>上,关联一个<mark>同步监视器</mark>



2.6 线程辅助工具

2.6.1 线程池 ExecutorService/Executors

  • ExecutorService
    线程池
    • execute(Runnable任务对象)
      把任务丢到线程池
  • Executors
    辅助创建线程池的工具类
    • newFixedThreadPool(5)
      最多5个线程的线程池
    • newCachedThreadPool()
      足够多的线程,使任务不必等待
    • newSingleThreadExecutor()
      只有一个线程的线程池

代码:https://blog.csdn.net/LawssssCat/article/details/103143793

package cn.edut.com.tarena;

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

public class Test4 {
	public static void main(String[] args) {
		ExecutorService pool = null;
		
		//pool = Executors.newSingleThreadExecutor(); //1线程
		//pool = Executors.newCachedThreadPool() ; //400多个线程
		pool = Executors.newFixedThreadPool(10); //最多10线程
		
		for (int i = 0; i < 1000; i++) {
			pool.execute(new T1(i));
		}
		
		//关闭线程池
		pool.shutdown();
	}
	
	static class T1 implements Runnable{
		private int id ; 
		public T1(int n) {
			id = n ;
		}

		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() +" : "+ id);
		}
		
	}
	
}




2.6.2 Future/FutureTask/Callable

  • Future 父类
    • FutureTask 子类
  • Callable
    可以代替Runnable,提供更丰富的功能
    • 有返回值
    • 可以使用泛型指定返回值类型
    • 可以抛出异常
Future future = pool.submit(Runnable)
//继续执行当前线程的功能
future.get(); //阻塞,等待任务结束,返回null
//当前线程继续处理
Future<String> future = pool.submit(Callable)
//继续执行当前线程的功能
String r = future.get();//阻塞,等待任务结束后,取得它的执行结果
//当前线程继续处理

启动一项并行任务,并在未来任务结束后,获得任务的执行结果

Future 测试

package cn.edut.com.tarena;

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

public class Test01 {
	public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(5);
		Future<?> futurr = pool.submit(new T1());
		try {
			futurr.get();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("执行结束");
		pool.shutdown();
	}
	static class T1 implements Runnable {
		@Override
		public void run() {
			System.out.println("线程执行");
			try {
				Thread.sleep(3000);
			} catch (Exception e) {
				
				e.printStackTrace();
			}
		}
	}
}

Callable测试
package cn.edut.com.tarena;

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

public class Test01 {
	public static void main(String[] args) throws Exception {
		ExecutorService pool = Executors.newFixedThreadPool(5);
		
		System.out.println("runnable测试:");
		Future<?> futurr = pool.submit(new T1());
		futurr.get();
		
		System.out.println("执行结束");
		System.out.println("---------------");
		
		System.out.println("callable测试:");
		Future<String> futurz = pool.submit(new C1());
		String s = futurz.get();
		System.out.println("执行结束:"+s);
		
		pool.shutdown();
	}
	static class T1 implements Runnable {
		@Override
		public void run() {
			System.out.println("执行"+this.getClass().getName());
			try {
				Thread.sleep(3000);
			} catch (Exception e) {
				
				e.printStackTrace();
			}
		}
	}
	
	static class C1 implements Callable<String>{

		@Override
		public String call() throws Exception {
			String name = this.getClass().getName();
			System.out.println("执行"+name);
			Thread.sleep(3000);
			return name;
		}
		
	}
}






2.7 创建线程集中方式?

两种

  • 继承Thread
  • 实现Runnable

两个工具<mark>辅助</mark>创建线程,控制线程的执行

  • 线程池
  • Callable/Future

<mstyle mathcolor="&#35;ff0011"> 2.7.1 T h r e a d L o c a l 线 </mstyle> \color{#ff0011}{2.7.1 ThreadLocal - 线程数据的键} 2.7.1ThreadLocal线

辅助一个线程持有自己的数据,这个数据与其他线程不共享

把数据绑定到线程,线程当做一条流水线,来传递数据

  • 多线程并行时,数据是安全的
threadLocal = new ThreadLocal<Double>();

// 在当前线程上绑定数据

threadLocal.set(2.744625);

// 从当前线程获取绑定的数据

threadLocal.get()
    // 从当前线程删除数据

threadLocal.remove()

  • 存储结构
    • 线程中封装一个 Map
    • 用ThreadLocal实例作为键
    • 对应的值是绑定的数据

package cn.edut.com.tarena;

import java.util.Random;

public class Test02 {

	//作为线程内部的键存放
	static ThreadLocal<Double> threadlocdl = new ThreadLocal<>();
	public static void main(String[] args) {
		 
		
		while(true) {
			new Thread() {
				@Override
				public void run() {
					System.out.print(currentThread().getName()+":");a();
					System.out.print(currentThread().getName()+":");b();
					System.out.print(currentThread().getName()+":");c();
				}
			}.start();
			new Thread() {
				@Override
				public void run() {
					System.out.print(currentThread().getName()+":");a();
					System.out.print(currentThread().getName()+":");b();
					System.out.print(currentThread().getName()+":");c();
				}
			}.start();
			new Thread() {
				@Override
				public void run() {
					System.out.print(currentThread().getName()+":");a();
					System.out.print(currentThread().getName()+":");b();
					System.out.print(currentThread().getName()+":");c();
				}
			}.start();
		}
	}
	
	static void a() {
		System.out.println("a ---- "+getData());
	}
	static void b() {
		System.out.println("b ---- "+getData());
	}
	static void c() {
		System.out.println("c ---- "+getData());
	}
	
	
	static double getData(){
	//线程内部的操作,threadlocal作为键,在线程内部的map进行查找,get数据
		Double d = threadlocdl.get();
		if(d==null) {
			d= new Random().nextDouble();
			threadlocdl.set(d);
		}
		return d;
	}
	static void removeData() {
		threadlocdl.remove();
	}
}

数据在每个线程是不同的,

JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的解决思路。使用这个工具类可以很简洁地编写出优美的多线程程序。这里注意线程锁是解决并发安全问题,而ThreadLocal是解决线程共享问题,何为共享呢?

例如:我们要使用一个对象,这个对象要在不同线程中传递,怎么保持一份呢?不是新对象呢?就需要使用ThreadLocal来保存和传递这个对象。




2.8 volatile - 变量需要(频繁访问+修改)

  • 共享变量的可见性
  • 禁止指令重排的优化

cpu 为了提高运算效率,可能根据一定规则,对运算指令重新排序
a = 6; flag = true
flag = true; a = 6;

  • 不能包装<mark>原子性, 只能靠锁来解决</mark>
  • <mark>什么时候使用volatile</mark>
    • <mark>volatile - 易变,不稳定</mark>
    • <mark>多个线程频繁访问</mark>,<mark>修改变量</mark>


添加 volatile 前

package cn.edut.com.tarena;

public class Test03 {
	static boolean flag= true;
	public static void main(String[] args) throws Exception {
		new Thread() {
			@Override
			public void run() {
				while(flag) {
				}
				System.out.println("结束成功!");
			}
		}.start();
		System.out.println("开始计时,flag="+flag);
		Thread.sleep(1000);
		flag = false ;
		System.out.println("结束计时,flag="+flag);
	}
	
}

<mark>第一个线程无法接收到修改的值</mark> =》 不停止

添加 volatile 后

package cn.edut.com.tarena;

public class Test03 {
	static volatile boolean flag= true;
	public static void main(String[] args) throws Exception {
		new Thread() {
			@Override
			public void run() {
				while(flag) {
				}
				System.out.println("结束成功!");
			}
		}.start();
		System.out.println("开始计时,flag="+flag);
		Thread.sleep(1000);
		flag = false ;
		System.out.println("结束计时,flag="+flag);
	}
	
}