`
线程
线程
进程:一个正在进行的程序
线程定义:轻量级进程,一个程序顺序执行流程
并发原理:CPU分时间片,交替执行 宏观并行 微观串行
CPU时间片:操作系统(OS)负责调度
数据空间: 堆空间共享(对象 等所有线程共享的),栈空间独立(局部变量等 所有线程之间特有的)
代 码:1>实现Runable接口 实现run方法 Runable对象:任务对象 new Thread(任务对象) 启动线程,调用start()方法
2>继承Thread类 覆盖run方法 new Thread() 启动线程,调用start()方法
并行 和 并发
并行:多个任务同时运行在同一个时间点(多核CPU执行)
并发:多个任务将请求提交给CPU,CPU分时间片执行
多线程的实现方式
继承类(Thread类)
创建一个线程的步骤:
1.继承Thread类
2.重写run方法
3.将线程要执行的代码写到run方法中
4.创建自定义类的对象
5.启动线程
实现接口(Runnable接口)
创建线程的步骤:
1.自定义一个类实现Runnable接口
2.实现run方法
3.将线程要运行的代码写到run方法中
4.创建自定义类的对象(资源对象)
5.创建一个线程对象(线程对象)
6.将资源对象交给线程对象
7.开启线程,调用start()方法
两种创建线程的方式比较
继承Thread类:
1.代码比较简洁
2.弊端:当继承Thread类后就不能继承别的类
实现Runnable接口:
1.代码比较复杂
2.优点:线程类可以继承别的类,实现Runnable接口
没有哪个更好,选择合适的就可以。
匿名内部类实现线程的方式
匿名内部类:条件是局部内部类必须继承一个类或者实现一个接口
继承Thread类的匿名内部类方式
public class Demo3 {
public static void main(String[] args) {
new Thread(){
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("第"+i+"次开头“);
}
}
}.start();;
new Thread(){
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("第"+i+"次结尾“);
}
}
}.start();;
}
}
实现接口的匿名内部类方式
public class Demo3 {
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("第"+i+"次开头“);
}
}
}).start();;
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("第"+i+"次结尾“);
}
}
}).start();;
}
}
方法
设置线程的名称:
1.通过构造方法设置线程的名称
2.通过setName()设置线程的名称
3.通过getName()获取线程的名称
4.currentThread():获取当前线程对象
Thread.sleep(int) 限时等待 休眠(int 毫秒 )(只能手动处理异常)
t.join() 当线程进入等待终止 才会恢复执行(处理异常)
Thread.yield() 放弃CPU 回到就绪状态(重新等OS选中)
t.setDaemon(true) 设置线程为守护线程,所有的非守护线程都结束时,进程就会结束(应设置在启动线程之前)
案列:
package com.wzx.entity;
public class TestThread2 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new Thread1());
Thread t2 = new Thread2();
t1.start();
//t2.setDaemon(true); //设置为守护(精灵)线程
t2.start();
/* t1.join();//无限期等待状态 当t1 t2线程交替执行完毕 才会执行主线程输出All Thread Over
t2.join();
System.out.println( Thread.currentThread().getName());//获取当前执行线程的名字 main
System.out.println("All Thread Over");*/
}
}
class Thread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 51; i++) {
System.out.println(i);
try {
Thread.sleep(100);//有限期等待状态 1000毫秒 = 1秒
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Thread2 extends Thread{
@Override
public void run() {
for(char c = 'A';c<='Z';c++){
System.out.println(c);
try {
Thread.sleep(180);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程的状态
[外链图片转存失败(img-FdoDotLr-1562821688632)(F:\166课件\线程的状态.png)]
初始状态:创建了一个线程,但是并没有启动
就绪状态:调用start方法之后就有了争夺cup的权利
运行状态:当一个处于就绪状态的线程抢到了cup,就从就绪状态到达运行状态,在同一时间片处于运行状态的线程只有一个
限时等待状态:当一个处于运行状态的线程调用了sleep(毫秒)方法之后,就处于了限时等待状态,当毫秒值一过,就从限时等待状态进入到了就绪状态
终止状态:当一个线程完全执行完了run方法中的代码,由运行状态到达终止状态
无限期等待状态:当一个线程中存在(其他的线程调用join()),那么当前的这个线程就进入到了无限期等待状态。
线程安全
线程安全:所有的线程操作一个对象,会产生混乱的情况,因此才要实现同步
临界资源:多线程并发时,共享的同一个对象
原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可被打乱或缺省。
线程同步的目的:多线程并发时,为了保证临界资源的正确性,而不能破坏程序中的原子操作。
实现线程同步的方式
1.同步代码块
synchronized(临界资源对象){//对临界资源对象加锁
//原子操作
}
锁对象(临界资源):所有的线程共享一个锁对象,每个锁对象有且只有一个锁标记(钥匙),只有拿到了所标记的线程才能进入到同步代码块中,锁对象可以为任意的一个对象,当一个线程执行完了一个原子操作后,会释放锁标记,所有的线程开始争夺锁标记,当一个线程既拿到了所标记又抢到了cpu的资源才能进入到同步代码块中。
2.同步方法
public synchronized 返回值类型 方法名(参数列表){//this当前对象
//原子操作
}
1>当同步方法为实例方法,他的锁对象是当前对象的引用this
2>当同步方法是静态方法时,同步方法的锁对象是,当前类的类对象 当前类.class
案列:
package com.wzx.entity;
public class TestThread3 {
public static void main(String[] args) throws InterruptedException {
final MyList list = new MyList();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//synchronized (list){
list.add("C");
// }
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//synchronized (list){
list.add("D");
// }
}
});
t1.start();
t2.start();
t1.join();
t2.join();
list.add("E");
list.print();
}
}
class MyList{
String[] data = {"A","B","","",""};
int index = 2;
public synchronized void add(String s){
data[index] = s;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
}
public void print(){
for (int i = 0; i < data.length; i++) {
System.out.println(data[i]);
}
}
}
等待通知机制
同步-->死锁-->线程通信(同步会产生死锁,死锁靠线程通信解锁)
线程通信 等待-通知
wait():当一个线程中的锁对象调用了wait方法,这个线程释放锁标记,进入到等待池中,如果没有其他线程去调用notify/notifyAll,会一直处于等待池中
wait(毫秒):当一个线程中的锁对象调用了wait(时间)方法,这个线程释放锁标记,进入到等待池中,当时间一过自动的从等待池中进入到锁池中
notify():是随机的从等待池中挑选一个线程进入到锁池中
notifyAll():将等待池中的所有线程都唤醒到锁池中,所有的线程在锁池中去抢锁标记,当抢到锁标记的线程由锁池到就绪状态,其他没有抢到锁标记的线程又回到了等待池中
以上的几个方法必须在同步代码块中运行,由锁对象调用
sleep(时间):当一个线程调用了sleep方法,进入到了限时等待状态,但是没有释放所标记,那么这个线程共享一把锁的线程都处于锁池中阻塞状态
案例:
package com.wzx.entity;
public class TestThread4 {
public static void main(String[] args) throws Exception {
//随便创建一个对象(匿名内部类调用外部类的局部变量需加final JDk1.8默认)
final Object o = new Object();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (o){
System.out.println("A");
System.out.println("B");
o.notify();
System.out.println("C");
System.out.println("D");
}
}
});
t.start();
synchronized (o){
System.out.println("1");
System.out.println("2");
o.wait();
System.out.println("3");
System.out.println("4");
}
}
}
结果:1 2 A B C D 3 4
案列:生产者、消费者(synchronized)
package com.wzx.entity;
public class TestThread5 {
public static void main(String[] args) {
final MyStack stack = new MyStack();
Runnable task1 = new Runnable(){
public void run(){
for(char c='A';c<='Z';c++){
stack.push(c+"");
}
}
};
Runnable task2 = new Runnable() {
public void run() {
for (int i = 1; i <= 26; i++) {
stack.pop();
}
}
};
new Thread(task1).start();
new Thread(task2).start();
}
}
class MyStack{
String[] data = {"","","","","",""};
int index;
//进栈(生产者)
public synchronized void push(String s){
while(data.length == index){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(s+" pushed ");
data[index] = s;
index++;
print();
this.notifyAll();//通知消费者
}
//出栈(消费者)
public synchronized void pop(){
while(index == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
String o = data[index];
data[index] = "";
System.out.print(o+" poped ");
print();
this.notifyAll();//通知生产者
}
//打印
public void print(){
for (int i = 0; i < data.length; i++) {
System.out.print(data[i]+" ");
}
System.out.println();
}
}
同步锁
同步锁
Lock接口:JDK5的新特性
实现类:ReentrantLock
Lock lock = new ReentrantLock();
上锁:lock.lock(); 解锁:lock.unlock();
方法:newCondition() await() signal()/signalAll()
//生成等待序列,可生成多个(synchronized 只有当前对象的一个等待序列)
Condition full = lock.newCondition();
full.await();//进入等待序列相当于wait()
emply.signalAll();//通知消费者(数组已满)相当于notifyAll()
生产者和消费者问题
有两个线程,共同去操作一个资源,一个线程负责消费,一个线程负责生产,当消费完了,通知生产者去生产,当生产者生产的商品满了,通知消费者去消费。涉及到线程之间的通行问题。
案列:
生产者、消费者(Lock)
package com.wzx.entity;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread5_1 {
public static void main(String[] args) {
final MyStack1 stack = new MyStack1();
new Thread(){
public void run(){
for(char c='A';c<='Z';c++){
stack.push(c+"");
}
}
}.start();
new Thread(){
public void run(){
for (int i = 1; i <= 26; i++) {
stack.pop();
}
}
}.start();
}
}
class MyStack1{
String[] data = {"","","","","",""};
int index;
Lock lock = new ReentrantLock();//创建锁lock
Condition full = lock.newCondition();//生成等待序列
Condition emply = lock.newCondition();
//生产者
public void push(String s){
try {
lock.lock();
while (data.length == index){
try {
full.await();//进入等待序列相当于wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(s+" pushed ");
data[index] = s;
index++;
print();
emply.signalAll();//通知消费者(数组已满)相当于notifyAll()
}finally {
lock.unlock();//解锁必须执行
}
}
//消费者
public void pop(){
try {
lock.lock();
while(index ==0){
try {
emply.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
String o = data[index];
data[index] = "";
System.out.print(o+" poped ");
print();
full.signalAll();//通知生产者
}finally {
lock.unlock();
}
}
//打印
public void print(){
for (int i = 0; i < data.length; i++) {
System.out.print(data[i]+" ");
}
System.out.println();
}
}
线程池
常量池 串池 锁池 等待池 线程池
线程池:线程容器,可设定线程分配的数量上限,将预先创建线程对象存入池中,并重用线程池中的线程。
线程池好处:减少创建和销毁线程的次数,每个工作线程可用于执行多个任务,只需将任务交给线程,即可重复利用。
获取线程池:
线程池的父接口:Executor
子接口:ExecutorService
线程池的工厂类(工具类):
工厂类中的方法:
1.获取固定数量的线程池:
newFixedThreadPool(int nThreads) :创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
ExecutorService es = Executors.newFixedThreadPool(2);
2.获取动态数量的线程池:
newCachedThreadPool():创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
ExecutorService es = Executors.newCachedThreadPool();
Callable接口
Runnable接口:run方法 无法抛出异常 没有返回值
JDK5.0加入Callable线程接口,主要的方法是call方法,相比较之前的run方法,call方法可以抛出任意的异常,并且有返回值。
Future对象call方法的返回结果,可利用Future中的get方法获取结果内容。
案列:
Future<Integer> f = pool.submit(c);
System,out,println(f.get());
案例:
创建两个线程
第一个线程用于计算 100以内所有偶数的和
第二个线程用于计算 100以内所有奇数的和
将两个线程的计算结果进行相加 得出结果 5050
package com.wzx.entity;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestThreadPool2 {
public static void main(String[] args) throws Exception{
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> f1 = es.submit(new Callable<Integer>(){
public Integer call() throws Exception{
int result = 0;
for(int i = 0;i<=100;i+=2){
result+=i;
}
return result;
}
});
Future<Integer> f2 = es.submit(new Callable<Integer>(){
public Integer call() throws Exception{
int result = 0;
for(int i=1;i<100;i+=2){
result+=i;
}
return result;
}
});
System.out.println(f1.get()+f2.get());
}
}
线程安全的集合
加锁的对锁优化的方式
1.线程安全的集合
Vector Hashtable
集合是用于存储数据的(读数据 写数据)
案列:
案例:
package cn.wzx.Deam;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Demo8 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();//不安全
List<Integer> synchronizedList = Collections.synchronizedList(list);//安全的集合
Set<Integer> set = new HashSet<>();//不安全
Set<Integer> synchronizedSet = Collections.synchronizedSet(set);
Collection<Integer> collection = new ArrayList<>();
Collection<Integer> synchronizedCollection = Collections.synchronizedCollection(collection);
Map<Integer, String> map = new HashMap<>();
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(map);
}
}
2.读写锁
读线程 和 读线程 不影响线程安全 不用加锁
读 和 写 必须加锁
写 和 写 必须加锁
在实际的开发中对数据
一个网站 注册(写) 登陆(读)
读锁 写锁
一个线程拿到读锁的锁标记 另一个线程也拿到了读锁的锁标记 不影响
读锁 读锁 朋友
写锁 读锁 死对头
写锁 写锁 死对头
现在中实现一个自己的集合 让集合中的方法 读操作的方法上读锁(size get) 写操作的方法上写锁(add remove clear)
读 写
读 非阻塞 阻塞
写 阻塞 阻塞
应用场景:当读的操作多于写的操作的时候可以用度写锁解决
读写锁 ReadWriteLock 写锁未分配时,读锁可以反复分配 读写分离
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();//读锁共享锁
Lock writeLock = lock.writeLock();//写锁互斥锁
案列:package com.wzx.entity;
import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestThreadLock {
public static void main(String[] args) {
}
}
class MyList extends ArrayList {
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();//读锁共享锁
Lock writeLock = lock.writeLock();//写锁互斥锁
//读锁
@Override
public int size(){
try{
readLock.lock();
return super.size();
}finally {
readLock.unlock();
}
}
@Override
public Object get(int index){
try{
readLock.lock();
return super.get(index);
}finally {
readLock.unlock();
}
}
//写锁
@Override
public boolean add(Object e){
try{
writeLock.lock();
return super.add(e);
}finally {
writeLock.unlock();
}
}
@Override
public Object remove(int index){
try{
writeLock.lock();
return super.remove(index);
}finally {
writeLock.unlock();
}
}
@Override
public void clear(){
try{
writeLock.lock();
super.clear();
}finally {
writeLock.unlock();
}
}
}
3.分段锁提高数据存储效率的集合
ConcurrentHashMap<K,V>
HashSet的底层数据存储的结构 数组链表
数组链表:一个数组的每一个元素是一张链表
原理:分段锁为每个下标位置分配了一把锁,将锁的细粒度减小 从而提高了数据存储的效率和并发
package cn.wzx.Deam;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Demo12 {
public static void main(String[] args) {
HashMap<Integer, String> hashMap = new HashMap<>();
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(hashMap);
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();//锁的细粒度减小 线程安全的集合
}
}
采用无锁的算法实现线程安全的集合
1.CopyOnWriteArrayList
原理: 当对集合进行读操作的时候 没有锁 当对集合进行写操作的时候 采用的是将原数组进行复制,在复制之后的数组进行写操作,写完之后,将新的数组的地址赋给旧的引用
2.
队列:先进先出
栈:后进先出
ConcurrentLinkedQueue<E>
存储数据实现线程安全的原理:采用的无锁比较算法(cas)
效率都高于加同步:
CopyOnWriteArrayList 利用复制数组的方式实现数组元素的修改 写效率低 读效率高(读操作远高于写操作)
CopyOnWriteArratSet
ConcurrentHashMap 分段锁(分成16段) 性能远远优于Hashtable
ConcurrentLinkedQueue 线程安全的队列(链表实现) 无锁算法(CAS) 利用无锁算法实现线程安全
集合的整理
Collection
|--List (ArrayList LinkedList Vector CopyOnWriteArrayList)
|--Set (HashSet LinkedHashSet CopyOnWriterArraySet)
|--SortedSet (TreeSet)
|--Queue (LinkedList ConcurrentLinkedQueue)
|--BlockingQueue (ArrayBlockingQueue LinkedBlockingQueue)
Map (HashMap LinkedHashMap Hashtable Properties ConcurrentHashMap)
|--SortedMap (TreeMap)
线程安全(实现类):
Vector CopyOnWriteArrayList CopyOnWriteArraySet ConcurrentLinkedQueue
ArrayBlockingQueue LinkedBlockingQueue Hashtable ConcuerentHashMap
E>
存储数据实现线程安全的原理:采用的无锁比较算法(cas)
效率都高于加同步:
CopyOnWriteArrayList 利用复制数组的方式实现数组元素的修改 写效率低 读效率高(读操作远高于写操作)
CopyOnWriteArratSet
ConcurrentHashMap 分段锁(分成16段) 性能远远优于Hashtable
ConcurrentLinkedQueue 线程安全的队列(链表实现) 无锁算法(CAS) 利用无锁算法实现线程安全
## 集合的整理
```jsva
Collection
|--List (ArrayList LinkedList Vector CopyOnWriteArrayList)
|--Set (HashSet LinkedHashSet CopyOnWriterArraySet)
|--SortedSet (TreeSet)
|--Queue (LinkedList ConcurrentLinkedQueue)
|--BlockingQueue (ArrayBlockingQueue LinkedBlockingQueue)
Map (HashMap LinkedHashMap Hashtable Properties ConcurrentHashMap)
|--SortedMap (TreeMap)
线程安全(实现类):
Vector CopyOnWriteArrayList CopyOnWriteArraySet ConcurrentLinkedQueue
ArrayBlockingQueue LinkedBlockingQueue Hashtable ConcuerentHashMap