文章目录
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();
}
}
∗∗笔记∗∗
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()
∗∗注意∗∗
- 必须在 <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任务对象)
把任务丢到线程池
- execute(Runnable任务对象)
- Executors
辅助创建线程池的工具类- newFixedThreadPool(5)
最多5个线程的线程池 - newCachedThreadPool()
足够多的线程,使任务不必等待 - newSingleThreadExecutor()
只有一个线程的线程池
- newFixedThreadPool(5)
代码: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
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);
}
}