线程学习
1.线程就是独立的执行路径
2.在程序运行时,即使没有自己创建的线程,后台也会存在多个线程,eg:GC线程,主线程
3.main()称之为主线程,如果开辟了多个线程,线程的运行调度器安排调度,调度是与操作系统紧密相关的,先后顺序是不能人为干预的
4.对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
5.线程会带来额外的开销,如CPU调度时间,并发控制开销
6.每个线程在自己工作的内存交互,加载和存储主内存控制不当会造成数据不一致
线程创建方式:
1、继承Thread
public class StartThread extends Thread{
/**
* 线程入口点
*/
@Override
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌");
}
}
public static void main(String[] args) {
//创建子类对象
StartThread st =new StartThread();
//启动
st.start(); //不保证立即运行 cpu调用
//st.run(); //普通方法调用
for(int i=0;i<20;i++) {
System.out.println("一边coding");
}
}
}
注:1.执行线程必须调用start()加入到调度器中
2.start()不一定立即执行,系统安排调度分配执行
3.直接调用run()不是启动线程,是普通方法的调用
2、实现Runnable
package com.sxt.thread;
/**
* 创建线程方式二:
* 1、创建:实现Runnable+重写run
* 2、启动: 创建实现类对象 +Thread对象+ start
* 推荐: 避免单继承的局限性,优先使用接口
* 方便共享资源
*/
public class StartRun implements Runnable{
/**
* 线程入口点
*/
@Override
public void run() {
for(int i=0;i<20;i++) {
System.out.println("一边听歌");
}
}
public static void main(String[] args) {
/*//创建实现类对象
StartRun sr =new StartRun();
//创建代理类对象
Thread t =new Thread(sr);
//启动
t.start(); //不保证立即运行 cpu调用
*/
new Thread(new StartRun()).start();
//st.run(); //普通方法调用
for(int i=0;i<20;i++) {
System.out.println("一边coding");
}
}
}
代理模式:静态代理和动态代理
就绪状态—>运行状态
CPU调度
运行状态—>阻塞状态
1.sleep()
2.wait()
3.join()
4.read write
运行状态—>死亡状态
1.stop()
2.destriy()
线程常见的状态
1.sleep()
z注:sleep(time)指定当前线程阻塞的毫秒数
sleep()存在InterruptedException;每一个对象都有一个锁,sleep不会释放锁
2.yield()让当前正在执行的线程暂停
不是阻塞线程,而是将线程从运行状态转为就绪状态
让CPU调度器重新调度
3.join()为合并线程(插队线程)
待此线程执行完成后,再执行其他线程,其他线程阻塞
其他状态—>就绪状态
1.start()
2.阻塞状态解除
3.yield()运行状态让出CPU调度
4.jvm本地切换线程
线程分类:
用户线程
守护线程:为用户线程服务,jvm停止不用等待守护线程执行完毕
虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
虚拟机等待用户线程执行完毕才会停止
eg:后台记录操作日志、监控内存使用
线程安全
//非线程安全
public class WebThread implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(ticket>0){
System.out.println(Thread.currentThread().getName()+"----"+ticket--);
}
}
public static void main(String[] args) {
new Thread(new WebThread(),"线程一").start();
new Thread(new WebThread(),"线程貮").start();
new Thread(new WebThread(),"线程三").start();
}
}
//非线程安全
public class ListThread {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());}).start();
}
System.out.println(list.size());
}
}
并发:同一个对象被多个线程同时操作
线程同步:同一个进程的多个线程共享一块存储空间,为保证数据被访问时是准确性,需要在访问时加上锁机制(synchronized),当一个线程获得对象的排他锁,独占资源,其他的线程必须等待,使用后释放锁即可。存在以下问题:
1.一个线程持有锁会导致其他所有需要此锁的线程挂起;
2.在多线程竞争下,加锁,释放锁会导致较多的上下文切换和调度延迟;
3.如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
package com.thread.com;
public class SynBlockTest03 {
public static void main(String[] args) {
//一份资源
SynWeb12306 web =new SynWeb12306();
//多个代理
new Thread(web,"码畜").start();
new Thread(web,"码农").start();
new Thread(web,"码蟥").start();;
}
}
class SynWeb12306 implements Runnable{
//票数
private int ticketNums =10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
test5();
}
}
//线程安全:尽可能锁定合理的范围(不是指代码 指数据的完整性)
//double checking
public void test5() {
if(ticketNums<=0) {//考虑的是没有票的情况
flag = false;
return ;
}
synchronized(this) {
if(ticketNums<=0) {//考虑最后的1张票
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程不安全 范围太小锁不住
public void test4() {
synchronized(this) {
if(ticketNums<=0) {
flag = false;
return ;
}
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
//线程不安全 ticketNums对象在变
public void test3() {
synchronized((Integer)ticketNums) {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程安全 范围太大 -->效率低下
public void test2() {
synchronized(this) {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程安全 同步
public synchronized void test1() {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
死锁:多个线程各自占有一个共享的资源,并且互相等待其他线程占有的资源才能进行,而导致
两个或者多个线程都在等待对方释放资源。
线程池
线程是一个操作系统概念。操作系统负责这个线程的创建、挂起、运行、阻塞和终结操作。操作系统创建线程、切换线程状态、终结线程都要进行CPU调度-----这是很耗费时间和系统资源的事情。
线程池是指在初始化一个多线程应用程序过程中创建的一个线程集合,线程池在任务未到来之前,会创建一定数量的线程放入到空闲队列中,这些线程都是处于休眠状态,即均未启动,因此不消耗CPU,只是占用很小的内存空间,当请求到来之后,线程池给这次请求分配一个空闲的线程,把请求传入到此线程中运行,进行处理
使用线程池可以提升性能,减少CPU资源的消耗,同时还可以控制活动线程,防止并发线程过多,避免内存消耗过度
java.util.concurrent.ThreadPoolExecutor
java.util.concurrent.AbstractExecutorService
java.util.concurrent.ExecutorService(服务接口)
java.util.concurrent.Executor(java 线程池的顶级接口)
构造方法:
java.util.concurrent.ThreadPoolExecutor