概念
程序:安装在系统中的软件,但未运行
进程:在操作系统中独立运行的程序,每运行一个应用程序就对应着一个进程(process)
多进程:在操作系统中可以同时运行多个程序(Thread)
特性:
一个进程中可以包含多个线程,且至少要有一个线程
一个线程必须属于某个进程,进程是线程的容器
一个进程中的多个线程共享该进程的所有资源
只是CPU在很短的时间内,在不同程序间切换,轮流执行每个程序,执行速度很快,所以感觉上像是同时在运行
3种创建方法
class TestThread1 extends Thread
@Override
public void run()
TestThread1 t1 = new TestThread1("自定义线程名");
t1.start();
// 调用后不会立刻执行,需要等到 cpu 分配了时间片才执行
// 如果调用 run 方法就只是调用了一个普通方法,start 会开启一个线程
// getName() 获取自定义线程的名字
// setName() 设置自定义名字
// main线程使用静态类方法 Thread.currentThread().getName()
// 自定义线程名字Thread-0开始,主线程默认名字main
class MyRun2 implements Runnable {
@Override
public void run() {
MyRun2 myRun2 = new MyRun2 ();
Thread t2 = new Thread(myRun2);
t2.start();
前两种方式区别:
1,接口方式避免了单继承的局限性
2,适合多个相同程序代码的线程去处理同一个资源
(继承方式会将同一个资源多次操作,接口方式共同操作一个票数 int num = 100,不会重复操作)
class TestThread3 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
return true;
}
TestThread3 t3 = new TestThread3();
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> result1 = ser.submit(t3);
//获取结果
boolean r1 = result1.get();
System.out.println(r1);
ser.shutdownNow();
生命周期
各个状态分别为:
NEW
至今尚未启动的线程处于这种状态。
RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED
已退出的线程处于这种状态。死亡后不能再次启动,生命只有一次
API
t1.wait(); //自行放弃锁,进入等待状态
t1.interrupt();//中断阻塞(等待状态是阻塞的一种)类似自我激活
t1.isAlive() //测试线程是否处于活动状态
Thread.currentThread().getName().equals("兔子") //获取当前线程name
Thread.sleep(2000);
模拟网络延时,睡眠,默认毫秒 1000ms=1s
休眠 , 可以扩大安全问题的发生性.
存在异常 InterruptedException 进入就绪状态
每一个对象都有一个锁,sleep不会释放锁;
//run和start
new Thread(race,"兔子").start();
调用线程的start方法来启动线程,此时处于就绪状态,可以被调度,实现多线程,在新的线程中执行。
调用线程的run方法是在主线程中执行该方法,和调用普通方法一样,并不能实现多线程。
//礼让yield
礼让线程,让当前正在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态
等待cpu重新调度,礼让不一定成功,因为很可能马上又得到时间片
//插队join
比如main线程正在执行,此时 t1.join() 强行插队进来,会先执行完t1,再接着执行main
//优先级Priority
范围从1~10,main 默认是5
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
使用以下方式改变或获取优先级
getPriority()
.setPriority(int xxx)
优先级低只是意味着获得调度的
概率低.并不是优先级低就不会
被调用了.这都是看CPU的调度
//守护线程daemon
用户线程main: 虚拟机要等待用户线程执行完毕
守护线程: 虚拟机 不用等待 守护线程 执行完毕
用户线程main结束后,守护线程之后会自动结束,但不会立刻结束,稍微延迟
t1.setDaemon(true); //设置线程为守护线程,默认为false
静态代理
public class StaticProxy {
public static void main(String[] args) {
//代理对象 代理 真实对象
You you = new You();
you.happyMarry();
new WeddingCompany(you).happyMarry();
}
}
//共同的接口:结婚
interface Marry{
void happyMarry();
}
//真实对象:你
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("我要结婚了,好hi呦");
}
}
//代理对象:婚庆公司
class WeddingCompany implements Marry{
//婚庆需要有你这个人 , 代理对象需要代理一个真实对象
private Marry you;
public WeddingCompany(Marry you){
this.you = you;
}
@Override
public void happyMarry() {
this.you.happyMarry();//你要结婚
}
}
lambda在线程中使用
//原先方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("你好二");
}
}).start();
//线程体只有一行可以省略到极致
new Thread(()-> System.out.println("你好二")).start();
//如果线程体有多行 , 用一个代码块包裹起来就好.
new Thread(()-> {
for (int i = 0; i < 40; i++) {
System.out.println("你好二");
}
}).start();
线程同步安全问题
当多个线程同时操作一个对象时,由于CPU的切换,导致一个线程只执行了关键代码的一部分,还没执行完。
关键代码是必须原子性执行的,此时另一个线程参与进来,导致共享数据发生错乱使用(设置sleep时间后极易发生异常)
被synchronized包围的代码块,称为同步代码块:synchronized (obj/this) {
}
被synchronized修饰的方法,称为同步方法:public synchronized void method1() {
}使用更多一些
锁,也称为对象锁,每个对象都自带一个锁(标识),且不同对象的锁是不一样的
执行过程:
当多个线程同时执行同步代码块或同步方法时,必须获取一个共用的对象的锁才行
(锁是哪个对象的,严格来说无所谓,只是为了锁保持独立,保持代码块原子性运行,来避免共享资源时的安全问题)
且一旦对象的锁被获取,则该对象就不再拥有锁,直接线程执行完 同步代码块或同步方法时 才会释放对象的锁
如果线程无法获取特定对象上的锁,则线程会进入该对象的锁池中等待,直到锁归,
此时去竞争该锁,因为可能有其他线程也在等待,拿到锁的就开始执行
线程同步的优缺点:
优点:解决了线程安全的问题,使代码块在某一时间只能被一个线程访问
缺点:由于需要进行锁的判断,消耗资源,效率变低
死锁
多个进程在运行过程中因争夺资源而造成的一种僵局
例如:a线程需要b的资源,而b线程在给a资源之前必须得到a的锁对象,因此ab线程形成僵局,都不能得到想要的资源
产生死锁的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系
//化妆的方法
public void makeup() throws InterruptedException {
if (choice==0){
//先拿口红,再拿镜子
synchronized (lipStick){
System.out.println("拿到口红");
Thread.sleep(1000);
//等待拿镜子的人释放锁
synchronized (mirror){
System.out.println("拿到镜子");
}
}
}else {
//先拿镜子 , 再拿口红
synchronized (mirror){
System.out.println("拿到镜子");
Thread.sleep(2000);
//等待拿口红的人释放锁
synchronized (lipStick){
System.out.println("拿到口红");
}
}
}
}
//化妆的方法
public void makeup() throws InterruptedException {
if (choice==0){
//先拿口红,再拿镜子
synchronized (lipStick){
System.out.println("拿到口红");
Thread.sleep(1000);
//等待拿镜子的人释放锁
}
synchronized (mirror){
System.out.println("拿到镜子");
}
}else {
//先拿镜子 , 再拿口红
synchronized (mirror){
System.out.println("拿到镜子");
Thread.sleep(2000);
//等待拿口红的人释放锁
}
synchronized (lipStick){
System.out.println("拿到口红");
}
}
}
lock
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)
@Override
public void run() {
while (true){
try {
lock.lock(); //加锁
if (ticketNums>0){
//判断是否有票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else {
break;
}
} finally {
lock.unlock();//解锁
}
}
}
生产者 消费者
生产者要持续供货,消费者不间断的使用,如果产品不够就是用缓冲区
public class ProductPool {
private int num; // 商品数量
private static final int MAX_COUNT = 20; // 最大数量
public void put() {
num++; }
public void get() {
num--; }
public int getNum() {
return num; }
public boolean isEmpty() {
return this.num == 0; }
public boolean isFull() {
return this.num == MAX_COUNT; }
}
public class Producer extends Thread {
private String name; // 生产者名称
private ProductPool pool; // 商品池
public Producer(String name, ProductPool pool) {
this.name = name;
this.pool = pool;
}
@Override
public void run() {
while (true) {
synchronized (pool) {
if (pool.isFull()) {
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
pool.put();
System.out.println(this.name + "生产了一个商品,现在商品数量:" + pool.getNum());
pool.notifyAll(); //唤醒消费者继续消费
}
}
try {
Thread.sleep(3000); // 让生产过程慢一点
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer extends Thread {
private String name; // 消费者名称
private ProductPool pool; // 商品池
public Consumer(String name, ProductPool pool) {
this.name = name;
this.pool = pool;
}
@Override
public void run() {
while (true) {
synchronized (pool) {
if (pool.isEmpty()) {
try {
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
pool.get();
System.out.println(this.name + "消费了一个商品,现在商品数量是:" + pool.getNum());
pool.notifyAll(); // 唤醒生产者继续生产
}
}
try {
Thread.sleep(5000); // 让消费过程慢一点
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
ProductPool pool = new ProductPool();
Producer p1 = new Producer("p1", pool);
Producer p2 = new Producer("p2", pool);
Producer p3 = new Producer("p3", pool);
Consumer c1 = new Consumer("c1", pool);
Consumer c2 = new Consumer("c2", pool);
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
}
}
ThreadPool
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理(....)
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
public class ThreadTest extends Thread{
public static void main(String[] args) {
//创建一个线程池(池子大小)
ExecutorService pool = Executors.newFixedThreadPool(10);
//执行runnable接口实现类
pool.execute(new MyThread4());
pool.execute(new MyThread4());
pool.execute(new MyThread4());
pool.execute(new MyThread4());
pool.shutdown();
}
}
class MyThread4 implements Runnable{
@Override
public void run() {
System.out.println("MyThread4");
}
}
线程间的通信
每个对象都自带锁池和等待池
锁池:
当线程执行synchronized块时如果无法获取特定对象上的锁,此时会进入该对象的锁池
当锁被归还给该对象时,锁池中的多个线程会竞争获取该对象的锁
获取对象锁的线程将执行synchronized块,执行完毕后会释放锁
等待池:
当线程获取对象的锁后,可以调用 wait() 方法放弃锁,此时会进入该对象的等待池
当其他线程调用该对象的 notify() 或 notifyAll() 方法时,等待池中的线程会被唤醒,会进入该对象的锁池
当线程获取对象的锁后,将从它上次调用wait()方法的位置开始继续运行
wait
使线程放弃对象锁,线程进入等待池 可以调用等待超时时间,超时后线程会自动唤醒
notify
随机唤醒等待池中的一个线程,线程进入锁池 唤醒的是特定对象的等待池中的线程
notifyAll 唤醒特定对象的等待池中的所有线程
注意:
这三个方法都只能在synchronized块中使用,即只有获取了锁的线程才能调用
等待和唤醒必须使用的是同一个对象(两个线程构造方法都传入同一个obj即可)
Wait w = new Wait(); //wait线程中调用了wait方法,进行放弃锁,所以会一直等待
w.start();
Notify n = new Notify();// notify线程中进行的notifyAll方法来唤醒所有线程
n.start();//虽然都在synchronized块中,但不是同一个对象,并不能唤醒
解决方法:两个线程各加一个属性obj,然后new Wait(obj) new Notify()传进去
线程单例
自定义一个 类,其实有现成的ThreadLocal
public class MyThreadLocal<T> {
private Map<Thread, T> map = new HashMap<>();
public void set(T t) {
map.put(Thread.currentThread(), t); // 将当前线程作为Key
}
public T get() {
return map.get(Thread.currentThread());
}
}
public class Test {
// static MyThreadLocal<String> local=new MyThreadLocal<String>();
static ThreadLocal<String> local=new ThreadLocal<String>();
public static void main(String[] args) {
local.set("aaa");
local.set("bbb");
local.set("ccc");
System.out.println(local.get());
System.out.println(local.get());
System.out.println(local.get()==local.get());
new Thread(new Runnable() {
@Override
public void run() {
local.set("aaa");
System.out.println(local.get());
}
}).start();
}
}