线程
使用场景:多线程可以解决资源耗时 提升效率
进程和线程
进程是一个内存中运行的应用程序,它是系统运行程序的基本单位。
线程是进程中的一个执行单元,负责当前进程执行的一个执行单元,一个进程中至少有一个线程。
JVM支持多线程(如垃圾回收器)
public class Test01_JVM {
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i < 1000000; i++) {
new Test(i);
}
// 通知垃圾回收器 需要回收对象(不一定回收)
System.gc();
// 当前线程睡眠3秒 此时不占用资源
Thread.sleep(300000);
System.err.println("main end...");
}
}
//main线程休眠3s
class Test {
int n;
public Test(int n) {
this.n = n;
}
//当GC进行垃圾回收指定对象的时候,对象的finalize方法会被自动
@Override
protected void finalize() throws Throwable {
System.out.println("Test被销毁, n: " + n);
super.finalize();
}
}
可以使用jsconsole命令查看线程情况(在cmd窗口执行);
线程调度策略(并发 单核)
-
多线程共享同一个cpu的 此时是轮流获取cpu
-
哪个线程获取了cpu,此时才能运行,没有获取cpu的,就等待
-
获取到cpu的这段时间,叫时间片
-
时间片不会被某线程独享,会随机切换分配
-
-
线程调度策略(分配时间片方案)
-
轮流
-
抢占(jvm)
-
线程等级高的优先获取时间片,如果等级一样,随机分配
-
-
内存分配
-
每个线程拥有各自的栈空间 互相独立
-
线程共享堆和方法区
线程的创建方式 (线程的执行顺序不可控)
-
创建Thread/Thread子类
-
通过Runnable创建Thread (复用run方法)
-
通过Callable创建有返回值的线程
-
call方法虽然有返回值,但是会导致阻塞
-
-
通过线程池创建
线程的种类
-
前台线程
-
如果存在前台线程,jvm不会关闭
-
默认开启的线程是前台线程,可以手动设置为守护线程
-
-
守护线程
线程的方法
-
静态方法
-
currentThread
-
-
普通方法
-
run 线程的执行逻辑 如果直接调用,不是新线程 是main线程
-
start 开启线程,会自动调用run
-
set/getName() 设置、获取线程名字
-
setPriority(10) 设置线程优先级
-
setDaemon(true) 守护线程
-
stop() 停止正在运行的线程(不建议使用 有安全隐患)
-
==触发线程状态转换的方法==
-
sleep(时间) == TimeUnit.SECONDS.sleep(10);
-
当前线程阻塞睡眠(timed_waitting),10秒后会自动进入可运行状态(runnable)
-
期间不获取时间片,无法运行
-
在匿名内部类中不能抛出异常,只能捕获异常
-
单位是毫秒
-
-
join() t1.join
-
当前线程阻塞无限等待(waitting),等插入的线程执行完进入可运行状态(runnable)
-
-
join(时间) t1.join 类似sleep
-
当前线程阻塞有限等待(timed_waitting),等时间过后完进入可运行状态(runnable)
-
-
wait()
-
notify();
==线程状态变化(线程有五大状态)==
-
NEW
-
创建线程时
-
-
RUNNABLE
-
start-》就绪 时间片-》运行
-
sleep时间结束回到就绪状态
-
join插入的线程执行完
-
join时间过后
-
处于锁阻塞的线程抢到锁后
-
notify/notify();
-
-
TIMED_WAITTING
-
sleep(时间)
-
join(时间)
-
-
WAITTING
-
join(); 本质就是调用wait() t1.join()
-
wait();
-
-
BLOCKED
-
线程没抢到锁
-
-
TERMINATED
-
run方法结束
-
==线程状态==
-
正常状态 NEW -> RUNNBALE(Runnable(就绪)/Running(运行)) -> TERMINED
-
阻塞状态
-
timed_waitting 有限等待
-
waitting 无限等待
-
blocked 锁阻塞
-
线程安全问题 (多线程 并发 写 共享资源,可能导致读的预期效果不一致)
解决思路: 控制临界资源只能同时被一个线程访问
==安全和效率==
-
线程安全 效率低 StringBuffer Vector
-
线程不安全 效率高 StringBuilder ArrayList
兼顾安全和效率(并发专题 无锁机制 cas)
-
通过工具类将线程不安全的转为线程安全
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* StringBuilder 拼接字符串
* @author vanse
*
*/
public class TestStringBuilder {
public static void main(String[] args) {
StringBuffer sb1 = new StringBuffer("a");
// 链式编程 在字符数组自身改变
StringBuilder sb = new StringBuilder("a");
String finalS = sb.append("b").append("c").toString();
System.out.println(finalS);
// s+= "bc"; 字符数组没有提供设置内容的方法
List<String> list = new ArrayList<>();
// 线程安全的ArrayList
List<String> synchronizedList = Collections.synchronizedList(list);
}
}
具体方案 锁机制
-
synchronized jdk提供的默认关键字,可对临界区上锁,保护资源
-
修饰代码块 同步(原子)代码块
-
用对象充当锁
-
用类充当锁
-
-
修饰方法 同步方法 只要保证同一个锁对象即可
-
如果是普通方法 锁对象是当前对象this
-
如果是静态方法 锁对象是当前类
-
-
==注意: 多个线程必须使用同一把锁对象==
线程通信 (解决线程之间以某种规则运行的问题)
生产消费队列模型(mq)
通过等待唤醒机制 Object的方法wait和notify
-
wait() 当前线程进入无限等待状态,除非调用同一个锁对象的唤醒方法
-
会释放锁
-
可能存在虚假唤醒的问题
-
-
notify() 将等待池中的某个线程唤醒
-
notfifyAll() 唤醒等待池中的所有线程唤醒
这两个方法都需要在同步资源中使用,并且需要用锁对象调用。
线程学习案例1:
证明共享同一个对象调用方法不加锁
第二个线程不需要等待线程一释放锁,哪怕他先执行;
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
Myclass mc=new Myclass();
Thread t1=new MyThread(mc);
Thread t2=new MyThread(mc);
t1.setName("虎子");
t2.setName("子虎");
t1.start();
Thread.sleep(100);//让主程序先睡会保证t1线程先执行;
t2.start();
}
}
class MyThread extends Thread{
private Myclass mc;
public MyThread(Myclass mc) {
this.mc=mc;
}
@Override
public void run() {
if("虎子".equals(Thread.currentThread().getName())) {
mc.dosome();
}else {
mc.doOther();
}
}
}
class Myclass{
public synchronized void dosome() {
System.out.println("DSbegin");
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("DSend");
}
public void doOther() {
System.out.println("DObegin");
System.out.println("DOend");
}
}
线程案例2:
证明共享同一个对象调用方法都加锁
第二个线程需要等待线程一释放锁,线程一先执行;
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
Myclass1 mc=new Myclass1();
Thread t1=new MyThread1(mc);
Thread t2=new MyThread1(mc);
t1.setName("虎子");
t2.setName("子虎");
t1.start();
Thread.sleep(100);//让主程序先睡会保证t1线程先执行;
t2.start();
}
}
class MyThread1 extends Thread{
private Myclass1 mc;
public MyThread1(Myclass1 mc) {
this.mc=mc;
}
@Override
public void run() {
if("虎子".equals(Thread.currentThread().getName())) {
mc.dosome();
}else {
mc.doOther();
}
}
}
class Myclass1{
public synchronized void dosome() {
System.out.println("DSbegin");
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("DSend");
}
public synchronized void doOther() {
System.out.println("DObegin");
System.out.println("DOend");
}
}
线程案例3:
证明共享对象有两个(两把锁)调用方法都加锁
第二个线程需要等待线程一释放锁嘛?线程一先执行;
不需要,找各自的锁,有100个共享对象就会有100把锁
public class Demo03 {
public static void main(String[] args) throws InterruptedException {
Myclass2 mc1=new Myclass2();
Myclass2 mc2=new Myclass2();
Thread t1=new MyThread2(mc1);
Thread t2=new MyThread2(mc2);
t1.setName("虎子");
t2.setName("子虎");
t1.start();
Thread.sleep(100);//让主程序先睡会保证t1线程先执行;
t2.start();
}
}
class MyThread2 extends Thread{
private Myclass2 mc;
public MyThread2(Myclass2 mc) {
this.mc=mc;
}
@Override
public void run() {
if("虎子".equals(Thread.currentThread().getName())) {
mc.dosome();
}else {
mc.doOther();
}
}
}
class Myclass2{
public synchronized void dosome() {
System.out.println("DSbegin");
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("DSend");
}
public synchronized void doOther() {
System.out.println("DObegin");
System.out.println("DOend");
}
}
线程案例4:
证明共享对象有两个(两把锁)调用静态方法都加锁(此时加的是类锁)
哪怕有100个对象但是也是同一个类创建的那就会占用
第二个线程需要等待线程一释放锁嘛?线程一先执行;
需要,找同一个类锁,有100个共享对象只有1把类锁(Myclass)
public class Demo04 {
public static void main(String[] args) throws InterruptedException {
Myclass4 mc1=new Myclass4();
Myclass4 mc2=new Myclass4();
Thread t1=new MyThread4(mc1);
Thread t2=new MyThread4(mc2);
t1.setName("虎子");
t2.setName("子虎");
t1.start();
Thread.sleep(100);//让主程序先睡会保证t1线程先执行;
t2.start();
}
}
class MyThread4 extends Thread{
private Myclass4 mc;
public MyThread4(Myclass4 mc) {
this.mc=mc;
}
@Override
public void run() {
if("虎子".equals(Thread.currentThread().getName())) {
mc.dosome();
}else {
mc.doOther();
}
}
}
class Myclass4{
public synchronized static void dosome() {
System.out.println("DSbegin");
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("DSend");
}
public synchronized static void doOther() {
System.out.println("DObegin");
System.out.println("DOend");
}
}
线程安全案例
账户资产信息安全
账户类:
public class Account {
private String aName;//账户信息
private int money;//账户余额
public Account(String aName, int money) {
this.aName = aName;
this.money = money;
}
public Account() {}
public String getaName() {
return aName;
}
public void setaName(String aName) {
this.aName = aName;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
//获取到账户余额
//多线程并发执行造成取钱不稳定,取完还能取
public void getAccount(int gMoney) {
//账户取出前:
int before=this.getMoney();
//账户取出后:
int after=before-gMoney;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setMoney(after);
}
}
账户提取资产线程类:
public class AccountThread implements Runnable {
private Account at1;
public AccountThread(Account at1) {
this.at1=at1;
}
@Override
public void run() {
//两个栈,两个线程
int money=5000;
at1.getAccount(money);
System.out.println(Thread.currentThread().getName()+"取钱:"
+money+"元 账户还剩下"+at1.getMoney()+"元");
}
}
账户初始设定(启动)类:
public class Test {
public static void main(String[] args) {
Account act=new Account("ada--1",10000);
AccountThread t = new AccountThread(act);
Thread t1 = new Thread(t,"小祝");
Thread t2 = new Thread(t,"小鹏");
t1.start();
t2.start();
}
}
线程状态:
线程生命周期图片
多线程并发隐患图片