进程:运行的程序。
线程:进程的执行执行路径,多个路径,即多线程
每个进程都有的基本组成:
1-CPU时间片 ,操作系统为每个线程分配执行时间。
2-运行数据
(栈空间:存储线程要的局部变量,每个线程栈都独立的)
(堆内存:存储线程要的的对象,多个线程可共享堆对象)
3-线程逻辑代码
创建不同线程
/**
* @author Mr.Gao
* @version 1.0
* @date 2021/4/8 11:29
*/
class A extends Thread {
@Override
public void run() {
//System.out.println(super.getName());
System.out.println(Thread.currentThread().getName()+"-----"); //两种获取线程名字。
//Thread.sleep(1); 子线程休眠
for (int i = 0; i < 30; i++) {
System.out.println("子线程:---"+i);
}
}
}
public class Test {
public static void main(String[] args) throws IOException, InterruptedException {
A a = new A();
a.start();// 启动线程,将a对象放入线程组,供CPU调度,CPU轮到你时,才执行该对象的run方法。
//a.start(); //同一个a对象不能同时启动多次会抛java.lang.IllegalThreadStateException,同一个线程执行后会,会改变threadstatus ,就会抛异常
a.setName("fage"); //设置子线程的线程名字
a.setPriority(Thread.MIN_PRIORITY);//设置优先级
new A().start();
Thread.sleep(1); //主线程休眠
for (int i = 0; i < 30; i++) {
System.out.println("主线程:---"+i);
if (i==10){
a.join(); //子线程插队,插到主线程中。
}
}
}
}
yield() 线程礼让:让出时间(没有给谁),继续争抢。插队的那个会直接执行完毕,再执行。
不同线程操作同一份数据,可能会出现线程安全(数据异常):
加锁
代码块锁/方法锁
//注意锁的范围。及锁同一个对象。
String name=Thread.currentThread().getName();//
synchronized (锁对象){
锁住的内容,让该线程走完这一趟。
}
两种创建线程方式的区别。
死锁:嵌套锁容易出现,尽量规避。
有线程进入到锁中,没释放锁,其它线程又在外等待。
针对多个线程,同时作用到一个锁时,一个要进,一个要出。谁也不让
synchronized (this) { //加锁--确保生产和消费是同一个锁对象
if(num>=20) {
this.wait(); //线程的等待 但是没占用锁了。让该线程暂停,其它线程不受影响。
}
num++;
System.out.println("已经生产了第"+num+"件货");
this.notify(); //唤醒
}
线程池利用:
先将一定数目的线程放入池中,用的时候拿出来,用完放回线程池。
线程池中的线程,如同公交车,谁都 可以上。用完就还给池子。
避免频繁创建销毁线程对象。
/**带缓冲区的线程池+手动关闭
* @author Mr.Gao
* @version 1.0
* @date 2021/4/10 10:44
*/
class Task implements Runnable{
@Override
public void run() {
for (int i = 0; i < 60; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
public class Test1 {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
Task task = new Task();
es.submit(task);
es.submit(task);
es.submit(task);
es.shutdown();
}
}
ExecutorService es = Executors.newCachedThreadPool();与上面区别是:不用指定线程数量,会智能分配。
Callable接口:产生-任务
线程池执行callable任务,返回future
//Callable接口
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <=50; i++) {
sum+=i;
}
return sum;
}
});
Integer num=future.get(); //返回的值 。
es.shutdown();
Lock接口:ReentrantLock() 互斥锁
synchronized{}代码块/synchronizedz()方法一样的效果
void lock();//获取锁
void unlock();//释放锁
/**
* @author Mr.Gao
* @version 1.0
* @date 2021/4/10 10:44
*/
class MyList {
private Lock lock = new ReentrantLock();
public String[] strs = {"A","B","","",""};
private int count = 2; //记录下标
public void add(String value) {
lock.lock(); //获取锁
try {
strs[count] = value;
count++;
} finally {
lock.unlock(); //释放锁,注意,往往需要放到finally中,确保获取了,一定释放锁
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyList list = new MyList();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
list.add("hello");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
list.add("fage");
}
});
t2.start();
t1.join(); //为让打印线程最后。
t2.join();//为让打印线程最后。
System.out.println("结果:"+ Arrays.toString(list.strs));//这里也是一个打印线程。
}
}
读写锁:ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();
List list = new CopyOnWriteArrayList<>(); //支持并发读和写, ArrayList 却不支持并发读写。但他们用法一样。
CopyOnWriteArraySet was = new CopyOnWriteArraySet<>();
存储的元素是唯一的。
ConcurrentHashMap cHashMap = new ConcurrentHashMap();
初始16个段,为每个段加锁。
ConcurrentHashMap cHashMap = new ConcurrentHashMap<String,Integer>();
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(new Runnable() {
@Override
public void run() {
cHashMap.put(""+temp,8);
System.out.println(cHashMap);
}
}).start();
ConcurrentLinkedQueue 并发性能最好队列,高效读写,线程安全
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("hello");
queue.offer("world");
queue.poll(); //删除hello
queue.peek(); //获得world
System.out.println(queue);
JDK 的 CopyOnWriteArrayList/CopyOnWriteArraySet 容器正是采用了 COW 思想,它是如何工作的呢?简单来说,就是平时查询的时候,都不需要加锁,随便访问,只有在更新的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。