线程创建的方式
- 继承Thread类,重写run方法
- 实现Runnable接口
- 实现Callable接口
获取线程ID和线程名称
- Thread子类中调用this.getId()或者this.getName()
- 使用Thread.currentThread().getID()和Thread.currentThread().getName()
修改线程名称
- 调用线程对象的setName()方法
- 使用线程子类的构造方法赋值
线程的状态
- 初始:线程对象被创建,即为初始状态。只在堆中开辟内存 ,与常规对象无异。
- 就绪:调用start()之后,进入就绪状态。等待OS选中,分配时间片
- 运行:获得时间片后,进入运行状态。时间片用完后,进入就绪状态。
常见方法
1. 休眠:public static void sleep(),毫秒数
2. 放弃:public static void yield()。当前线程主动放弃时间片,进入就绪状态。
3. 加入:public final void join(),允许其他线程加入到当前线程
4. 优先级:线程对象.setPriority(),线程优先级为1-10,默认为5,优先级越高,表示获取CPU的时间片机会越大
5. 守护线程:线程对象.setDaemon(ture):设置为守护线程 。垃圾回收器属于守护线程,
线程安全问题
当多线程并发访问临界资源时,如果破坏了原子操作,会造成数据不一致性。
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
同步方式
- 同步代码块:
synchronized(临界资源对象){ //对临界资源加锁
//代码(原子操作)
}
- 每个对象都有一个互斥锁标记,用来分配给线程的。线程退出同步方法时,会释放相应的互斥锁标记。
- 同步方法:
synchronized 返回值类型 方法名称 (形参列表0){// 对当前对象(this) 加锁
//如果是静态方法,锁是当前类。类名.class
// 代码 原子操作
}
同步规则
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
如果用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
已知JDK中线程安全类:StringBuffer Vector HashTable,这些类的公开方法中,均为synchronized修饰的同步方法。
死锁
当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标
记,由此可能造成死锁。
线程通信
等待:
public final void wait()
public fianl void wait(long timeout)
必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在等待队列中。释放锁,进入等待队列。
通知:
public final void notify()
public final void notifyAll()
生产者,消费者
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一入空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。
线程池
问题:
线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出
频繁的创建和销毁线程会造成虚拟机回收频率,资源开销,造成程序性能下降
线程池:
线程容器,可设定线程分配的数量上限。
将预先创建的线程对象放入池中,并重用线程池中的线程对象
避免频繁的创建和销毁
创建线程池
常用的线程池接口和类(java.util.concurrent)
Executor:线程池的顶级接口
ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码
Executors工厂类:通过此类可以获得一个线程池。
1.创建固定的线程池
2.创建缓存线程池;
3.创建但线程池
4.创建调度线程池,调度:周期,定时执行。
通过newFixedThreadPoll(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo {
public static void main(String[] args) {
// 1.1 创建固定个数的线程池
ExecutorService es = Executors.newFixedThreadPool(4) ;
// 1.2 创建缓存线程池
ExecutorService es1 =Executors.newCachedThreadPool();
// 1.3 创建单线程池
ExecutorService es2 = Executors.newSingleThreadExecutor();
// 1.4 创建调度线程池
ExecutorService es3 = Executors.newScheduledThreadPool();
// 2. 提交任务
Runnable runnable = new Runnable() {
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket <= 0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
};
//3.提交任务
for (int i = 0; i <4 ; i++) {
es.submit(runnable);
}
// 4. 关闭线程池
es.shutdown();
}
}
Callable 接口
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
JDK1.5之后加入,与Runnable接口类似,实现之后代表 一个线程启动
Callable具有泛型返回值,可以声明异常。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Demo2 {
public static void main(String[] args) {
// 1. 创建callable对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算");
int sum =0;
for (int i = 0; i <=100 ; i++) {
sum+=i;
Thread.sleep(100);
}
return sum;
}
};
// 2. 把callable对象转成可执行的任务
FutureTask<Integer> task = new FutureTask<>(callable);
// 3. 创建线程
Thread thread = new Thread(task);
// 4. 启动线程
thread.start();
// 5. 获取结果(等待call()执行完毕返回结果)
Integer sum = null;
try {
sum = task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("结果是"+sum);
}
}
线程池配合Callable接口
import java.util.concurrent.*;
public class Demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 2. 提交任务 Future :表示将要执行玩任务的结果
Future<Integer> future= executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算");
int sum =0;
for (int i = 0; i <=100 ; i++) {
sum+= i;
Thread.sleep(100);
}
return sum;
}
});
// 3.获取任务结果
System.out.println(future.get());
// 4.关闭线程池
executorService.shutdown();;
}
}
Future 接口
表示将要完成任务的结果
package 多线程.Thread;
import java.util.concurrent.*;
/**
* @Author 杨栋
* @Date 2020/7/7 14:39
* 使用两个线程计算1~50,50~100的和,并将结果汇总
*/
public class Demo4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.创建线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 2.提交任务
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum =0;
for (int i = 0; i <=50 ; i++) {
sum+=i;
}
System.out.println("1~50计算完毕");
return sum;
}
});
Future<Integer> future1= es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum =0;
for (int i = 51; i <=100 ; i++) {
sum+= i;
}
System.out.println("50-100计算完毕");
return sum;
}
});
int sum = future.get() + future1.get();
System.out.println("结果是" + sum);
// 4. 关闭线程池
es.shutdown();
}
}
线程的同步异步
同步:形容一次方法调用,同步一旦开始,调用者必须等待改方法返回,才能继续。(一条执行路径)
异步:形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后就立即返回。二者竞争时间片,并发执行。(多条执行路径)
Lock接口
JDK5加入,与synchronized比较,显示定义,结构更灵活。
提供更多实用方法,功能更强大,性能更优越
常用方法
void lock() // 获取锁,如果锁被占用,则等待
boolean tryLock() // 尝试加锁,(成功返回true,失败返回false)
void unlock() // 释放锁
重入锁
ReentrantLock():Lock接口的实现类,与synchronized一样具有互斥锁功能
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyList {
// 创建重入锁对象
private final Lock lock = new ReentrantLock();
private String [] atrs = {"A","B","","",""};
private int count = 2; // 元素个数
// 添加元素
public void add(String value){
lock.lock();
try{
atrs[count] = value;
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
count++;
}finally {
lock.unlock();
}
}
public String [] getatrs(){
return atrs;
}
}
import java.util.ArrayList;
import java.util.Arrays;
public class TestMylist {
public static void main(String[] args) throws InterruptedException {
MyList myList = new MyList();
Runnable runnable = new Runnable() {
@Override
public void run() {
myList.add("hello");
}
};
Runnable runnable1 = new Runnable() {
@Override
public void run() {
myList.add("world");
}
};
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable1);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Arrays.toString(myList.getatrs()));
}
}
读写锁
ReentrantReadWriteLock:一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。支持多次分配读锁,使多个读操作可以并发执行。
互斥规则 :
写–写:互斥,阻塞
读–写:互斥,读阻塞写,写阻塞读
读–读:不互斥,不阻塞
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteDemo {
// 创建读写锁
private ReentrantReadWriteLock rrl = new ReentrantReadWriteLock();
// 获取读锁
private ReentrantReadWriteLock.ReadLock readLock = rrl.readLock();
// 获取写锁
private ReentrantReadWriteLock.WriteLock writeLock = rrl.writeLock();
private String value;
public String getValue(){
// 使用读上锁
readLock.lock();
try{
Thread.sleep(1000);
System.out.println("读取:"+value);
return this.value;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return value;
}
public void setValue(String value){
writeLock.lock();
try {
Thread.sleep(1000);
System.out.println("写入:"+value);
this.value = value;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestReadWite {
public static void main(String[] args) {
ReadWriteDemo readWriteDemo = new ReadWriteDemo();
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(20);
Runnable read = new Runnable() {
@Override
public void run() {
readWriteDemo.getValue();
}
};
Runnable write = new Runnable() {
@Override
public void run() {
readWriteDemo.setValue("张三");
}
};
// 分配写的任务
for (int i = 0; i <2 ; i++) {
executorService.submit(write);
}
// 分配任务
long stretTime = System.currentTimeMillis();
for (int i = 0; i <18 ; i++) {
executorService.submit(read);
}
// 关闭
executorService.shutdown();
while(!executorService.isTerminated()){
}
long endTime = System.currentTimeMillis();
System.out.println("用时"+(endTime-stretTime));
}
}
线程安全集合
多线程演示线程不安全集合
结果会出现java.util.ConcurrentModificationException异常
import java.util.ArrayList;
import java.util.Arrays;
public class Demo5 {
public static void main(String[] args) {
// 1. 创建arraylist
ArrayList<String > arrayList = new ArrayList<> ();
// 2. 创建线程
for (int i = 0; i <20 ; i++) {
int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j <10 ; j++) {
arrayList.add(Thread.currentThread().getName()+"===="+ temp + "==="+j);
System.out.println(arrayList.toString());
}
}
}).start();
}
}
}
Collections中的工具方法
public static <T> Collection<T> synchronizedCollecction(Collection<T> c)
public static <T> List<T> synchronizedList(List<T> list)
public static <T> Set<T> synchronizedSet(Set<T> s )
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m )
public static <T> SortesSet<T> synchronizedSortesSet(SortesSet<T> s)
public static <K,V> SortMap<K,V> synchronizedSortMap(SortMap<K,V> m)
JDK1.2提供,接口统一,维护性高,但性能没有提升,均已synchronized实现
// 1. 创建arraylist
ArrayList<String > arrayList = new ArrayList<> ();
List<String> synlist = Collections.synchronizedList(arrayList);
CopyOnWriteArrayList
线程安全的ArrayList,加强版的读写分离
写有锁,读无锁,读写之间不阻塞,优于读写锁
写入时,先copy一个容器副本,在添加新元素,最后替换引用
CopyOnWriteArraySet
线程安全的Set,底层使用CopyOnWrityArrayList实现。
唯一不同在于 ,使用addIfAbsent()添加元素,会遍历数组。
如存在元素,则不添加(扔掉副本)
Queue接口(队列)
Collcetion的子接口,表示队列FIFO,先进先出
常用方法:
抛出异常:
boolean add(E e) :顺序添加第一个元素(到达上限后,再添加会抛出异常)
E remove() // 获得第一个元素并移除(如果队列没有 元素时,会抛出异常)
E element() // 获得第一个元素但不移除(如果队列没有元素,则跑异常)
返回特殊值:推荐使用
boolean offer(E e): // 顺序添加一个元素(到达上限后,在添加则会返回false)
E poll()//获得第一个元素并移除(如果队列没有元素,则返回null)
E peek() // 获得第一个元素但并不移除,(如果队列没有元素时,则返回null)
ConcurrentLinkedQueue
线程安全,可高效读写的队列,高并发下性能最好的队列。
无锁,CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
V:要更新的变量,E:预期值, N:新值
只有当V==E时,V=N;否则表示已被更新过,则取消 当前操作。
package 多线程;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @Author 杨栋
* @Date 2020/7/8 17:37
* ConcurrentLinkedQueue的使用
*/
public class Demo6 {
public static void main(String[] args) throws InterruptedException {
// 1.创建安全队列
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
// 2. 入队操作
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
queue.offer(i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 6; i <10 ; i++) {
queue.offer(i);
}
}
});
// 3. 启动线程
t1.start();
t2.start();
t1.join();
t2.join();
// 4. 出队操作
int size = queue.size();
for (int i = 0; i <size ; i++) {
System.out.println(queue.poll());
}
}
}
BlockQueue接口(阻塞队列)
Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法
void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待。
E take() //获取并移除队列头部元素,如果没有可用元素,则等待
可用于解决生产者,消费者问题。
ArrayBlockingQueue
数组结构实现,有界队列。(手工固定上限)
LinkedBlockingQueue
链表结构实现,有界队列。(默认上限Integer.Max_VALUE)
ConcurrentHashMap
初始默认容量为16段(Segment),使用分段锁设计。
不对整个Map加锁,而是为每个Segment加锁。
当多个对象 存入同一个Segment时,才需要互斥
最理想的状态为16个对象分别存入16个Segment,并行数量16
使用方式与HashTable无异。
总结
ExecutrService线程池接口,Executors工厂。
Callable线程任务,Future异步返回值。
Lock,ReentrantLocak重入锁,ReenTrantWriteLock读写锁
CopyOnWriteArrayList线程安全的ArrayList
CopyOnWriteArraySet线程安全的Set
ConcurrentLinkedQueue线程安全的Queue
ArrayBlockQueue线程安全的HashMap